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