| /* |
| * Copyright 2023 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/private/SkExif.h" |
| |
| #include "include/core/SkData.h" |
| #include "src/codec/SkCodecPriv.h" |
| #include "src/codec/SkTiffUtility.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstring> |
| #include <memory> |
| #include <utility> |
| |
| constexpr uint16_t kSubIFDOffsetTag = 0x8769; |
| constexpr uint16_t kOriginTag = 0x112; |
| constexpr uint16_t kMarkerNoteTag = 0x927c; |
| |
| // Physical resolution. |
| constexpr uint16_t kXResolutionTag = 0x011a; |
| constexpr uint16_t kYResolutionTag = 0x011b; |
| constexpr uint16_t kResolutionUnitTag = 0x0128; |
| |
| // Size in pixels. Also sometimes called ImageWidth and ImageHeight. |
| constexpr uint16_t kPixelXDimensionTag = 0xa002; |
| constexpr uint16_t kPixelYDimensionTag = 0xa003; |
| |
| static bool get_maker_note_hdr_headroom(sk_sp<SkData> data, float* hdrHeadroom) { |
| // No little endian images that specify this data have been observed. Do not add speculative |
| // support. |
| const bool kLittleEndian = false; |
| const uint8_t kSig[] = { |
| 'A', 'p', 'p', 'l', 'e', ' ', 'i', 'O', 'S', 0, 0, 1, 'M', 'M', // |
| }; |
| if (!data || data->size() < sizeof(kSig)) { |
| return false; |
| } |
| if (memcmp(data->data(), kSig, sizeof(kSig)) != 0) { |
| return false; |
| } |
| auto ifd = |
| SkTiffImageFileDirectory::MakeFromOffset(std::move(data), kLittleEndian, sizeof(kSig)); |
| if (!ifd) { |
| return false; |
| } |
| |
| // See documentation at: |
| // https://developer.apple.com/documentation/appkit/images_and_pdf/applying_apple_hdr_effect_to_your_photos |
| bool hasMaker33 = false; |
| bool hasMaker48 = false; |
| float maker33 = 0.f; |
| float maker48 = 0.f; |
| for (uint32_t i = 0; i < ifd->getNumEntries(); ++i) { |
| switch (ifd->getEntryTag(i)) { |
| case 33: |
| if (!hasMaker33) { |
| hasMaker33 = ifd->getEntrySignedRational(i, 1, &maker33); |
| } |
| break; |
| case 48: |
| if (!hasMaker48) { |
| hasMaker48 = ifd->getEntrySignedRational(i, 1, &maker48); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| if (!hasMaker33 || !hasMaker48) { |
| return false; |
| } |
| float stops = 0.f; |
| if (maker33 < 1.0f) { |
| if (maker48 <= 0.01f) { |
| stops = -20.0f * maker48 + 1.8f; |
| } else { |
| stops = -0.101f * maker48 + 1.601f; |
| } |
| } else { |
| if (maker48 <= 0.01f) { |
| stops = -70.0f * maker48 + 3.0f; |
| } else { |
| stops = -0.303f * maker48 + 2.303f; |
| } |
| } |
| *hdrHeadroom = std::pow(2.f, std::max(stops, 0.f)); |
| return true; |
| } |
| |
| SkExifMetadata::SkExifMetadata(sk_sp<SkData> data) : fData(std::move(data)) { |
| bool littleEndian = false; |
| uint32_t ifdOffset = 0; |
| if (!SkTiffImageFileDirectory::ParseHeader(fData.get(), &littleEndian, &ifdOffset)) { |
| SkCodecPrintf("Failed to parse Exif header.\n"); |
| return; |
| } |
| parseIfd(ifdOffset, littleEndian, /*isRoot=*/true); |
| } |
| |
| void SkExifMetadata::parseIfd(uint32_t ifdOffset, bool littleEndian, bool isRoot) { |
| auto ifd = SkTiffImageFileDirectory::MakeFromOffset(fData, littleEndian, ifdOffset); |
| if (!ifd) { |
| SkCodecPrintf("Failed to make IFD\n"); |
| return; |
| } |
| for (uint32_t i = 0; i < ifd->getNumEntries(); ++i) { |
| switch (ifd->getEntryTag(i)) { |
| case kOriginTag: { |
| uint16_t value = 0; |
| if (!fOriginPresent && ifd->getEntryUnsignedShort(i, 1, &value)) { |
| if (0 < value && value <= kLast_SkEncodedOrigin) { |
| fOriginValue = static_cast<SkEncodedOrigin>(value); |
| fOriginPresent = true; |
| } |
| } |
| break; |
| } |
| case kMarkerNoteTag: |
| if (!fHdrHeadroomPresent) { |
| if (auto makerNoteData = ifd->getEntryUndefinedData(i)) { |
| fHdrHeadroomPresent = get_maker_note_hdr_headroom(std::move(makerNoteData), |
| &fHdrHeadroomValue); |
| } |
| } |
| break; |
| case kSubIFDOffsetTag: { |
| uint32_t subIfdOffset = 0; |
| if (isRoot && ifd->getEntryUnsignedLong(i, 1, &subIfdOffset)) { |
| parseIfd(subIfdOffset, littleEndian, /*isRoot=*/false); |
| } |
| break; |
| } |
| case kXResolutionTag: |
| if (!fXResolutionPresent) { |
| fXResolutionPresent = ifd->getEntryUnsignedRational(i, 1, &fXResolutionValue); |
| } |
| break; |
| case kYResolutionTag: |
| if (!fYResolutionPresent) { |
| fYResolutionPresent = ifd->getEntryUnsignedRational(i, 1, &fYResolutionValue); |
| } |
| break; |
| case kResolutionUnitTag: |
| if (!fResolutionUnitPresent) { |
| fResolutionUnitPresent = |
| ifd->getEntryUnsignedShort(i, 1, &fResolutionUnitValue); |
| } |
| break; |
| case kPixelXDimensionTag: |
| // The type for this tag can be unsigned short or unsigned long (as per the Exif 2.3 |
| // spec, aka CIPA DC-008-2012). Support for unsigned long was added in |
| // https://crrev.com/817600. |
| if (!fPixelXDimensionPresent) { |
| uint16_t value16 = 0; |
| if (ifd->getEntryUnsignedShort(i, 1, &value16)) { |
| fPixelXDimensionValue = value16; |
| fPixelXDimensionPresent = true; |
| } |
| } |
| if (!fPixelXDimensionPresent) { |
| fPixelXDimensionPresent = |
| ifd->getEntryUnsignedLong(i, 1, &fPixelXDimensionValue); |
| } |
| break; |
| case kPixelYDimensionTag: |
| if (!fPixelYDimensionPresent) { |
| uint16_t value16 = 0; |
| if (ifd->getEntryUnsignedShort(i, 1, &value16)) { |
| fPixelYDimensionValue = value16; |
| fPixelYDimensionPresent = true; |
| } |
| } |
| if (!fPixelYDimensionPresent) { |
| fPixelYDimensionPresent = |
| ifd->getEntryUnsignedLong(i, 1, &fPixelYDimensionValue); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |