// Copyright 2015 Google Inc.
|
//
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
// you may not use this file except in compliance with the License.
|
// You may obtain a copy of the License at
|
//
|
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
// Unless required by applicable law or agreed to in writing, software
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// See the License for the specific language governing permissions and
|
// limitations under the License.
|
//
|
////////////////////////////////////////////////////////////////////////////////
|
|
#include "src/tiff_parser.h"
|
|
#include <cstring>
|
#include <limits>
|
#include <numeric>
|
|
#include "src/tiff_directory/tiff_directory.h"
|
|
namespace piex {
|
namespace {
|
|
using tiff_directory::Endian;
|
using tiff_directory::Rational;
|
using tiff_directory::SRational;
|
using tiff_directory::SizeOfType;
|
using tiff_directory::TIFF_TYPE_LONG;
|
using tiff_directory::TIFF_TYPE_UNDEFINED;
|
using tiff_directory::TiffDirectory;
|
using tiff_directory::kBigEndian;
|
using tiff_directory::kLittleEndian;
|
|
// Specifies all tags that might be of interest to parse JPEG data.
|
const std::uint32_t kStartOfFrame = 0xFFC0;
|
const std::uint32_t kStartOfImage = 0xFFD8;
|
const std::uint32_t kStartOfScan = 0xFFDA;
|
|
bool GetFullDimension16(const TiffDirectory& tiff_directory,
|
std::uint16_t* width, std::uint16_t* height) {
|
std::uint32_t tmp_width = 0;
|
std::uint32_t tmp_height = 0;
|
if (!GetFullDimension32(tiff_directory, &tmp_width, &tmp_height) ||
|
tmp_width > std::numeric_limits<std::uint16_t>::max() ||
|
tmp_height > std::numeric_limits<std::uint16_t>::max()) {
|
return false;
|
}
|
*width = static_cast<std::uint16_t>(tmp_width);
|
*height = static_cast<std::uint16_t>(tmp_height);
|
return true;
|
}
|
|
bool GetRational(const TiffDirectory::Tag& tag, const TiffDirectory& directory,
|
const int data_size, PreviewImageData::Rational* data) {
|
std::vector<Rational> value;
|
if (directory.Get(tag, &value) &&
|
value.size() == static_cast<size_t>(data_size)) {
|
for (size_t i = 0; i < value.size(); ++i) {
|
data[i].numerator = value[i].numerator;
|
data[i].denominator = value[i].denominator;
|
}
|
return true;
|
}
|
return false;
|
}
|
|
void FillGpsPreviewImageData(const TiffDirectory& gps_directory,
|
PreviewImageData* preview_image_data) {
|
if (gps_directory.Has(kGpsTagLatitudeRef) &&
|
gps_directory.Has(kGpsTagLatitude) &&
|
gps_directory.Has(kGpsTagLongitudeRef) &&
|
gps_directory.Has(kGpsTagLongitude) &&
|
gps_directory.Has(kGpsTagTimeStamp) &&
|
gps_directory.Has(kGpsTagDateStamp)) {
|
preview_image_data->gps.is_valid = false;
|
std::string value;
|
if (!gps_directory.Get(kGpsTagLatitudeRef, &value) || value.empty() ||
|
(value[0] != 'N' && value[0] != 'S') ||
|
!GetRational(kGpsTagLatitude, gps_directory, 3 /* data size */,
|
preview_image_data->gps.latitude)) {
|
return;
|
}
|
preview_image_data->gps.latitude_ref = value[0];
|
|
if (!gps_directory.Get(kGpsTagLongitudeRef, &value) || value.empty() ||
|
(value[0] != 'E' && value[0] != 'W') ||
|
!GetRational(kGpsTagLongitude, gps_directory, 3 /* data size */,
|
preview_image_data->gps.longitude)) {
|
return;
|
}
|
preview_image_data->gps.longitude_ref = value[0];
|
|
if (!GetRational(kGpsTagTimeStamp, gps_directory, 3 /* data size */,
|
preview_image_data->gps.time_stamp)) {
|
return;
|
}
|
|
const size_t kGpsDateStampSize = 11;
|
if (!gps_directory.Get(kGpsTagDateStamp,
|
&preview_image_data->gps.date_stamp)) {
|
return;
|
}
|
if (preview_image_data->gps.date_stamp.size() == kGpsDateStampSize) {
|
// Resize the date_stamp to remove the "NULL" at the end of string.
|
preview_image_data->gps.date_stamp.resize(kGpsDateStampSize - 1);
|
} else {
|
return;
|
}
|
|
if (gps_directory.Has(kGpsTagAltitudeRef) &&
|
gps_directory.Has(kGpsTagAltitude)) {
|
std::vector<std::uint8_t> bytes;
|
if (!gps_directory.Get(kGpsTagAltitudeRef, &bytes) || bytes.empty() ||
|
!GetRational(kGpsTagAltitude, gps_directory, 1,
|
&preview_image_data->gps.altitude)) {
|
return;
|
}
|
preview_image_data->gps.altitude_ref = bytes[0] != 0;
|
}
|
preview_image_data->gps.is_valid = true;
|
}
|
}
|
|
void GetImageSize(const TiffDirectory& tiff_directory, StreamInterface* stream,
|
Image* image) {
|
switch (image->format) {
|
case Image::kUncompressedRgb: {
|
GetFullDimension16(tiff_directory, &image->width, &image->height);
|
break;
|
}
|
case Image::kJpegCompressed: {
|
GetJpegDimensions(image->offset, stream, &image->width, &image->height);
|
break;
|
}
|
default: { return; }
|
}
|
}
|
|
bool FillPreviewImageData(const TiffDirectory& tiff_directory,
|
StreamInterface* stream,
|
PreviewImageData* preview_image_data) {
|
bool success = true;
|
// Get preview or thumbnail. The code assumes that only thumbnails can be
|
// uncompressed. Preview images are always JPEG compressed.
|
Image image;
|
if (GetImageData(tiff_directory, stream, &image)) {
|
if (IsThumbnail(image)) {
|
preview_image_data->thumbnail = image;
|
} else if (image.format == Image::kJpegCompressed) {
|
preview_image_data->preview = image;
|
}
|
}
|
|
// Get exif_orientation if it was not set already.
|
if (tiff_directory.Has(kTiffTagOrientation) &&
|
preview_image_data->exif_orientation == 1) {
|
success &= tiff_directory.Get(kTiffTagOrientation,
|
&preview_image_data->exif_orientation);
|
}
|
|
// Get color_space
|
if (tiff_directory.Has(kExifTagColorSpace)) {
|
std::uint32_t color_space;
|
if (tiff_directory.Get(kExifTagColorSpace, &color_space)) {
|
if (color_space == 1) {
|
preview_image_data->color_space = PreviewImageData::kSrgb;
|
} else if (color_space == 65535 || color_space == 2) {
|
preview_image_data->color_space = PreviewImageData::kAdobeRgb;
|
}
|
} else {
|
success = false;
|
}
|
}
|
|
success &= GetFullDimension32(tiff_directory, &preview_image_data->full_width,
|
&preview_image_data->full_height);
|
|
if (tiff_directory.Has(kTiffTagMake)) {
|
success &= tiff_directory.Get(kTiffTagMake, &preview_image_data->maker);
|
}
|
|
if (tiff_directory.Has(kTiffTagModel)) {
|
success &= tiff_directory.Get(kTiffTagModel, &preview_image_data->model);
|
}
|
|
if (tiff_directory.Has(kTiffTagCfaPatternDim)) {
|
std::vector<std::uint32_t> cfa_pattern_dim;
|
if (tiff_directory.Get(kTiffTagCfaPatternDim, &cfa_pattern_dim) &&
|
cfa_pattern_dim.size() == 2) {
|
preview_image_data->cfa_pattern_dim[0] = cfa_pattern_dim[0];
|
preview_image_data->cfa_pattern_dim[1] = cfa_pattern_dim[1];
|
}
|
}
|
|
if (tiff_directory.Has(kExifTagDateTimeOriginal)) {
|
success &= tiff_directory.Get(kExifTagDateTimeOriginal,
|
&preview_image_data->date_time);
|
}
|
|
if (tiff_directory.Has(kExifTagIsoSpeed)) {
|
success &= tiff_directory.Get(kExifTagIsoSpeed, &preview_image_data->iso);
|
} else if (tiff_directory.Has(kPanaTagIso)) {
|
success &= tiff_directory.Get(kPanaTagIso, &preview_image_data->iso);
|
}
|
|
if (tiff_directory.Has(kExifTagExposureTime)) {
|
success &= GetRational(kExifTagExposureTime, tiff_directory, 1,
|
&preview_image_data->exposure_time);
|
}
|
|
if (tiff_directory.Has(kExifTagFnumber)) {
|
success &= GetRational(kExifTagFnumber, tiff_directory, 1,
|
&preview_image_data->fnumber);
|
}
|
|
if (tiff_directory.Has(kExifTagFocalLength)) {
|
success &= GetRational(kExifTagFocalLength, tiff_directory, 1,
|
&preview_image_data->focal_length);
|
}
|
|
return success;
|
}
|
|
const TiffDirectory* FindFirstTagInIfds(const TiffDirectory::Tag& tag,
|
const IfdVector& tiff_directory) {
|
for (std::uint32_t i = 0; i < tiff_directory.size(); ++i) {
|
if (tiff_directory[i].Has(tag)) {
|
return &tiff_directory[i];
|
}
|
|
// Recursively search sub directories.
|
const TiffDirectory* sub_directory =
|
FindFirstTagInIfds(tag, tiff_directory[i].GetSubDirectories());
|
if (sub_directory != NULL) {
|
return sub_directory;
|
}
|
}
|
return NULL;
|
}
|
|
// Return true if all data blocks are ordered one after the other without gaps.
|
bool OffsetsAreConsecutive(
|
const std::vector<std::uint32_t>& strip_offsets,
|
const std::vector<std::uint32_t>& strip_byte_counts) {
|
if (strip_offsets.size() != strip_byte_counts.size() ||
|
strip_offsets.empty()) {
|
return false;
|
}
|
|
for (size_t i = 0; i < strip_offsets.size() - 1; ++i) {
|
if (strip_offsets[i] + strip_byte_counts[i] != strip_offsets[i + 1]) {
|
return false;
|
}
|
}
|
return true;
|
}
|
|
// Gets the SubIfd content.
|
bool ParseSubIfds(const std::uint32_t tiff_offset, const TagSet& desired_tags,
|
const std::uint32_t max_number_ifds, const Endian endian,
|
StreamInterface* stream, TiffDirectory* tiff_ifd) {
|
if (tiff_ifd->Has(kTiffTagSubIfd)) {
|
std::uint32_t offset = 0;
|
std::uint32_t length = 0;
|
tiff_ifd->GetOffsetAndLength(kTiffTagSubIfd, TIFF_TYPE_LONG, &offset,
|
&length);
|
length /= 4; // length in bytes divided by 4 gives number of IFDs.
|
for (std::uint32_t j = 0; j < length && j < max_number_ifds; ++j) {
|
std::uint32_t sub_offset;
|
if (!Get32u(stream, offset + 4 * j, endian, &sub_offset)) {
|
return false;
|
}
|
|
std::uint32_t next_ifd_offset;
|
TiffDirectory sub_ifd(static_cast<Endian>(endian));
|
if (!ParseDirectory(tiff_offset, sub_offset, endian, desired_tags, stream,
|
&sub_ifd, &next_ifd_offset)) {
|
return false;
|
}
|
|
tiff_ifd->AddSubDirectory(sub_ifd);
|
}
|
}
|
return true;
|
}
|
|
} // namespace
|
|
bool Get16u(StreamInterface* stream, const std::uint32_t offset,
|
const Endian& endian, std::uint16_t* value) {
|
std::uint8_t data[2];
|
if (stream->GetData(offset, 2, data) == kOk) {
|
if (endian == kBigEndian) {
|
*value = (data[0] * 0x100) | data[1];
|
} else {
|
*value = (data[1] * 0x100) | data[0];
|
}
|
return true;
|
} else {
|
return false;
|
}
|
}
|
|
bool Get32u(StreamInterface* stream, const std::uint32_t offset,
|
const Endian& endian, std::uint32_t* value) {
|
std::uint8_t data[4];
|
if (stream->GetData(offset, 4, data) == kOk) {
|
if (endian == kBigEndian) {
|
*value = (data[0] * 0x1000000u) | (data[1] * 0x10000u) |
|
(data[2] * 0x100u) | data[3];
|
} else {
|
*value = (data[3] * 0x1000000u) | (data[2] * 0x10000u) |
|
(data[1] * 0x100u) | data[0];
|
}
|
return true;
|
} else {
|
return false;
|
}
|
}
|
|
std::vector<std::uint8_t> GetData(const size_t offset, const size_t length,
|
StreamInterface* stream, Error* error) {
|
// Read in chunks with a maximum size of 1 MiB.
|
const size_t kChunkSize = 1048576;
|
|
std::vector<std::uint8_t> data;
|
size_t processed_data = 0;
|
while (*error == kOk && processed_data < length) {
|
size_t chunk_length = kChunkSize;
|
if (length - data.size() < kChunkSize) {
|
chunk_length = length - data.size();
|
}
|
|
data.resize(processed_data + chunk_length);
|
*error = stream->GetData(offset + processed_data, chunk_length,
|
&data[processed_data]);
|
|
processed_data += chunk_length;
|
}
|
return data;
|
}
|
|
bool GetEndianness(const std::uint32_t tiff_offset, StreamInterface* stream,
|
Endian* endian) {
|
const std::uint8_t kTiffBigEndianMagic[] = {'M', 'M'};
|
const std::uint8_t kTiffLittleEndianMagic[] = {'I', 'I'};
|
std::uint8_t tiff_endian[sizeof(kTiffBigEndianMagic)];
|
if (stream->GetData(tiff_offset, sizeof(tiff_endian), &tiff_endian[0]) !=
|
kOk) {
|
return false;
|
}
|
|
if (!memcmp(tiff_endian, kTiffLittleEndianMagic, sizeof(tiff_endian))) {
|
*endian = kLittleEndian;
|
return true;
|
} else if (!memcmp(tiff_endian, kTiffBigEndianMagic, sizeof(tiff_endian))) {
|
*endian = kBigEndian;
|
return true;
|
} else {
|
return false;
|
}
|
}
|
|
bool GetImageData(const TiffDirectory& tiff_directory, StreamInterface* stream,
|
Image* image) {
|
std::uint32_t length = 0;
|
std::uint32_t offset = 0;
|
|
if (tiff_directory.Has(kTiffTagJpegOffset) &&
|
tiff_directory.Has(kTiffTagJpegByteCount)) {
|
if (!tiff_directory.Get(kTiffTagJpegOffset, &offset) ||
|
!tiff_directory.Get(kTiffTagJpegByteCount, &length)) {
|
return false;
|
}
|
image->format = Image::kJpegCompressed;
|
} else if (tiff_directory.Has(kTiffTagStripOffsets) &&
|
tiff_directory.Has(kTiffTagStripByteCounts)) {
|
std::vector<std::uint32_t> strip_offsets;
|
std::vector<std::uint32_t> strip_byte_counts;
|
if (!tiff_directory.Get(kTiffTagStripOffsets, &strip_offsets) ||
|
!tiff_directory.Get(kTiffTagStripByteCounts, &strip_byte_counts)) {
|
return false;
|
}
|
|
std::uint32_t compression = 0;
|
if (!OffsetsAreConsecutive(strip_offsets, strip_byte_counts) ||
|
!tiff_directory.Get(kTiffTagCompression, &compression)) {
|
return false;
|
}
|
|
std::uint32_t photometric_interpretation = 0;
|
if (tiff_directory.Get(kTiffTagPhotometric, &photometric_interpretation) &&
|
photometric_interpretation != 2 /* RGB */ &&
|
photometric_interpretation != 6 /* YCbCr */) {
|
return false;
|
}
|
|
switch (compression) {
|
case 1: /*uncompressed*/
|
image->format = Image::kUncompressedRgb;
|
break;
|
case 6: /* JPEG(old) */
|
case 7: /* JPEG */
|
image->format = Image::kJpegCompressed;
|
break;
|
default:
|
return false;
|
}
|
length = static_cast<std::uint32_t>(
|
std::accumulate(strip_byte_counts.begin(), strip_byte_counts.end(), 0));
|
offset = strip_offsets[0];
|
} else if (tiff_directory.Has(kPanaTagJpegImage)) {
|
if (!tiff_directory.GetOffsetAndLength(
|
kPanaTagJpegImage, TIFF_TYPE_UNDEFINED, &offset, &length)) {
|
return false;
|
}
|
image->format = Image::kJpegCompressed;
|
} else {
|
return false;
|
}
|
|
image->length = length;
|
image->offset = offset;
|
GetImageSize(tiff_directory, stream, image);
|
return true;
|
}
|
|
bool GetJpegDimensions(const std::uint32_t jpeg_offset, StreamInterface* stream,
|
std::uint16_t* width, std::uint16_t* height) {
|
const Endian endian = kBigEndian;
|
std::uint32_t offset = jpeg_offset;
|
std::uint16_t segment;
|
|
// Parse the JPEG header until we find Frame0 which contains the image width
|
// and height or the actual image data starts (StartOfScan)
|
do {
|
if (!Get16u(stream, offset, endian, &segment)) {
|
return false;
|
}
|
offset += 2;
|
|
switch (segment) {
|
case kStartOfImage:
|
break;
|
case kStartOfFrame:
|
return Get16u(stream, offset + 3, endian, height) &&
|
Get16u(stream, offset + 5, endian, width);
|
default: {
|
std::uint16_t length;
|
if (!Get16u(stream, offset, endian, &length)) {
|
return false;
|
}
|
offset += length;
|
}
|
}
|
} while (segment != kStartOfScan);
|
|
// No width and hight information found.
|
return false;
|
}
|
|
bool IsThumbnail(const Image& image, const int max_dimension) {
|
return image.width <= max_dimension && image.height <= max_dimension;
|
}
|
|
bool ParseDirectory(const std::uint32_t tiff_offset,
|
const std::uint32_t ifd_offset, const Endian endian,
|
const TagSet& desired_tags, StreamInterface* stream,
|
TiffDirectory* tiff_directory,
|
std::uint32_t* next_ifd_offset) {
|
std::uint16_t number_of_entries;
|
if (!Get16u(stream, ifd_offset, endian, &number_of_entries)) {
|
return false;
|
}
|
|
for (std::uint32_t i = 0;
|
i < static_cast<std::uint32_t>(number_of_entries) * 12; i += 12) {
|
std::uint16_t tag;
|
std::uint16_t type;
|
std::uint32_t number_of_elements;
|
if (Get16u(stream, ifd_offset + 2 + i, endian, &tag) &&
|
Get16u(stream, ifd_offset + 4 + i, endian, &type) &&
|
Get32u(stream, ifd_offset + 6 + i, endian, &number_of_elements)) {
|
// Check if the current tag should be handled.
|
if (desired_tags.count(static_cast<TiffDirectory::Tag>(tag)) != 1) {
|
continue;
|
}
|
} else {
|
return false;
|
}
|
|
const size_t type_size = SizeOfType(type, nullptr /* no error */);
|
|
// Check that type_size * number_of_elements does not exceed UINT32_MAX.
|
if (type_size != 0 && number_of_elements > UINT32_MAX / type_size) {
|
return false;
|
}
|
const size_t byte_count =
|
type_size * static_cast<size_t>(number_of_elements);
|
|
std::uint32_t value_offset;
|
if (byte_count > 4 &&
|
Get32u(stream, ifd_offset + 10 + i, endian, &value_offset)) {
|
value_offset += tiff_offset;
|
} else if (byte_count != 0) {
|
value_offset = ifd_offset + 10 + i;
|
} else {
|
// Ignore entries with an invalid byte count.
|
continue;
|
}
|
|
Error error = kOk;
|
const std::vector<std::uint8_t> data =
|
GetData(value_offset, byte_count, stream, &error);
|
if (error != kOk) {
|
return false;
|
}
|
tiff_directory->AddEntry(tag, type, number_of_elements, value_offset, data);
|
}
|
|
return Get32u(stream, ifd_offset + 2u + number_of_entries * 12u, endian,
|
next_ifd_offset);
|
}
|
|
bool GetExifOrientation(StreamInterface* stream, const std::uint32_t offset,
|
std::uint32_t* orientation) {
|
const TagSet kOrientationTagSet = {kTiffTagOrientation};
|
const std::uint32_t kNumberOfIfds = 1;
|
|
TiffContent tiff_content;
|
if (!TiffParser(stream, offset)
|
.Parse(kOrientationTagSet, kNumberOfIfds, &tiff_content)) {
|
return false;
|
}
|
|
for (const auto& tiff_directory : tiff_content.tiff_directory) {
|
if (tiff_directory.Has(kTiffTagOrientation) &&
|
tiff_directory.Get(kTiffTagOrientation, orientation)) {
|
return true;
|
}
|
}
|
|
return false;
|
}
|
|
bool GetFullDimension32(const TiffDirectory& tiff_directory,
|
std::uint32_t* width, std::uint32_t* height) {
|
// The sub file type needs to be 0 (main image) to contain a valid full
|
// dimensions. This is important in particular for DNG.
|
if (tiff_directory.Has(kTiffTagSubFileType)) {
|
std::uint32_t sub_file_type;
|
if (!tiff_directory.Get(kTiffTagSubFileType, &sub_file_type) ||
|
sub_file_type != 0) {
|
return false;
|
}
|
}
|
|
if (tiff_directory.Has(kExifTagDefaultCropSize)) {
|
if (!GetFullCropDimension(tiff_directory, width, height)) {
|
return false;
|
}
|
} else if (tiff_directory.Has(kExifTagWidth) &&
|
tiff_directory.Has(kExifTagHeight)) {
|
if (!tiff_directory.Get(kExifTagWidth, width) ||
|
!tiff_directory.Get(kExifTagHeight, height)) {
|
return false;
|
}
|
} else if (tiff_directory.Has(kTiffTagImageWidth) &&
|
tiff_directory.Has(kTiffTagImageLength)) {
|
if (!tiff_directory.Get(kTiffTagImageWidth, width) ||
|
!tiff_directory.Get(kTiffTagImageLength, height)) {
|
return false;
|
}
|
} else if (tiff_directory.Has(kPanaTagTopBorder) &&
|
tiff_directory.Has(kPanaTagLeftBorder) &&
|
tiff_directory.Has(kPanaTagBottomBorder) &&
|
tiff_directory.Has(kPanaTagRightBorder)) {
|
std::uint32_t left;
|
std::uint32_t right;
|
std::uint32_t top;
|
std::uint32_t bottom;
|
if (tiff_directory.Get(kPanaTagLeftBorder, &left) &&
|
tiff_directory.Get(kPanaTagRightBorder, &right) &&
|
tiff_directory.Get(kPanaTagTopBorder, &top) &&
|
tiff_directory.Get(kPanaTagBottomBorder, &bottom) && bottom > top &&
|
right > left) {
|
*height = bottom - top;
|
*width = right - left;
|
} else {
|
return false;
|
}
|
}
|
return true;
|
}
|
|
bool GetFullCropDimension(const tiff_directory::TiffDirectory& tiff_directory,
|
std::uint32_t* width, std::uint32_t* height) {
|
if (!tiff_directory.Has(kExifTagDefaultCropSize)) {
|
// This doesn't look right to return true here, as we have not written
|
// anything to *width and *height. However, changing the return value here
|
// causes a whole bunch of tests to fail.
|
// TODO(timurrrr): Return false and fix the tests.
|
// In fact, this whole if() seems to be not needed,
|
// as tiff_directory(kExifTagDefaultCropSize) will return false below.
|
return true;
|
}
|
|
std::vector<std::uint32_t> crop(2);
|
if (tiff_directory.Get(kExifTagDefaultCropSize, &crop)) {
|
if (crop.size() == 2 && crop[0] > 0 && crop[1] > 0) {
|
*width = crop[0];
|
*height = crop[1];
|
return true;
|
} else {
|
return false;
|
}
|
}
|
|
std::vector<Rational> crop_rational(2);
|
if (tiff_directory.Get(kExifTagDefaultCropSize, &crop_rational)) {
|
if (crop_rational.size() == 2 && crop_rational[0].numerator > 0 &&
|
crop_rational[0].denominator > 0 && crop_rational[1].numerator > 0 &&
|
crop_rational[1].denominator > 0) {
|
*width = crop_rational[0].numerator / crop_rational[0].denominator;
|
*height = crop_rational[1].numerator / crop_rational[1].denominator;
|
return true;
|
} else {
|
return false;
|
}
|
}
|
|
return false;
|
}
|
|
TiffParser::TiffParser(StreamInterface* stream) : stream_(stream) {}
|
|
TiffParser::TiffParser(StreamInterface* stream, const std::uint32_t offset)
|
: stream_(stream), tiff_offset_(offset) {}
|
|
bool TiffParser::GetPreviewImageData(const TiffContent& tiff_content,
|
PreviewImageData* preview_image_data) {
|
bool success = true;
|
for (const auto& tiff_directory : tiff_content.tiff_directory) {
|
success = FillPreviewImageData(tiff_directory, stream_, preview_image_data);
|
if (success && tiff_directory.Has(kTiffTagExifIfd) &&
|
tiff_content.exif_directory) {
|
success = FillPreviewImageData(*tiff_content.exif_directory, stream_,
|
preview_image_data);
|
}
|
if (success && tiff_directory.Has(kExifTagGps) &&
|
tiff_content.gps_directory) {
|
FillGpsPreviewImageData(*tiff_content.gps_directory, preview_image_data);
|
}
|
for (const auto& sub_directory : tiff_directory.GetSubDirectories()) {
|
if (success) {
|
success =
|
FillPreviewImageData(sub_directory, stream_, preview_image_data);
|
}
|
}
|
}
|
return success;
|
}
|
|
bool TiffParser::Parse(const TagSet& desired_tags,
|
const std::uint16_t max_number_ifds,
|
TiffContent* tiff_content) {
|
if (!tiff_content->tiff_directory.empty()) {
|
return false; // You shall call Parse() only once.
|
}
|
|
const std::uint32_t kTiffIdentifierSize = 4;
|
std::uint32_t offset_to_ifd = 0;
|
if (!GetEndianness(tiff_offset_, stream_, &endian_) ||
|
!Get32u(stream_, tiff_offset_ + kTiffIdentifierSize, endian_,
|
&offset_to_ifd)) {
|
return false;
|
}
|
|
if (!ParseIfd(tiff_offset_ + offset_to_ifd, desired_tags, max_number_ifds,
|
&tiff_content->tiff_directory)) {
|
return false;
|
}
|
|
// Get the Exif data.
|
if (FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory) !=
|
nullptr) {
|
const TiffDirectory* tiff_ifd =
|
FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory);
|
std::uint32_t offset;
|
if (tiff_ifd->Get(kTiffTagExifIfd, &offset)) {
|
tiff_content->exif_directory.reset(new TiffDirectory(endian_));
|
std::uint32_t next_ifd_offset;
|
if (!ParseDirectory(
|
tiff_offset_, tiff_offset_ + offset, endian_, desired_tags,
|
stream_, tiff_content->exif_directory.get(), &next_ifd_offset)) {
|
return false;
|
}
|
|
return ParseGpsData(tiff_ifd, tiff_content);
|
}
|
}
|
|
// Get the GPS data from the tiff ifd.
|
if (FindFirstTagInIfds(kExifTagGps, tiff_content->tiff_directory) !=
|
nullptr) {
|
const TiffDirectory* tiff_ifd =
|
FindFirstTagInIfds(kExifTagGps, tiff_content->tiff_directory);
|
return ParseGpsData(tiff_ifd, tiff_content);
|
}
|
|
return true;
|
}
|
|
bool TiffParser::ParseIfd(const std::uint32_t offset_to_ifd,
|
const TagSet& desired_tags,
|
const std::uint16_t max_number_ifds,
|
IfdVector* tiff_directory) {
|
std::uint32_t next_ifd_offset;
|
TiffDirectory tiff_ifd(static_cast<Endian>(endian_));
|
if (!ParseDirectory(tiff_offset_, offset_to_ifd, endian_, desired_tags,
|
stream_, &tiff_ifd, &next_ifd_offset) ||
|
!ParseSubIfds(tiff_offset_, desired_tags, max_number_ifds, endian_,
|
stream_, &tiff_ifd)) {
|
return false;
|
}
|
|
tiff_directory->push_back(tiff_ifd);
|
if (next_ifd_offset != 0 && tiff_directory->size() < max_number_ifds) {
|
return ParseIfd(tiff_offset_ + next_ifd_offset, desired_tags,
|
max_number_ifds, tiff_directory);
|
}
|
return true;
|
}
|
|
bool TiffParser::ParseGpsData(const TiffDirectory* tiff_ifd,
|
TiffContent* tiff_content) {
|
std::uint32_t offset;
|
if (tiff_ifd->Get(kExifTagGps, &offset)) {
|
tiff_content->gps_directory.reset(new TiffDirectory(endian_));
|
const TagSet gps_tags = {kGpsTagLatitudeRef, kGpsTagLatitude,
|
kGpsTagLongitudeRef, kGpsTagLongitude,
|
kGpsTagAltitudeRef, kGpsTagAltitude,
|
kGpsTagTimeStamp, kGpsTagDateStamp};
|
std::uint32_t next_ifd_offset;
|
return ParseDirectory(tiff_offset_, tiff_offset_ + offset, endian_,
|
gps_tags, stream_, tiff_content->gps_directory.get(),
|
&next_ifd_offset);
|
}
|
return true;
|
}
|
|
} // namespace piex
|