blob: c2e6557c7e65faacbbb6fe46e81946584715b83c [file] [log] [blame]
/*
* 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;
}
}
}