aboutsummaryrefslogtreecommitdiff
path: root/src/piex.cc
diff options
context:
space:
mode:
authorSadaf Ebrahimi <sadafebrahimi@google.com>2022-09-24 03:52:09 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-09-24 03:52:09 +0000
commita7f30384a8bedbe1becc40abff65219cc9c68e7d (patch)
treeb72f2a863d0f19484283327097d58164ccd66e09 /src/piex.cc
parent035916e09570c220d043dc855d9b155f518a7878 (diff)
parent98ecd898ea61034d9f88adf50c7d82fd3ade1b2b (diff)
downloadpiex-android14-dev.tar.gz
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-xsrc/piex.cc740
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