diff options
author | Sadaf Ebrahimi <sadafebrahimi@google.com> | 2022-09-24 03:52:09 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-09-24 03:52:09 +0000 |
commit | a7f30384a8bedbe1becc40abff65219cc9c68e7d (patch) | |
tree | b72f2a863d0f19484283327097d58164ccd66e09 /src/piex.cc | |
parent | 035916e09570c220d043dc855d9b155f518a7878 (diff) | |
parent | 98ecd898ea61034d9f88adf50c7d82fd3ade1b2b (diff) | |
download | piex-android14-dev.tar.gz |
Adding METADATA to piex and merging with upstream am: f85cc3c7c1 am: fb64679d7c am: 8eef416f69 am: 0471da4872 am: 98ecd898eaandroid-14.0.0_r51android-14.0.0_r50android-14.0.0_r45android-14.0.0_r44android-14.0.0_r43android-14.0.0_r42android-14.0.0_r41android-14.0.0_r40android-14.0.0_r39android-14.0.0_r38android-14.0.0_r37android-14.0.0_r36android-14.0.0_r35android-14.0.0_r34android-14.0.0_r33android-14.0.0_r32android-14.0.0_r31android-14.0.0_r30android-14.0.0_r29android-14.0.0_r27android-14.0.0_r26android-14.0.0_r25android-14.0.0_r24android-14.0.0_r23android-14.0.0_r22android-14.0.0_r21android-14.0.0_r20android-14.0.0_r19android-14.0.0_r18android-14.0.0_r17android-14.0.0_r16aml_rkp_341510000aml_rkp_341311000aml_rkp_341114000aml_rkp_341015010aml_rkp_341012000aml_hef_341717050aml_hef_341613000aml_hef_341512030aml_hef_341415040aml_hef_341311010aml_hef_341114030aml_cfg_341510000android14-qpr3-releaseandroid14-qpr2-s5-releaseandroid14-qpr2-s4-releaseandroid14-qpr2-s3-releaseandroid14-qpr2-s2-releaseandroid14-qpr2-s1-releaseandroid14-qpr2-releaseandroid14-qpr1-s2-releaseandroid14-qpr1-releaseandroid14-mainline-healthfitness-releaseandroid14-devandroid14-d2-s5-releaseandroid14-d2-s4-releaseandroid14-d2-s3-releaseandroid14-d2-s2-releaseandroid14-d2-s1-releaseandroid14-d2-release
Original change: https://android-review.googlesource.com/c/platform/external/piex/+/2224358
Change-Id: Ie958983dca2e0bf775b2ac1cb2bbe31ce4ebd9d9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Diffstat (limited to 'src/piex.cc')
-rwxr-xr-x | src/piex.cc | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/src/piex.cc b/src/piex.cc new file mode 100755 index 0000000..ac2ef0b --- /dev/null +++ b/src/piex.cc @@ -0,0 +1,740 @@ +// 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/piex.h" + +#include <cstdint> +#include <limits> +#include <set> +#include <vector> + +#include "src/binary_parse/range_checked_byte_ptr.h" +#include "src/image_type_recognition/image_type_recognition_lite.h" +#include "src/piex_cr3.h" +#include "src/tiff_parser.h" + +namespace piex { +namespace { + +using binary_parse::RangeCheckedBytePtr; +using image_type_recognition::RawImageTypes; +using image_type_recognition::RecognizeRawImageTypeLite; +using tiff_directory::Endian; +using tiff_directory::TiffDirectory; + +const std::uint32_t kRafOffsetToPreviewOffset = 84; + +bool GetDngInformation(const tiff_directory::TiffDirectory& tiff_directory, + std::uint32_t* width, std::uint32_t* height, + std::vector<std::uint32_t>* 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<std::uint32_t>* 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); + + if (!tiff_parser.Parse(desired_tags, number_of_ifds, tiff_content)) { + return false; + } + if (tiff_content->tiff_directory.empty()) { + // Returns false if the stream does not contain any TIFF structure. + return false; + } + return tiff_parser.GetPreviewImageData(*tiff_content, 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); +} + +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; + return GetPreviewData(kExtendedTags, exif_offset, kNumberOfIfds, stream, + &tiff_content, preview_image_data); +} + +// Reads the jpeg compressed thumbnail information. +void GetThumbnailOffsetAndLength(const TagSet& extended_tags, + StreamInterface* stream, + PreviewImageData* preview_image_data) { + TagSet desired_tags = {kTiffTagJpegByteCount, kTiffTagJpegOffset}; + desired_tags.insert(extended_tags.cbegin(), extended_tags.cend()); + + const std::uint32_t kNumberOfIfds = 2; + PreviewImageData thumbnail_data; + if (GetPreviewData(desired_tags, kNumberOfIfds, stream, &thumbnail_data)) { + preview_image_data->thumbnail = thumbnail_data.thumbnail; + } +} + +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 false; + } + + std::uint32_t next_ifd_offset; + TiffDirectory tiff_ifd(endian); + 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 ParseDirectory(kTiffOffset, exif_offset, endian, + {kExifTagMakernotes}, stream, exif_ifd, + &next_ifd_offset); + } + + return true; +} + +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 false; + } + + std::uint32_t next_ifd_offset; + return ParseDirectory(*makernote_offset, *makernote_offset + skip_offset, + endian, {kTiffTagImageWidth, kOlymTagCameraSettings, + kOlymTagRawProcessing, kPentaxTagColorSpace}, + stream, makernote_ifd, &next_ifd_offset); +} + +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 false; + } + + std::uint32_t next_ifd_offset; + if (!Get32u(stream, camera_settings_offset, endian, + &camera_settings_offset)) { + return false; + } + return ParseDirectory(makernote_offset, + makernote_offset + camera_settings_offset, endian, + {kTiffTagBitsPerSample, kTiffTagImageLength}, stream, + camera_settings_ifd, &next_ifd_offset); +} + +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 false; + } + + std::uint32_t next_ifd_offset; + if (!Get32u(stream, raw_processing_offset, endian, &raw_processing_offset)) { + return false; + } + + return ParseDirectory( + makernote_offset, makernote_offset + raw_processing_offset, endian, + desired_tags, stream, raw_processing_ifd, &next_ifd_offset); +} + +// 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 false if anything is wrong. +bool GetOlympusPreviewImage(StreamInterface* stream, + PreviewImageData* preview_image_data) { + Endian endian; + if (!GetEndianness(0 /* tiff offset */, stream, &endian)) { + return false; + } + + TiffDirectory exif_ifd(endian); + if (!GetExifIfd(endian, stream, &exif_ifd)) { + return false; + } + + std::uint32_t makernote_offset; + TiffDirectory makernote_ifd(endian); + const std::uint32_t kSkipMakernoteStart = 12; + if (!GetMakernoteIfd(exif_ifd, endian, kSkipMakernoteStart, stream, + &makernote_offset, &makernote_ifd)) { + return false; + } + + const std::uint32_t kThumbnailTag = 0x0100; + if (makernote_ifd.Has(kThumbnailTag)) { + if (!makernote_ifd.GetOffsetAndLength( + kThumbnailTag, tiff_directory::TIFF_TYPE_UNDEFINED, + &preview_image_data->thumbnail.offset, + &preview_image_data->thumbnail.length)) { + return false; + } + } + + TiffDirectory camera_settings_ifd(endian); + 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 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); + + // Get the crop size from the raw processing ifd. + TiffDirectory raw_processing_ifd(endian); + if (!GetRawProcessingIfd({kOlymTagAspectFrame}, makernote_ifd, + makernote_offset, endian, stream, + &raw_processing_ifd)) { + return false; + } + + if (raw_processing_ifd.Has(kOlymTagAspectFrame)) { + std::vector<std::uint32_t> aspect_frame; + if (raw_processing_ifd.Get(kOlymTagAspectFrame, &aspect_frame) && + aspect_frame.size() == 4 && aspect_frame[2] > aspect_frame[0] && + aspect_frame[3] > aspect_frame[1]) { + preview_image_data->full_width = aspect_frame[2] - aspect_frame[0] + 1; + preview_image_data->full_height = aspect_frame[3] - aspect_frame[1] + 1; + if (preview_image_data->full_width < preview_image_data->full_height) { + std::swap(preview_image_data->full_width, + preview_image_data->full_height); + } + } + } + + return true; +} + +bool PefGetColorSpace(StreamInterface* stream, + PreviewImageData* preview_image_data) { + Endian endian; + if (!GetEndianness(0 /* tiff offset */, stream, &endian)) { + return false; + } + + TiffDirectory exif_ifd(endian); + if (!GetExifIfd(endian, stream, &exif_ifd)) { + return false; + } + + std::uint32_t makernote_offset; + TiffDirectory makernote_ifd(endian); + const std::uint32_t kSkipMakernoteStart = 6; + 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 false; + } + preview_image_data->color_space = color_space == 0 + ? PreviewImageData::kSrgb + : PreviewImageData::kAdobeRgb; + } + 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. +bool RafGetDimension(StreamInterface* stream, std::uint32_t* width, + std::uint32_t* height) { + const Endian endian = tiff_directory::kBigEndian; + std::uint32_t cfa_header_index = 0; // actual position in the cfa header. + std::uint32_t cfa_header_entries = 0; + if (!Get32u(stream, 92 /* cfa header offset */, endian, &cfa_header_index) || + !Get32u(stream, cfa_header_index, endian, &cfa_header_entries)) { + return false; + } + + // Add 4 to point to the actual read position in the cfa header. + cfa_header_index += 4; + + for (std::uint32_t i = 0; i < cfa_header_entries; ++i) { + std::uint16_t id = 0; + std::uint16_t length = 0; + if (!Get16u(stream, cfa_header_index, endian, &id) || + !Get16u(stream, cfa_header_index + 2, endian, &length)) { + return false; + } + + std::uint16_t tmp_width = 0; + std::uint16_t tmp_height = 0; + if (id == 0x0111 /* tags the crop dimensions */ && + Get16u(stream, cfa_header_index + 4, endian, &tmp_height) && + Get16u(stream, cfa_header_index + 6, endian, &tmp_width)) { + *width = tmp_width; + *height = tmp_height; + return true; + } + cfa_header_index += 4u + length; + } + return false; +} + +Error ArwGetPreviewData(StreamInterface* stream, + PreviewImageData* preview_image_data) { + const TagSet extended_tags = {kExifTagHeight, kExifTagWidth, + kTiffTagJpegByteCount, kTiffTagJpegOffset, + kTiffTagSubIfd}; + + GetThumbnailOffsetAndLength(TagSet(), stream, preview_image_data); + + const std::uint32_t kNumberOfIfds = 1; + if (GetPreviewData(extended_tags, kNumberOfIfds, stream, + preview_image_data)) { + return kOk; + } + return kFail; +} + +Error Cr2GetPreviewData(StreamInterface* stream, + PreviewImageData* preview_image_data) { + const TagSet extended_tags = {kExifTagHeight, kExifTagWidth, + kTiffTagStripByteCounts, kTiffTagStripOffsets}; + + GetThumbnailOffsetAndLength(TagSet(), stream, preview_image_data); + + const std::uint32_t kNumberOfIfds = 1; + 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, kTiffTagImageWidth, kTiffTagImageLength, + kTiffTagStripByteCounts, kTiffTagStripOffsets, kTiffTagSubIfd}; + + TiffContent tiff_content; + const std::uint32_t kNumberOfIfds = 3; + if (!GetPreviewData(extended_tags, 0, kNumberOfIfds, stream, &tiff_content, + preview_image_data)) { + return kFail; + } + + const TiffDirectory& tiff_directory = tiff_content.tiff_directory[0]; + + if (!GetFullCropDimension(tiff_directory, &preview_image_data->full_width, + &preview_image_data->full_height)) { + return kFail; + } + + // Find the jpeg compressed thumbnail and preview image. + Image preview; + Image thumbnail; + + // Search for images in IFD0 + Image temp_image; + if (GetImageData(tiff_directory, stream, &temp_image)) { + if (IsThumbnail(temp_image, kDngThumbnailMaxDimension)) { + thumbnail = temp_image; + } else if (temp_image.format == Image::kJpegCompressed) { + preview = temp_image; + } + } + + // Search for images in other IFDs + for (const auto& ifd : tiff_directory.GetSubDirectories()) { + if (GetImageData(ifd, stream, &temp_image)) { + // Try to find the largest thumbnail/preview. + if (IsThumbnail(temp_image, kDngThumbnailMaxDimension)) { + if (temp_image > thumbnail) { + thumbnail = temp_image; + } + } else { + if (temp_image > preview && + temp_image.format == Image::kJpegCompressed) { + preview = temp_image; + } + } + } + } + preview_image_data->preview = preview; + preview_image_data->thumbnail = thumbnail; + + return kOk; +} + +Error NefGetPreviewData(StreamInterface* stream, + PreviewImageData* preview_image_data) { + const TagSet extended_tags = {kTiffTagImageWidth, kTiffTagImageLength, + kTiffTagJpegByteCount, kTiffTagJpegOffset, + kTiffTagStripByteCounts, kTiffTagStripOffsets, + kTiffTagSubIfd}; + const std::uint32_t kNumberOfIfds = 2; + if (!GetPreviewData(extended_tags, kNumberOfIfds, stream, + preview_image_data)) { + return kFail; + } + + 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 + // determine the correct full width and height of the image, the preview image + // 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 + const float kEpsilon = 0.9f; + + std::uint16_t width; + std::uint16_t 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; + } + + if (static_cast<float>(width) / + static_cast<float>(preview_image_data->full_width) > + kEpsilon || + static_cast<float>(height) / + static_cast<float>(preview_image_data->full_height) > + kEpsilon) { + preview_image_data->full_width = width; + preview_image_data->full_height = height; + } + } + return kOk; +} + +Error OrfGetPreviewData(StreamInterface* stream, + PreviewImageData* preview_image_data) { + if (!GetExifData(0, stream, preview_image_data)) { + return kFail; + } + // Omit errors, because some images do not contain any preview data. + GetOlympusPreviewImage(stream, preview_image_data); + return kOk; +} + +Error PefGetPreviewData(StreamInterface* stream, + PreviewImageData* preview_image_data) { + const TagSet extended_tags = {kTiffTagImageWidth, kTiffTagImageLength, + kTiffTagJpegByteCount, kTiffTagJpegOffset, + kTiffTagSubIfd}; + const std::uint32_t kNumberOfIfds = 3; + 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; + + return kOk; +} + +Error RafGetPreviewData(StreamInterface* stream, + PreviewImageData* preview_image_data) { + // 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; + std::uint32_t preview_length = 0; + if (!Get32u(stream, kRafOffsetToPreviewOffset, endian, &preview_offset) || + !Get32u(stream, kRafOffsetToPreviewOffset + 4, endian, &preview_length)) { + return kFail; + } + + if (!RafGetDimension(stream, &preview_image_data->full_width, + &preview_image_data->full_height)) { + return kFail; + } + + if (preview_length > 0) { // when preview image exists + // 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)) { + return kFail; + } + } + + // Merge the Exif data with the RAW data to form the preview_image_data. + preview_image_data->thumbnail.offset += 160; // Skip the cfa header. + preview_image_data->preview.offset = preview_offset; + preview_image_data->preview.length = preview_length; + return kOk; +} + +Error Rw2GetPreviewData(StreamInterface* stream, + PreviewImageData* preview_image_data) { + const TagSet extended_tags = {kPanaTagTopBorder, kPanaTagLeftBorder, + kPanaTagBottomBorder, kPanaTagRightBorder, + kPanaTagIso, kPanaTagJpegImage, + kTiffTagJpegByteCount, kTiffTagJpegOffset}; + // Parse the RAW data to get the ISO, offset and length of the preview image, + // which contains the Exif information. + const std::uint32_t kNumberOfIfds = 1; + PreviewImageData preview_data; + 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. + const std::uint32_t exif_offset = preview_data.preview.offset + 12; + if (!GetExifData(exif_offset, stream, preview_image_data)) { + return kFail; + } + preview_image_data->thumbnail.offset += exif_offset; + } + + // Merge the Exif data with the RAW data to form the preview_image_data. + preview_image_data->preview = preview_data.preview; + 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; + + return kOk; +} + +Error SrwGetPreviewData(StreamInterface* stream, + PreviewImageData* preview_image_data) { + GetThumbnailOffsetAndLength({kTiffTagSubIfd}, stream, preview_image_data); + + const TagSet extended_tags = {kExifTagWidth, kExifTagHeight, + kTiffTagJpegByteCount, kTiffTagJpegOffset, + kTiffTagSubIfd}; + const std::uint32_t kNumberOfIfds = 1; + if (!GetPreviewData(extended_tags, kNumberOfIfds, stream, + preview_image_data)) { + return kFail; + } + return kOk; +} + +} // namespace + +size_t BytesRequiredForIsRaw() { + return image_type_recognition::GetNumberOfBytesForIsRawLite(); +} + +bool IsRaw(StreamInterface* data) { + const size_t bytes = BytesRequiredForIsRaw(); + if (data == nullptr) { + return false; + } + + // Read required number of bytes into a vector. + std::vector<std::uint8_t> file_header(bytes); + if (data->GetData(0, file_header.size(), file_header.data()) != kOk) { + return false; + } + + RangeCheckedBytePtr data_buffer(file_header.data(), file_header.size()); + + return image_type_recognition::IsRawLite(data_buffer); +} + +Error GetPreviewImageData(StreamInterface* data, + PreviewImageData* preview_image_data, + RawImageTypes* output_type) { + const size_t bytes = BytesRequiredForIsRaw(); + if (data == nullptr || bytes == 0) { + return kFail; + } + + std::vector<std::uint8_t> file_header(bytes); + Error error = data->GetData(0, file_header.size(), file_header.data()); + if (error != kOk) { + return error; + } + RangeCheckedBytePtr header_buffer(file_header.data(), file_header.size()); + + RawImageTypes type = RecognizeRawImageTypeLite(header_buffer); + if (output_type != nullptr) *output_type = type; + switch (type) { + case image_type_recognition::kArwImage: + return ArwGetPreviewData(data, preview_image_data); + case image_type_recognition::kCr2Image: + return Cr2GetPreviewData(data, preview_image_data); + case image_type_recognition::kCr3Image: + return Cr3GetPreviewData(data, preview_image_data); + case image_type_recognition::kDngImage: + return DngGetPreviewData(data, preview_image_data); + case image_type_recognition::kNefImage: + case image_type_recognition::kNrwImage: + return NefGetPreviewData(data, preview_image_data); + case image_type_recognition::kOrfImage: + return OrfGetPreviewData(data, preview_image_data); + case image_type_recognition::kPefImage: + return PefGetPreviewData(data, preview_image_data); + case image_type_recognition::kRafImage: + return RafGetPreviewData(data, preview_image_data); + case image_type_recognition::kRw2Image: + return Rw2GetPreviewData(data, preview_image_data); + case image_type_recognition::kSrwImage: + return SrwGetPreviewData(data, preview_image_data); + default: + return kUnsupported; + } +} + +bool GetDngInformation(StreamInterface* data, std::uint32_t* width, + std::uint32_t* height, + std::vector<std::uint32_t>* 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; + + size_t min_header_bytes = + std::max(GetNumberOfBytesForIsOfType(image_type_recognition::kRafImage), + GetNumberOfBytesForIsOfType(image_type_recognition::kCr3Image)); + + std::vector<std::uint8_t> file_header(min_header_bytes); + if (data->GetData(0, file_header.size(), file_header.data()) != kOk) { + return false; + } + + // For RAF and CR# 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 if (IsOfType( + RangeCheckedBytePtr(file_header.data(), file_header.size()), + image_type_recognition::kCr3Image)) { + return Cr3GetOrientation(data, orientation); + } else { + return GetExifOrientation(data, 0 /* offset */, orientation); + } +} + +std::vector<std::string> SupportedExtensions() { + return {"ARW", "CR2", "CR3", "DNG", "NEF", "NRW", + "ORF", "PEF", "RAF", "RW2", "SRW"}; +} + +} // namespace piex |