From 2f5d8eafb5648056703d6b3dec237d293f75f99e Mon Sep 17 00:00:00 2001 From: Yujie Qin Date: Wed, 6 Apr 2016 12:55:08 +0200 Subject: Update PIEX * Add IsOfType() and GEtNumberofBytesForIsOfType() to image_type_recognition_lite * Add GetDngInformation() and GetOrientation() to piex * Remove deprecated data entries from piex_types --- .../image_type_recognition_lite.cc | 127 +++--- .../image_type_recognition_lite.h | 8 + src/piex.cc | 446 +++++++++++---------- src/piex.h | 11 +- src/piex_types.h | 7 +- src/tiff_parser.cc | 433 ++++++++++++-------- src/tiff_parser.h | 67 ++-- 7 files changed, 621 insertions(+), 478 deletions(-) diff --git a/src/image_type_recognition/image_type_recognition_lite.cc b/src/image_type_recognition/image_type_recognition_lite.cc index 45429fd..e0c8491 100644 --- a/src/image_type_recognition/image_type_recognition_lite.cc +++ b/src/image_type_recognition/image_type_recognition_lite.cc @@ -61,6 +61,13 @@ class TypeChecker { // Checks if source data belongs to current checker type. virtual bool IsMyType(const RangeCheckedBytePtr& source) const = 0; + + protected: + // Limits the source length to the RequestedSize(), using it guarantees that + // we will not read more than this size from the source. + RangeCheckedBytePtr LimitSource(const RangeCheckedBytePtr& source) const { + return source.pointerToSubArray(0 /* pos */, RequestedSize()); + } }; // Check if the uint16 value at (source + offset) is equal to the target value. @@ -150,10 +157,7 @@ class ArwTypeChecker : public TypeChecker { // 3. signature "SONY" in first requested bytes; // 4. correct signature for (section + version) in first requested bytes. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -209,10 +213,7 @@ class Cr2TypeChecker : public TypeChecker { // 2. magic number "42" at the (offset == 2) position of the file; // 3. signature "CR2" at the (offset == 8) position of the file. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -239,10 +240,7 @@ class CrwTypeChecker : public TypeChecker { // Check only the signature at the (offset == 6) position of the file. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -271,10 +269,7 @@ class DcrTypeChecker : public TypeChecker { // 2. two tags (OriginalFileName and FirmwareVersion) can be found in the // first requested bytes of the file. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -316,10 +311,7 @@ class DngTypeChecker : public TypeChecker { // 2. at least two dng specific tags in the first requested bytes of the // file virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -370,10 +362,7 @@ class KdcTypeChecker : public TypeChecker { // 1. valid endianness at the beginning of the file; // 2. two tags (WhiteBalance and SerialNumber) in the first requested bytes. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -410,10 +399,7 @@ class MosTypeChecker : public TypeChecker { // 2. signature "PKTS " in the first requested bytes. Note the // "whitespace". It's important as they are special binary values. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(source, &use_big_endian)) { @@ -498,10 +484,7 @@ class NefTypeChecker : public TypeChecker { // special images that the signature locates in the middle of the file, and it // costs too long time to check; virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -537,10 +520,7 @@ class NrwTypeChecker : public TypeChecker { // 4. the ReferenceBlackWhite tag in the requested bytes of the file; // 5. contains the NRW signature; virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -573,10 +553,7 @@ class OrfTypeChecker : public TypeChecker { // 2. tag at the (offset == 2) position of the file; // 3. signature "OLYMP" in the first requested bytes. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -611,10 +588,7 @@ class PefTypeChecker : public TypeChecker { // 2. magic numbers at the (offset == 2 and offset==4) positions of the file; // 3. signature "AOC " or "PENTAX " in first requested bytes. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(limited_source, &use_big_endian)) { @@ -649,10 +623,7 @@ class QtkTypeChecker : public TypeChecker { // Check only the signature at the beginning of the file. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); const size_t kSignatureSize = 2; const string kSignature[kSignatureSize] = { @@ -672,10 +643,7 @@ class RafTypeChecker : public TypeChecker { // Check only the signature at the beginning of the file. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); const string kSignature("FUJIFILM"); return IsSignatureMatched(limited_source, 0 /* offset */, kSignature); @@ -692,10 +660,7 @@ class RawContaxNTypeChecker : public TypeChecker { // Check only the signature at the (offset == 25) position of the // file. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); const string kSignature("ARECOYK"); return IsSignatureMatched(limited_source, 25, kSignature); @@ -712,10 +677,7 @@ class Rw2TypeChecker : public TypeChecker { // Check two points: 1. valid endianness at the beginning of the // file; 2. tag at the (offset == 2) position of the file. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(source, &use_big_endian)) { @@ -740,10 +702,7 @@ class SrwTypeChecker : public TypeChecker { // 2. magic numbers at the (offset == 2 and offset==4) positions of the file; // 3. the signature "SAMSUNG" in the requested bytes of the file; virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); bool use_big_endian; if (!DetermineEndianness(source, &use_big_endian)) { @@ -776,10 +735,7 @@ class X3fTypeChecker : public TypeChecker { // Check only the signature at the beginning of the file. virtual bool IsMyType(const RangeCheckedBytePtr& source) const { - // Limits the source length to the RequestedSize(), using it guarantees that - // we will not read more than this size from the source. - RangeCheckedBytePtr limited_source = - source.pointerToSubArray(0 /* pos */, RequestedSize()); + RangeCheckedBytePtr limited_source = LimitSource(source); const string kSignature("FOVb", 4); return IsSignatureMatched(limited_source, 0 /* offset */, kSignature); @@ -843,7 +799,34 @@ class TypeCheckerList { return checkers_.back()->RequestedSize(); } + bool IsOfType(const RangeCheckedBytePtr& source, const RawImageTypes type) { + const TypeChecker* type_checker = GetTypeCheckerForType(type); + if (type_checker) { + return type_checker->IsMyType(source); + } else { + return false; + } + } + + size_t RequestedSizeForType(const RawImageTypes type) { + const TypeChecker* type_checker = GetTypeCheckerForType(type); + if (type_checker) { + return type_checker->RequestedSize(); + } else { + return 0; + } + } + private: + const TypeChecker* GetTypeCheckerForType(const RawImageTypes type) { + for (const auto* type_checker : checkers_) { + if (type_checker->Type() == type) { + return type_checker; + } + } + return nullptr; + } + std::vector checkers_; }; @@ -886,6 +869,10 @@ bool IsRaw(const RawImageTypes type) { return false; } +bool IsOfType(const RangeCheckedBytePtr& source, const RawImageTypes type) { + return TypeCheckerList().IsOfType(source, type); +} + RawImageTypes RecognizeRawImageTypeLite(const RangeCheckedBytePtr& source) { return TypeCheckerList().GetType(source); } @@ -894,6 +881,10 @@ size_t GetNumberOfBytesForIsRawLite() { return TypeCheckerList().RequestedSize(); } +size_t GetNumberOfBytesForIsOfType(const RawImageTypes type) { + return TypeCheckerList().RequestedSizeForType(type); +} + bool IsRawLite(const RangeCheckedBytePtr& source) { return IsRaw(RecognizeRawImageTypeLite(source)); } diff --git a/src/image_type_recognition/image_type_recognition_lite.h b/src/image_type_recognition/image_type_recognition_lite.h index 5b9ca64..a7e212d 100644 --- a/src/image_type_recognition/image_type_recognition_lite.h +++ b/src/image_type_recognition/image_type_recognition_lite.h @@ -61,6 +61,10 @@ enum RawImageTypes { // Checks if the given type is a RAW image type. bool IsRaw(const RawImageTypes type); +// Checks if the given source is from given type. +bool IsOfType(const binary_parse::RangeCheckedBytePtr& source, + const RawImageTypes type); + // This function will check the source and return the corresponding image type. // If the source is not a recognizable type, this function will return // kNonRawImage. @@ -71,6 +75,10 @@ RawImageTypes RecognizeRawImageTypeLite( // IsRawLite(). size_t GetNumberOfBytesForIsRawLite(); +// Returns the maximum number of bytes needed to recognize a RAF image type in +// IsOfType(). +size_t GetNumberOfBytesForIsOfType(const RawImageTypes type); + // This function will check if the source belongs to one of the known RAW types. bool IsRawLite(const binary_parse::RangeCheckedBytePtr& source); diff --git a/src/piex.cc b/src/piex.cc index 244a44b..fd381db 100644 --- a/src/piex.cc +++ b/src/piex.cc @@ -34,44 +34,85 @@ using image_type_recognition::RecognizeRawImageTypeLite; using tiff_directory::Endian; using tiff_directory::TiffDirectory; -Error GetPreviewData(const TagSet& extended_tags, - const std::uint32_t tiff_offset, - const std::uint32_t number_of_ifds, - StreamInterface* stream, TiffContent* tiff_content, - PreviewImageData* preview_image_data) { - TagSet desired_tags = {kExifTagColorSpace, kExifTagDateTimeOriginal, - kExifTagExposureTime, kExifTagFnumber, - kExifTagFocalLength, kExifTagGps, - kExifTagIsoSpeed, kTiffTagDateTime, - kTiffTagExifIfd, kTiffTagCfaPatternDim, - kTiffTagMake, kTiffTagModel, - kTiffTagOrientation}; +const std::uint32_t kRafOffsetToPreviewOffset = 84; + +bool GetDngInformation(const tiff_directory::TiffDirectory& tiff_directory, + std::uint32_t* width, std::uint32_t* height, + std::vector* cfa_pattern_dim) { + if (!GetFullDimension32(tiff_directory, width, height) || *width == 0 || + *height == 0) { + return false; + } + + if (!tiff_directory.Get(kTiffTagCfaPatternDim, cfa_pattern_dim) || + cfa_pattern_dim->size() != 2) { + return false; + } + return true; +} + +bool GetDngInformation(const TagSet& extended_tags, StreamInterface* data, + std::uint32_t* width, std::uint32_t* height, + std::vector* cfa_pattern_dim) { + TagSet desired_tags = {kExifTagDefaultCropSize, kTiffTagCfaPatternDim, + kTiffTagExifIfd, kTiffTagSubFileType}; + desired_tags.insert(extended_tags.cbegin(), extended_tags.cend()); + + TiffParser tiff_parser(data, 0 /* offset */); + + TiffContent tiff_content; + if (!tiff_parser.Parse(desired_tags, 1, &tiff_content) || + tiff_content.tiff_directory.empty()) { + return false; + } + + // If IFD0 contains already the full dimensions we do not parse into the sub + // IFD. + const TiffDirectory& tiff_directory = tiff_content.tiff_directory[0]; + if (tiff_directory.GetSubDirectories().empty()) { + return GetDngInformation(tiff_directory, width, height, cfa_pattern_dim); + } else { + return GetDngInformation(tiff_directory.GetSubDirectories()[0], width, + height, cfa_pattern_dim); + } +} + +bool GetPreviewData(const TagSet& extended_tags, + const std::uint32_t tiff_offset, + const std::uint32_t number_of_ifds, StreamInterface* stream, + TiffContent* tiff_content, + PreviewImageData* preview_image_data) { + TagSet desired_tags = { + kExifTagColorSpace, kExifTagDateTimeOriginal, kExifTagExposureTime, + kExifTagFnumber, kExifTagFocalLength, kExifTagGps, + kExifTagIsoSpeed, kTiffTagCompression, kTiffTagDateTime, + kTiffTagExifIfd, kTiffTagCfaPatternDim, kTiffTagMake, + kTiffTagModel, kTiffTagOrientation, kTiffTagPhotometric}; desired_tags.insert(extended_tags.cbegin(), extended_tags.cend()); TiffParser tiff_parser(stream, tiff_offset); - Error error = tiff_parser.Parse(desired_tags, number_of_ifds, tiff_content); - if (error != kOk) { - return error; + + if (!tiff_parser.Parse(desired_tags, number_of_ifds, tiff_content)) { + return false; } if (tiff_content->tiff_directory.empty()) { - // Returns kFail if the stream does not contain any TIFF structure. - return kFail; + // Returns false if the stream does not contain any TIFF structure. + return false; } return tiff_parser.GetPreviewImageData(*tiff_content, preview_image_data); } -Error GetPreviewData(const TagSet& extended_tags, - const std::uint32_t number_of_ifds, - StreamInterface* stream, - PreviewImageData* preview_image_data) { +bool GetPreviewData(const TagSet& extended_tags, + const std::uint32_t number_of_ifds, StreamInterface* stream, + PreviewImageData* preview_image_data) { const std::uint32_t kTiffOffset = 0; TiffContent tiff_content; return GetPreviewData(extended_tags, kTiffOffset, number_of_ifds, stream, &tiff_content, preview_image_data); } -Error GetExifData(const std::uint32_t exif_offset, StreamInterface* stream, - PreviewImageData* preview_image_data) { +bool GetExifData(const std::uint32_t exif_offset, StreamInterface* stream, + PreviewImageData* preview_image_data) { const TagSet kExtendedTags = {kTiffTagJpegByteCount, kTiffTagJpegOffset}; const std::uint32_t kNumberOfIfds = 2; TiffContent tiff_content; @@ -88,80 +129,45 @@ void GetThumbnailOffsetAndLength(const TagSet& extended_tags, const std::uint32_t kNumberOfIfds = 2; PreviewImageData thumbnail_data; - if (GetPreviewData(desired_tags, kNumberOfIfds, stream, &thumbnail_data) == - kOk) { - preview_image_data->thumbnail = thumbnail_data.preview; - // TODO: remove the old vars. - preview_image_data->thumbnail_offset = thumbnail_data.preview_offset; - preview_image_data->thumbnail_length = thumbnail_data.preview_length; + if (GetPreviewData(desired_tags, kNumberOfIfds, stream, &thumbnail_data)) { + preview_image_data->thumbnail = thumbnail_data.thumbnail; } } -Error GetExifIfd(const Endian endian, StreamInterface* stream, - TiffDirectory* exif_ifd) { +bool GetExifIfd(const Endian endian, StreamInterface* stream, + TiffDirectory* exif_ifd) { const std::uint32_t kTiffOffset = 0; std::uint32_t offset_to_ifd; if (!Get32u(stream, sizeof(offset_to_ifd), endian, &offset_to_ifd)) { - return kFail; + return false; } std::uint32_t next_ifd_offset; TiffDirectory tiff_ifd(endian); - Error error = - ParseDirectory(kTiffOffset, offset_to_ifd, endian, {kTiffTagExifIfd}, - stream, &tiff_ifd, &next_ifd_offset); - if (error != kOk) { - return error; + if (!ParseDirectory(kTiffOffset, offset_to_ifd, endian, {kTiffTagExifIfd}, + stream, &tiff_ifd, &next_ifd_offset)) { + return false; } std::uint32_t exif_offset; - if (!tiff_ifd.Get(kTiffTagExifIfd, &exif_offset)) { - return kUnsupported; + if (tiff_ifd.Get(kTiffTagExifIfd, &exif_offset)) { + return ParseDirectory(kTiffOffset, exif_offset, endian, + {kExifTagMakernotes}, stream, exif_ifd, + &next_ifd_offset); } - return ParseDirectory(kTiffOffset, exif_offset, endian, {kExifTagMakernotes}, - stream, exif_ifd, &next_ifd_offset); -} - -bool IsThumbnail(const Image& image) { - // According to Tiff/EP a thumbnail has max 256 pixels per dimension. - // http://standardsproposals.bsigroup.com/Home/getPDF/567 - const std::uint16_t kThumbnailAxis = 256; - return image.width <= kThumbnailAxis && image.height <= kThumbnailAxis; -} - -bool GetImageFromIfd(const TiffDirectory& ifd, StreamInterface* stream, - Image* image) { - std::uint32_t compression; - std::uint32_t photometric_interpretation; - if (ifd.Get(kTiffTagPhotometric, &photometric_interpretation) && - ifd.Get(kTiffTagCompression, &compression)) { - if (photometric_interpretation == 6 /* YCbCr */ && - (compression == 6 /* JPEG(old) */ || compression == 7 /* JPEG */)) { - std::vector strip_offsets; - std::vector byte_counts; - if (ifd.Get(kTiffTagStripOffsets, &strip_offsets) && - ifd.Get(kTiffTagStripByteCounts, &byte_counts) && - strip_offsets.size() == 1 && byte_counts.size() == 1) { - image->length = byte_counts[0]; - image->offset = strip_offsets[0]; - return GetPreviewDimensions(image->offset, stream, &image->width, - &image->height); - } - } - } - return false; + return true; } -Error GetMakernoteIfd(const TiffDirectory& exif_ifd, const Endian endian, - const std::uint32_t skip_offset, StreamInterface* stream, - std::uint32_t* makernote_offset, - TiffDirectory* makernote_ifd) { +bool GetMakernoteIfd(const TiffDirectory& exif_ifd, const Endian endian, + const std::uint32_t skip_offset, StreamInterface* stream, + std::uint32_t* makernote_offset, + TiffDirectory* makernote_ifd) { std::uint32_t makernote_length; if (!exif_ifd.GetOffsetAndLength(kExifTagMakernotes, tiff_directory::TIFF_TYPE_UNDEFINED, makernote_offset, &makernote_length)) { - return kUnsupported; + return false; } std::uint32_t next_ifd_offset; @@ -171,22 +177,22 @@ Error GetMakernoteIfd(const TiffDirectory& exif_ifd, const Endian endian, stream, makernote_ifd, &next_ifd_offset); } -Error GetCameraSettingsIfd(const TiffDirectory& makernote_ifd, - const std::uint32_t makernote_offset, - const Endian endian, StreamInterface* stream, - TiffDirectory* camera_settings_ifd) { +bool GetCameraSettingsIfd(const TiffDirectory& makernote_ifd, + const std::uint32_t makernote_offset, + const Endian endian, StreamInterface* stream, + TiffDirectory* camera_settings_ifd) { std::uint32_t camera_settings_offset; std::uint32_t camera_settings_length; if (!makernote_ifd.GetOffsetAndLength( kOlymTagCameraSettings, tiff_directory::TIFF_IFD, &camera_settings_offset, &camera_settings_length)) { - return kUnsupported; + return false; } std::uint32_t next_ifd_offset; if (!Get32u(stream, camera_settings_offset, endian, &camera_settings_offset)) { - return kFail; + return false; } return ParseDirectory(makernote_offset, makernote_offset + camera_settings_offset, endian, @@ -194,22 +200,22 @@ Error GetCameraSettingsIfd(const TiffDirectory& makernote_ifd, camera_settings_ifd, &next_ifd_offset); } -Error GetRawProcessingIfd(const TagSet& desired_tags, - const TiffDirectory& makernote_ifd, - const std::uint32_t makernote_offset, - const Endian endian, StreamInterface* stream, - TiffDirectory* raw_processing_ifd) { +bool GetRawProcessingIfd(const TagSet& desired_tags, + const TiffDirectory& makernote_ifd, + const std::uint32_t makernote_offset, + const Endian endian, StreamInterface* stream, + TiffDirectory* raw_processing_ifd) { std::uint32_t raw_processing_offset; std::uint32_t raw_processing_length; if (!makernote_ifd.GetOffsetAndLength( kOlymTagRawProcessing, tiff_directory::TIFF_IFD, &raw_processing_offset, &raw_processing_length)) { - return kUnsupported; + return false; } std::uint32_t next_ifd_offset; if (!Get32u(stream, raw_processing_offset, endian, &raw_processing_offset)) { - return kFail; + return false; } return ParseDirectory( @@ -219,28 +225,25 @@ Error GetRawProcessingIfd(const TagSet& desired_tags, // Retrieves the preview image offset and length from the camera settings and // the 'full_width' and 'full_height' from the raw processing ifd in 'stream'. -// Returns kUnsupported if the camera settings are missing, since it is not able -// to get the preview data. -Error GetOlympusPreviewImage(StreamInterface* stream, - PreviewImageData* preview_image_data) { +// Returns false if anything is wrong. +bool GetOlympusPreviewImage(StreamInterface* stream, + PreviewImageData* preview_image_data) { Endian endian; if (!GetEndianness(0 /* tiff offset */, stream, &endian)) { - return kFail; + return false; } TiffDirectory exif_ifd(endian); - Error error = GetExifIfd(endian, stream, &exif_ifd); - if (error != kOk) { - return error; + if (!GetExifIfd(endian, stream, &exif_ifd)) { + return false; } std::uint32_t makernote_offset; TiffDirectory makernote_ifd(endian); const std::uint32_t kSkipMakernoteStart = 12; - error = GetMakernoteIfd(exif_ifd, endian, kSkipMakernoteStart, stream, - &makernote_offset, &makernote_ifd); - if (error != kOk) { - return error; + if (!GetMakernoteIfd(exif_ifd, endian, kSkipMakernoteStart, stream, + &makernote_offset, &makernote_ifd)) { + return false; } const std::uint32_t kThumbnailTag = 0x0100; @@ -249,41 +252,33 @@ Error GetOlympusPreviewImage(StreamInterface* stream, kThumbnailTag, tiff_directory::TIFF_TYPE_UNDEFINED, &preview_image_data->thumbnail.offset, &preview_image_data->thumbnail.length)) { - return kFail; + return false; } - // TODO: remove the old vars. - preview_image_data->thumbnail_offset = preview_image_data->thumbnail.offset; - preview_image_data->thumbnail_length = preview_image_data->thumbnail.length; } TiffDirectory camera_settings_ifd(endian); - error = GetCameraSettingsIfd(makernote_ifd, makernote_offset, endian, stream, - &camera_settings_ifd); - if (error != kOk) { - return error; + if (!GetCameraSettingsIfd(makernote_ifd, makernote_offset, endian, stream, + &camera_settings_ifd)) { + return false; } const std::uint32_t kPreviewOffset = 0x0101; const std::uint32_t kPreviewLength = 0x0102; if (!camera_settings_ifd.Has(kPreviewOffset) || !camera_settings_ifd.Has(kPreviewLength)) { - return kUnsupported; + return false; } camera_settings_ifd.Get(kPreviewOffset, &preview_image_data->preview.offset); preview_image_data->preview.offset += makernote_offset; camera_settings_ifd.Get(kPreviewLength, &preview_image_data->preview.length); - // TODO: remove the old vars. - preview_image_data->preview_offset = preview_image_data->preview.offset; - preview_image_data->preview_length = preview_image_data->preview.length; // Get the crop size from the raw processing ifd. TiffDirectory raw_processing_ifd(endian); - error = GetRawProcessingIfd({kOlymTagAspectFrame}, makernote_ifd, - makernote_offset, endian, stream, - &raw_processing_ifd); - if (error != kOk) { - return error; + if (!GetRawProcessingIfd({kOlymTagAspectFrame}, makernote_ifd, + makernote_offset, endian, stream, + &raw_processing_ifd)) { + return false; } if (raw_processing_ifd.Has(kOlymTagAspectFrame)) { @@ -300,40 +295,51 @@ Error GetOlympusPreviewImage(StreamInterface* stream, } } - return kOk; + return true; } -Error PefGetColorSpace(StreamInterface* stream, - PreviewImageData* preview_image_data) { +bool PefGetColorSpace(StreamInterface* stream, + PreviewImageData* preview_image_data) { Endian endian; if (!GetEndianness(0 /* tiff offset */, stream, &endian)) { - return kFail; + return false; } TiffDirectory exif_ifd(endian); - Error error = GetExifIfd(endian, stream, &exif_ifd); - if (error != kOk) { - return error; + if (!GetExifIfd(endian, stream, &exif_ifd)) { + return false; } std::uint32_t makernote_offset; TiffDirectory makernote_ifd(endian); const std::uint32_t kSkipMakernoteStart = 6; - error = GetMakernoteIfd(exif_ifd, endian, kSkipMakernoteStart, stream, - &makernote_offset, &makernote_ifd); - if (error != kOk) { - return error; + if (!GetMakernoteIfd(exif_ifd, endian, kSkipMakernoteStart, stream, + &makernote_offset, &makernote_ifd)) { + return false; } if (makernote_ifd.Has(kPentaxTagColorSpace)) { std::uint32_t color_space; if (!makernote_ifd.Get(kPentaxTagColorSpace, &color_space)) { - return kFail; + return false; } preview_image_data->color_space = color_space == 0 ? PreviewImageData::kSrgb : PreviewImageData::kAdobeRgb; } - return kOk; + return true; +} + +bool RafGetOrientation(StreamInterface* stream, std::uint32_t* orientation) { + // Parse the Fuji RAW header to get the offset and length of the preview + // image, which contains the Exif information. + const Endian endian = tiff_directory::kBigEndian; + std::uint32_t preview_offset = 0; + if (!Get32u(stream, kRafOffsetToPreviewOffset, endian, &preview_offset)) { + return false; + } + + const std::uint32_t exif_offset = preview_offset + 12; + return GetExifOrientation(stream, exif_offset, orientation); } // Parses the Fuji Cfa header for the image width and height. @@ -381,8 +387,11 @@ Error ArwGetPreviewData(StreamInterface* stream, GetThumbnailOffsetAndLength(TagSet(), stream, preview_image_data); const std::uint32_t kNumberOfIfds = 1; - return GetPreviewData(extended_tags, kNumberOfIfds, stream, - preview_image_data); + if (GetPreviewData(extended_tags, kNumberOfIfds, stream, + preview_image_data)) { + return kOk; + } + return kFail; } Error Cr2GetPreviewData(StreamInterface* stream, @@ -393,22 +402,27 @@ Error Cr2GetPreviewData(StreamInterface* stream, GetThumbnailOffsetAndLength(TagSet(), stream, preview_image_data); const std::uint32_t kNumberOfIfds = 1; - return GetPreviewData(extended_tags, kNumberOfIfds, stream, - preview_image_data); + if (GetPreviewData(extended_tags, kNumberOfIfds, stream, + preview_image_data)) { + return kOk; + } + return kFail; } Error DngGetPreviewData(StreamInterface* stream, PreviewImageData* preview_image_data) { + // Some thumbnails from DngCreator are larger than the specified 256 pixel. + const int kDngThumbnailMaxDimension = 512; + const TagSet extended_tags = { - kExifTagDefaultCropSize, kTiffTagCompression, kTiffTagPhotometric, + kExifTagDefaultCropSize, kTiffTagImageWidth, kTiffTagImageLength, kTiffTagStripByteCounts, kTiffTagStripOffsets, kTiffTagSubIfd}; TiffContent tiff_content; const std::uint32_t kNumberOfIfds = 4; - Error error = GetPreviewData(extended_tags, 0, kNumberOfIfds, stream, - &tiff_content, preview_image_data); - if (error != kOk) { - return error; + if (!GetPreviewData(extended_tags, 0, kNumberOfIfds, stream, &tiff_content, + preview_image_data)) { + return kFail; } // Find the jpeg compressed thumbnail and preview image. @@ -417,24 +431,25 @@ Error DngGetPreviewData(StreamInterface* stream, // Search for images in IFD0 Image temp_image; - if (GetImageFromIfd(tiff_content.tiff_directory[0], stream, &temp_image)) { - if (IsThumbnail(temp_image)) { + if (GetImageData(tiff_content.tiff_directory[0], stream, &temp_image)) { + if (IsThumbnail(temp_image, kDngThumbnailMaxDimension)) { thumbnail = temp_image; - } else { + } else if (temp_image.format == Image::kJpegCompressed) { preview = temp_image; } } // Search for images in other IFDs for (const auto& ifd : tiff_content.tiff_directory[0].GetSubDirectories()) { - if (GetImageFromIfd(ifd, stream, &temp_image)) { + if (GetImageData(ifd, stream, &temp_image)) { // Try to find the largest thumbnail/preview. - if (IsThumbnail(temp_image)) { + if (IsThumbnail(temp_image, kDngThumbnailMaxDimension)) { if (temp_image > thumbnail) { thumbnail = temp_image; } } else { - if (temp_image > preview) { + if (temp_image > preview && + temp_image.format == Image::kJpegCompressed) { preview = temp_image; } } @@ -442,33 +457,27 @@ Error DngGetPreviewData(StreamInterface* stream, } preview_image_data->preview = preview; preview_image_data->thumbnail = thumbnail; - // TODO: remove the old vars. - preview_image_data->preview_length = preview.length; - preview_image_data->preview_offset = preview.offset; - preview_image_data->thumbnail_length = thumbnail.length; - preview_image_data->thumbnail_offset = thumbnail.offset; return kOk; } Error NefGetPreviewData(StreamInterface* stream, PreviewImageData* preview_image_data) { - const TagSet extended_tags = {kTiffTagImageWidth, kTiffTagImageLength, - kTiffTagJpegByteCount, kTiffTagJpegOffset, + const TagSet extended_tags = {kTiffTagImageWidth, kTiffTagImageLength, + kTiffTagJpegByteCount, kTiffTagJpegOffset, + kTiffTagStripByteCounts, kTiffTagStripOffsets, kTiffTagSubIfd}; const std::uint32_t kNumberOfIfds = 2; - Error error = - GetPreviewData(extended_tags, kNumberOfIfds, stream, preview_image_data); - if (error != kOk) { - return error; + if (!GetPreviewData(extended_tags, kNumberOfIfds, stream, + preview_image_data)) { + return kFail; } - PreviewImageData thumbnail_data; - GetThumbnailOffsetAndLength(TagSet(), stream, &thumbnail_data); - preview_image_data->thumbnail = thumbnail_data.thumbnail; - // TODO: remove the old vars. - preview_image_data->thumbnail_offset = thumbnail_data.thumbnail_offset; - preview_image_data->thumbnail_length = thumbnail_data.thumbnail_length; + if (preview_image_data->thumbnail.length == 0) { + PreviewImageData thumbnail_data; + GetThumbnailOffsetAndLength(TagSet(), stream, &thumbnail_data); + preview_image_data->thumbnail = thumbnail_data.thumbnail; + } // The Nikon RAW data provides the dimensions of the sensor image, which are // slightly larger than the dimensions of the preview image. In order to @@ -476,13 +485,13 @@ Error NefGetPreviewData(StreamInterface* stream, // size needs to be taken into account. Based on experiments the preview image // dimensions must be at least 90% of the sensor image dimensions to let it be // a full size preview image. - if (preview_image_data->preview_length > 0) { // when preview image exists + if (preview_image_data->preview.length > 0) { // when preview image exists const float kEpsilon = 0.9f; std::uint16_t width; std::uint16_t height; - if (!GetPreviewDimensions(preview_image_data->preview_offset, stream, - &width, &height) || + if (!GetJpegDimensions(preview_image_data->preview.offset, stream, &width, + &height) || preview_image_data->full_width == 0 || preview_image_data->full_height == 0) { return kUnsupported; @@ -503,13 +512,12 @@ Error NefGetPreviewData(StreamInterface* stream, Error OrfGetPreviewData(StreamInterface* stream, PreviewImageData* preview_image_data) { - // Omit kUnsupported, because the exif data does not contain any preview - // image. - if (GetExifData(0, stream, preview_image_data) == kFail) { + if (!GetExifData(0, stream, preview_image_data)) { return kFail; } - - return GetOlympusPreviewImage(stream, preview_image_data); + // Omit errors, because some images do not contain any preview data. + GetOlympusPreviewImage(stream, preview_image_data); + return kOk; } Error PefGetPreviewData(StreamInterface* stream, @@ -518,23 +526,15 @@ Error PefGetPreviewData(StreamInterface* stream, kTiffTagJpegByteCount, kTiffTagJpegOffset, kTiffTagSubIfd}; const std::uint32_t kNumberOfIfds = 3; - Error error = - GetPreviewData(extended_tags, kNumberOfIfds, stream, preview_image_data); - if (error != kOk) { - return error; - } - - error = PefGetColorSpace(stream, preview_image_data); - if (error != kOk) { - return error; + if (!GetPreviewData(extended_tags, kNumberOfIfds, stream, + preview_image_data) || + !PefGetColorSpace(stream, preview_image_data)) { + return kFail; } PreviewImageData thumbnail_data; GetThumbnailOffsetAndLength(TagSet(), stream, &thumbnail_data); preview_image_data->thumbnail = thumbnail_data.thumbnail; - // TODO: remove the old vars. - preview_image_data->thumbnail_offset = thumbnail_data.thumbnail_offset; - preview_image_data->thumbnail_length = thumbnail_data.thumbnail_length; return kOk; } @@ -546,8 +546,8 @@ Error RafGetPreviewData(StreamInterface* stream, const Endian endian = tiff_directory::kBigEndian; std::uint32_t preview_offset = 0; std::uint32_t preview_length = 0; - if (!Get32u(stream, 84 /* preview offset */, endian, &preview_offset) || - !Get32u(stream, 88 /* preview length */, endian, &preview_length)) { + if (!Get32u(stream, kRafOffsetToPreviewOffset, endian, &preview_offset) || + !Get32u(stream, kRafOffsetToPreviewOffset + 4, endian, &preview_length)) { return kFail; } @@ -557,26 +557,17 @@ Error RafGetPreviewData(StreamInterface* stream, } if (preview_length > 0) { // when preview image exists - // Parse the Exif information from the preview image. Omit kUnsupported, - // because the exif data does not contain any preview image. + // Parse the Exif information from the preview image. const std::uint32_t exif_offset = preview_offset + 12; - if (GetExifData(exif_offset, stream, preview_image_data) == kFail) { + if (!GetExifData(exif_offset, stream, preview_image_data)) { return kFail; } } // Merge the Exif data with the RAW data to form the preview_image_data. - // The preview offset and length extracted from the Exif data are actually - // the thumbnail offset and length. - preview_image_data->thumbnail = preview_image_data->preview; preview_image_data->thumbnail.offset += 160; // Skip the cfa header. preview_image_data->preview.offset = preview_offset; preview_image_data->preview.length = preview_length; - // TODO: remove the old vars. - preview_image_data->thumbnail_offset = preview_image_data->thumbnail.offset; - preview_image_data->thumbnail_length = preview_image_data->thumbnail.length; - preview_image_data->preview_offset = preview_image_data->preview.offset; - preview_image_data->preview_length = preview_image_data->preview.length; return kOk; } @@ -590,26 +581,17 @@ Error Rw2GetPreviewData(StreamInterface* stream, // which contains the Exif information. const std::uint32_t kNumberOfIfds = 1; PreviewImageData preview_data; - Error error = - GetPreviewData(extended_tags, kNumberOfIfds, stream, &preview_data); - if (error != kOk) { - return error; + if (!GetPreviewData(extended_tags, kNumberOfIfds, stream, &preview_data)) { + return kFail; } - if (preview_data.preview_length > 0) { // when preview image exists - // Parse the Exif information from the preview image. Omit kUnsupported, - // because the exif data does not contain any preview image. - const std::uint32_t exif_offset = preview_data.preview_offset + 12; - if (GetExifData(exif_offset, stream, preview_image_data) == kFail) { + if (preview_data.preview.length > 0) { // when preview image exists + // Parse the Exif information from the preview image. + const std::uint32_t exif_offset = preview_data.preview.offset + 12; + if (!GetExifData(exif_offset, stream, preview_image_data)) { return kFail; } - // The preview offset and length extracted from the Exif data are actually - // the thumbnail offset and length. - preview_image_data->thumbnail = preview_image_data->preview; preview_image_data->thumbnail.offset += exif_offset; - // TODO: remove old vars. - preview_image_data->thumbnail_offset = preview_image_data->thumbnail.offset; - preview_image_data->thumbnail_length = preview_image_data->thumbnail.length; } // Merge the Exif data with the RAW data to form the preview_image_data. @@ -617,9 +599,6 @@ Error Rw2GetPreviewData(StreamInterface* stream, preview_image_data->iso = preview_data.iso; preview_image_data->full_width = preview_data.full_width; preview_image_data->full_height = preview_data.full_height; - // TODO: remove old vars. - preview_image_data->preview_offset = preview_image_data->preview.offset; - preview_image_data->preview_length = preview_image_data->preview.length; return kOk; } @@ -632,8 +611,11 @@ Error SrwGetPreviewData(StreamInterface* stream, kTiffTagJpegByteCount, kTiffTagJpegOffset, kTiffTagSubIfd}; const std::uint32_t kNumberOfIfds = 1; - return GetPreviewData(extended_tags, kNumberOfIfds, stream, - preview_image_data); + if (!GetPreviewData(extended_tags, kNumberOfIfds, stream, + preview_image_data)) { + return kFail; + } + return kOk; } } // namespace @@ -698,6 +680,38 @@ Error GetPreviewImageData(StreamInterface* data, } } +bool GetDngInformation(StreamInterface* data, std::uint32_t* width, + std::uint32_t* height, + std::vector* cfa_pattern_dim) { + // If IFD0 contains already the full dimensions we do not parse into the sub + // IFD. + if (!GetDngInformation({}, data, width, height, cfa_pattern_dim)) { + return GetDngInformation({kTiffTagSubIfd}, data, width, height, + cfa_pattern_dim); + } + return true; +} + +bool GetOrientation(StreamInterface* data, std::uint32_t* orientation) { + using image_type_recognition::GetNumberOfBytesForIsOfType; + using image_type_recognition::IsOfType; + + std::vector file_header( + GetNumberOfBytesForIsOfType(image_type_recognition::kRafImage)); + if (data->GetData(0, file_header.size(), file_header.data()) != kOk) { + return false; + } + + // For RAF files a special routine is necessary to get orientation. For others + // the general approach is sufficient. + if (IsOfType(RangeCheckedBytePtr(file_header.data(), file_header.size()), + image_type_recognition::kRafImage)) { + return RafGetOrientation(data, orientation); + } else { + return GetExifOrientation(data, 0 /* offset */, orientation); + } +} + std::vector SupportedExtensions() { return {"ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "PEF", "RAF", "RW2", "SRW"}; } diff --git a/src/piex.h b/src/piex.h index 13c190f..3225421 100644 --- a/src/piex.h +++ b/src/piex.h @@ -16,7 +16,6 @@ // // The purpose of the preview-image-extractor (piex) is to find and extract the // largest JPEG compressed preview image contained in a RAW file. -// For details: go/piex // // Even for unsupported RAW files we want to provide high quality images using a // dedicated, small and portable library. That is possible by taking the preview @@ -74,6 +73,16 @@ bool IsRaw(StreamInterface* data); Error GetPreviewImageData(StreamInterface* data, PreviewImageData* preview_image_data); +// Returns true if the full width and height and the mosaic pattern dimension of +// a DNG image could be obtained. False otherwise. +bool GetDngInformation(StreamInterface* data, std::uint32_t* width, + std::uint32_t* height, + std::vector* cfa_pattern_dim); + +// Returns true if Exif orientation for the image can be obtained. False +// otherwise. +bool GetOrientation(StreamInterface* data, std::uint32_t* orientation); + // Returns a vector of upper case file extensions, which are used as a first // step to quickly guess a supported file format. std::vector SupportedExtensions(); diff --git a/src/piex_types.h b/src/piex_types.h index b4824fc..4fdb7c2 100644 --- a/src/piex_types.h +++ b/src/piex_types.h @@ -36,6 +36,7 @@ enum Error { struct Image { enum Format { kJpegCompressed, + kUncompressedRgb, }; std::uint16_t width = 0; @@ -80,12 +81,6 @@ struct PreviewImageData { // correctly. A thumbnail is typically 160x120 pixel small and usually // has black borders at the top and bottom. If length is 0 the image could not // be extracted. - // Note: Deprecate the offset, length versions. Use these Image structs - // instead. - std::uint32_t preview_offset = 0; - std::uint32_t preview_length = 0; - std::uint32_t thumbnail_offset = 0; - std::uint32_t thumbnail_length = 0; Image preview; Image thumbnail; diff --git a/src/tiff_parser.cc b/src/tiff_parser.cc index 618ac80..697e320 100644 --- a/src/tiff_parser.cc +++ b/src/tiff_parser.cc @@ -17,6 +17,8 @@ #include "src/tiff_parser.h" #include +#include +#include #include "src/tiff_directory/tiff_directory.h" @@ -38,54 +40,17 @@ 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 crop(2); - std::vector 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; - } +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::max() || + tmp_height > std::numeric_limits::max()) { + return false; } + *width = static_cast(tmp_width); + *height = static_cast(tmp_height); return true; } @@ -160,45 +125,34 @@ void FillGpsPreviewImageData(const TiffDirectory& gps_directory, } } -Error FillPreviewImageData(const TiffDirectory& tiff_directory, - PreviewImageData* preview_image_data) { - bool success = true; - // Get preview_offset and preview_length - if (tiff_directory.Has(kTiffTagStripOffsets) && - tiff_directory.Has(kTiffTagStripByteCounts)) { - std::vector strip_offsets; - std::vector 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]; - // TODO: remove old vars. - 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); - // TODO: remove old vars. - preview_image_data->preview_offset = preview_image_data->preview.offset; - preview_image_data->preview_length = preview_image_data->preview.length; +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; } + } +} - } 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; +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; } - // TODO: remove old vars. - preview_image_data->preview_offset = preview_image_data->preview.offset; - preview_image_data->preview_length = preview_image_data->preview.length; } // Get exif_orientation if it was not set already. @@ -219,8 +173,8 @@ Error FillPreviewImageData(const TiffDirectory& tiff_directory, } } - success &= GetFullDimension(tiff_directory, &preview_image_data->full_width, - &preview_image_data->full_height); + 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); @@ -265,11 +219,7 @@ Error FillPreviewImageData(const TiffDirectory& tiff_directory, &preview_image_data->focal_length); } - if (!success) { - return kFail; - } - - return kOk; + return success; } const TiffDirectory* FindFirstTagInIfds(const TiffDirectory::Tag& tag, @@ -289,12 +239,28 @@ const TiffDirectory* FindFirstTagInIfds(const TiffDirectory::Tag& tag, return NULL; } +// Return true if all data blocks are ordered one after the other without gaps. +bool OffsetsAreConsecutive( + const std::vector& strip_offsets, + const std::vector& 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. -void ParseSubIfds(const std::uint32_t tiff_offset, const TagSet& desired_tags, +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, - Error* error) { - if (*error == kOk && tiff_ifd->Has(kTiffTagSubIfd)) { + 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, @@ -303,21 +269,20 @@ void ParseSubIfds(const std::uint32_t tiff_offset, const TagSet& desired_tags, 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; + return false; } std::uint32_t next_ifd_offset; TiffDirectory sub_ifd(static_cast(endian)); - *error = ParseDirectory(tiff_offset, sub_offset, endian, desired_tags, - stream, &sub_ifd, &next_ifd_offset); - if (*error != kOk) { - return; + 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 @@ -397,9 +362,72 @@ bool GetEndianness(const std::uint32_t tiff_offset, StreamInterface* stream, } } -bool GetPreviewDimensions(const std::uint32_t jpeg_offset, - StreamInterface* stream, std::uint16_t* width, - std::uint16_t* height) { +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 strip_offsets; + std::vector 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::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; @@ -432,14 +460,18 @@ bool GetPreviewDimensions(const std::uint32_t jpeg_offset, 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) { +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 kFail; + return false; } for (std::uint32_t i = 0; @@ -455,14 +487,14 @@ Error ParseDirectory(const std::uint32_t tiff_offset, continue; } } else { - return kFail; + 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 kFail; + return false; } const size_t byte_count = type_size * static_cast(number_of_elements); @@ -482,17 +514,93 @@ Error ParseDirectory(const std::uint32_t tiff_offset, const std::vector data = GetData(value_offset, byte_count, stream, &error); if (error != kOk) { - return error; + return false; } tiff_directory->AddEntry(tag, type, number_of_elements, value_offset, data); } - if (Get32u(stream, ifd_offset + 2u + number_of_entries * 12u, endian, - next_ifd_offset)) { - return kOk; - } else { - return kFail; + 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(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 crop(2); + std::vector 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; } TiffParser::TiffParser(StreamInterface* stream) : stream_(stream) {} @@ -500,34 +608,35 @@ 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; +bool TiffParser::GetPreviewImageData(const TiffContent& tiff_content, + PreviewImageData* preview_image_data) { + bool success = true; for (const auto& tiff_directory : tiff_content.tiff_directory) { - error = FillPreviewImageData(tiff_directory, preview_image_data); - if (error == kOk && tiff_directory.Has(kTiffTagExifIfd) && + success = FillPreviewImageData(tiff_directory, stream_, preview_image_data); + if (success && tiff_directory.Has(kTiffTagExifIfd) && tiff_content.exif_directory) { - error = FillPreviewImageData(*tiff_content.exif_directory, - preview_image_data); + success = FillPreviewImageData(*tiff_content.exif_directory, stream_, + preview_image_data); } - if (error == kOk && tiff_directory.Has(kExifTagGps) && + 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 (error == kOk) { - error = FillPreviewImageData(sub_directory, preview_image_data); + if (success) { + success = + FillPreviewImageData(sub_directory, stream_, preview_image_data); } } } - return error; + return success; } -Error TiffParser::Parse(const TagSet& desired_tags, - const std::uint16_t max_number_ifds, - TiffContent* tiff_content) { +bool 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. + return false; // You shall call Parse() only once. } const std::uint32_t kTiffIdentifierSize = 4; @@ -535,13 +644,12 @@ Error TiffParser::Parse(const TagSet& desired_tags, if (!GetEndianness(tiff_offset_, stream_, &endian_) || !Get32u(stream_, tiff_offset_ + kTiffIdentifierSize, endian_, &offset_to_ifd)) { - return kFail; + return false; } - Error error = ParseIfd(tiff_offset_ + offset_to_ifd, desired_tags, - max_number_ifds, &tiff_content->tiff_directory); - if (error != kOk) { - return error; + if (!ParseIfd(tiff_offset_ + offset_to_ifd, desired_tags, max_number_ifds, + &tiff_content->tiff_directory)) { + return false; } // Get the Exif data. @@ -553,11 +661,10 @@ Error TiffParser::Parse(const TagSet& desired_tags, 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 (!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); @@ -572,34 +679,32 @@ Error TiffParser::Parse(const TagSet& desired_tags, return ParseGpsData(tiff_ifd, tiff_content); } - return error; + return true; } -Error TiffParser::ParseIfd(const std::uint32_t offset_to_ifd, - const TagSet& desired_tags, - const std::uint16_t max_number_ifds, - IfdVector* tiff_directory) { +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_)); - 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); - } + 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; } - return error; + 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; } -Error TiffParser::ParseGpsData(const TiffDirectory* tiff_ifd, - TiffContent* tiff_content) { +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_)); @@ -612,7 +717,7 @@ Error TiffParser::ParseGpsData(const TiffDirectory* tiff_ifd, gps_tags, stream_, tiff_content->gps_directory.get(), &next_ifd_offset); } - return kOk; + return true; } } // namespace piex diff --git a/src/tiff_parser.h b/src/tiff_parser.h index 553b652..a19b71e 100644 --- a/src/tiff_parser.h +++ b/src/tiff_parser.h @@ -27,6 +27,9 @@ namespace piex { +// Specifies the maximum number of pixels for thumbnails in each direction. +const int kThumbnailMaxDimension = 256; + // Specifies all tags that might be of interest to get the preview data. enum GpsTags { kGpsTagLatitudeRef = 1, @@ -83,6 +86,7 @@ enum TiffTags { kTiffTagSoftware = 0x0131, kTiffTagStripByteCounts = 0x0117, kTiffTagStripOffsets = 0x0111, + kTiffTagSubFileType = 0x00FE, kTiffTagSubIfd = 0x014A, kTiffTagTileByteCounts = 0x0145, kTiffTagTileLength = 0x0143, @@ -120,25 +124,43 @@ std::vector GetData(const size_t offset, const size_t length, StreamInterface* stream, Error* error); // Retrieves the endianness of TIFF compliant data at 'tiff_offset' from -// 'stream' returning true on success. Returns false if when something is wrong. +// 'stream' returning true on success. Returns false when something is wrong. bool GetEndianness(const std::uint32_t tiff_offset, StreamInterface* stream, tiff_directory::Endian* endian); -// Retrieves the width and height from the jpeg preview returning true on +// Retrieves an image from tiff_directory. Return false when something is wrong. +bool GetImageData(const tiff_directory::TiffDirectory& tiff_directory, + StreamInterface* stream, Image* image); + +// Retrieves the width and height from the jpeg image returning true on // success. Returns false when something is wrong. -bool GetPreviewDimensions(const std::uint32_t jpeg_offset, - StreamInterface* stream, std::uint16_t* width, - std::uint16_t* height); +bool GetJpegDimensions(const std::uint32_t jpeg_offset, StreamInterface* stream, + std::uint16_t* width, std::uint16_t* height); + +// According to Tiff/EP a thumbnail has max 256 pixels per dimension. +// http://standardsproposals.bsigroup.com/Home/getPDF/567 +bool IsThumbnail(const Image& image, + const int max_dimension = kThumbnailMaxDimension); // Parses through a Tiff IFD and writes all 'desired_tags' to a // 'tiff_directory'. -// Sets 'error' to kFail if something with the Tiff data is wrong. -Error ParseDirectory(const std::uint32_t tiff_offset, - const std::uint32_t ifd_offset, - const tiff_directory::Endian endian, - const TagSet& desired_tags, StreamInterface* stream, - tiff_directory::TiffDirectory* tiff_directory, - std::uint32_t* next_ifd_offset); +// Returns false if something with the Tiff data is wrong. +bool ParseDirectory(const std::uint32_t tiff_offset, + const std::uint32_t ifd_offset, + const tiff_directory::Endian endian, + const TagSet& desired_tags, StreamInterface* stream, + tiff_directory::TiffDirectory* tiff_directory, + std::uint32_t* next_ifd_offset); + +// Returns true if Exif orientation for the image can be obtained. False +// otherwise. +bool GetExifOrientation(StreamInterface* stream, const std::uint32_t offset, + std::uint32_t* orientation); + +// Reads the width and height of the full resolution image. The tag groups are +// exclusive. +bool GetFullDimension32(const tiff_directory::TiffDirectory& tiff_directory, + std::uint32_t* width, std::uint32_t* height); // Enables us to parse through data that complies to the Tiff/EP specification. class TiffParser { @@ -149,25 +171,24 @@ class TiffParser { TiffParser(StreamInterface* stream, const std::uint32_t offset); // Runs over the Tiff IFD, Exif IFD and subIFDs to get the preview image data. - // Returns kFail if something with the Tiff tags is wrong. - Error GetPreviewImageData(const TiffContent& tiff_content, - PreviewImageData* image_metadata); + // Returns false if something with the Tiff tags is wrong. + bool GetPreviewImageData(const TiffContent& tiff_content, + PreviewImageData* image_metadata); - // Returns kFail if called more that once or something with the Tiff data is + // Returns false if called more that once or something with the Tiff data is // wrong. - Error Parse(const TagSet& desired_tags, const std::uint16_t max_number_ifds, - TiffContent* tiff_content); + bool Parse(const TagSet& desired_tags, const std::uint16_t max_number_ifds, + TiffContent* tiff_content); private: // Disallow copy and assignment. TiffParser(const TiffParser&) = delete; TiffParser& operator=(const TiffParser&) = delete; - Error ParseIfd(const std::uint32_t ifd_offset, const TagSet& desired_tags, - const std::uint16_t max_number_ifds, - IfdVector* tiff_directory); - Error ParseGpsData(const tiff_directory::TiffDirectory* tiff_ifd, - TiffContent* tiff_content); + bool ParseIfd(const std::uint32_t ifd_offset, const TagSet& desired_tags, + const std::uint16_t max_number_ifds, IfdVector* tiff_directory); + bool ParseGpsData(const tiff_directory::TiffDirectory* tiff_ifd, + TiffContent* tiff_content); StreamInterface* stream_ = nullptr; std::uint32_t tiff_offset_ = 0; -- cgit v1.2.3