aboutsummaryrefslogtreecommitdiff
path: root/src/tiff_parser.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/tiff_parser.cc')
-rw-r--r--src/tiff_parser.cc580
1 files changed, 580 insertions, 0 deletions
diff --git a/src/tiff_parser.cc b/src/tiff_parser.cc
new file mode 100644
index 0000000..a36d5c8
--- /dev/null
+++ b/src/tiff_parser.cc
@@ -0,0 +1,580 @@
+// 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 "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;
+
+// Reads the width and height of the full resolution image. The tag groups are
+// exclusive.
+bool GetFullDimension(const TiffDirectory& tiff_directory, std::uint32_t* width,
+ std::uint32_t* height) {
+ 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;
+ }
+ } else if (tiff_directory.Has(kExifTagDefaultCropSize)) {
+ std::vector<std::uint32_t> crop(2);
+ std::vector<Rational> crop_rational(2);
+ if (tiff_directory.Get(kExifTagDefaultCropSize, &crop)) {
+ *width = crop[0];
+ *height = crop[1];
+ } else if (tiff_directory.Get(kExifTagDefaultCropSize, &crop_rational) &&
+ crop_rational[0].denominator != 0 &&
+ crop_rational[1].denominator != 0) {
+ *width = crop_rational[0].numerator / crop_rational[0].denominator;
+ *height = crop_rational[1].numerator / crop_rational[1].denominator;
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool GetRational(const Tags& tag, const TiffDirectory& directory,
+ const int data_size, PreviewImageData::Rational* data) {
+ std::vector<Rational> value;
+ if (directory.Get(tag, &value)) {
+ 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;
+ }
+
+ constexpr 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;
+ }
+}
+
+Error FillPreviewImageData(const TiffDirectory& tiff_directory,
+ PreviewImageData* preview_image_data) {
+ bool success = true;
+ // Get jpeg_offset and jpeg_length
+ 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 kFail;
+ }
+ if (strip_offsets.size() == 1 && strip_byte_counts.size() == 1) {
+ preview_image_data->preview_offset = strip_offsets[0];
+ preview_image_data->preview_length = strip_byte_counts[0];
+ }
+ } else if (tiff_directory.Has(kTiffTagJpegOffset) &&
+ tiff_directory.Has(kTiffTagJpegByteCount)) {
+ success &= tiff_directory.Get(kTiffTagJpegOffset,
+ &preview_image_data->preview_offset);
+ success &= tiff_directory.Get(kTiffTagJpegByteCount,
+ &preview_image_data->preview_length);
+ } else if (tiff_directory.Has(kPanaTagJpegImage)) {
+ if (!tiff_directory.GetOffsetAndLength(
+ kPanaTagJpegImage, TIFF_TYPE_UNDEFINED,
+ &preview_image_data->preview_offset,
+ &preview_image_data->preview_length)) {
+ return kFail;
+ }
+ }
+
+ // Get exif_orientation
+ if (tiff_directory.Has(kTiffTagOrientation)) {
+ success &= tiff_directory.Get(kTiffTagOrientation,
+ &preview_image_data->exif_orientation);
+ }
+
+ // Get color_space
+ if (tiff_directory.Has(kExifTagColorSpace)) {
+ std::uint32_t color_space;
+ success &= tiff_directory.Get(kExifTagColorSpace, &color_space);
+ if (color_space == 1) {
+ preview_image_data->color_space = PreviewImageData::kSrgb;
+ } else if (color_space == 65535) {
+ preview_image_data->color_space = PreviewImageData::kAdobeRgb;
+ }
+ }
+
+ success &= GetFullDimension(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(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);
+ }
+
+ if (!success) {
+ return kFail;
+ }
+
+ return kOk;
+}
+
+const TiffDirectory* FindFirstTagInIfds(const Tags& 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;
+}
+
+// Gets the SubIfd content.
+void 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,
+ Error* error) {
+ if (*error == kOk && 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)) {
+ *error = kFail;
+ return;
+ }
+
+ std::uint32_t next_ifd_offset;
+ TiffDirectory sub_ifd(static_cast<Endian>(endian));
+ *error = ParseDirectory(tiff_offset, sub_offset, endian, desired_tags,
+ stream, &sub_ifd, &next_ifd_offset);
+ if (*error != kOk) {
+ return;
+ }
+
+ tiff_ifd->AddSubDirectory(sub_ifd);
+ }
+ }
+}
+
+} // 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] * 0x1000000) | (data[1] * 0x10000) | (data[2] * 0x100) |
+ data[3];
+ } else {
+ *value = (data[3] * 0x1000000) | (data[2] * 0x10000) | (data[1] * 0x100) |
+ 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 GetPreviewDimensions(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;
+}
+
+Error 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 kFail;
+ }
+
+ 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<Tags>(tag)) != 1) {
+ continue;
+ }
+ } else {
+ return kFail;
+ }
+
+ 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 kFail;
+ }
+ 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 error;
+ }
+ tiff_directory->AddEntry(tag, type, number_of_elements, value_offset, data);
+ }
+
+ if (Get32u(stream, ifd_offset + 2 + number_of_entries * 12, endian,
+ next_ifd_offset)) {
+ return kOk;
+ } else {
+ return kFail;
+ }
+}
+
+TiffParser::TiffParser(StreamInterface* stream) : stream_(stream) {}
+
+TiffParser::TiffParser(StreamInterface* stream, const std::uint32_t offset)
+ : stream_(stream), tiff_offset_(offset) {}
+
+Error TiffParser::GetPreviewImageData(const TiffContent& tiff_content,
+ PreviewImageData* preview_image_data) {
+ Error error = kOk;
+ for (const auto& tiff_directory : tiff_content.tiff_directory) {
+ error = FillPreviewImageData(tiff_directory, preview_image_data);
+ if (error == kOk && tiff_directory.Has(kTiffTagExifIfd) &&
+ tiff_content.exif_directory) {
+ error = FillPreviewImageData(*tiff_content.exif_directory,
+ preview_image_data);
+ }
+ if (error == kOk && 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 (error == kOk) {
+ error = FillPreviewImageData(sub_directory, preview_image_data);
+ }
+ }
+ }
+ return error;
+}
+
+Error TiffParser::Parse(const TagSet& desired_tags,
+ const std::uint16_t max_number_ifds,
+ TiffContent* tiff_content) {
+ if (!tiff_content->tiff_directory.empty()) {
+ return kFail; // 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 kFail;
+ }
+
+ Error error = ParseIfd(tiff_offset_ + offset_to_ifd, desired_tags,
+ max_number_ifds, &tiff_content->tiff_directory);
+ if (error != kOk) {
+ return error;
+ }
+
+ // Get the Exif data.
+ const TiffDirectory* tiff_ifd =
+ FindFirstTagInIfds(kTiffTagExifIfd, tiff_content->tiff_directory);
+ if (tiff_ifd != NULL) {
+ std::uint32_t offset;
+ if (tiff_ifd->Get(kTiffTagExifIfd, &offset)) {
+ tiff_content->exif_directory.reset(new TiffDirectory(endian_));
+ std::uint32_t next_ifd_offset;
+ error = ParseDirectory(
+ tiff_offset_, tiff_offset_ + offset, endian_, desired_tags, stream_,
+ tiff_content->exif_directory.get(), &next_ifd_offset);
+ if (error != kOk) {
+ return error;
+ }
+
+ 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};
+ return ParseDirectory(
+ tiff_offset_, tiff_offset_ + offset, endian_, gps_tags, stream_,
+ tiff_content->gps_directory.get(), &next_ifd_offset);
+ }
+ }
+ }
+
+ return error;
+}
+
+Error 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_));
+ Error error =
+ 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, &error);
+ if (error == kOk) {
+ tiff_directory->push_back(tiff_ifd);
+ if (next_ifd_offset != 0 && tiff_directory->size() < max_number_ifds) {
+ error = ParseIfd(tiff_offset_ + next_ifd_offset, desired_tags,
+ max_number_ifds, tiff_directory);
+ }
+ }
+
+ return error;
+}
+
+} // namespace piex