// 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/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(4);
if (raw_processing_ifd.Get(kOlymTagAspectFrame, &aspect_frame) &&
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) {
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());
switch (RecognizeRawImageTypeLite(header_buffer)) {
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::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;
std::vector<std::uint8_t> 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<std::string> SupportedExtensions() {
return {"ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "PEF", "RAF", "RW2", "SRW"};
}
} // namespace piex