| // 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 |