• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/private/SkExif.h"
9 
10 #include "include/core/SkData.h"
11 #include "include/core/SkRefCnt.h"
12 #include "src/codec/SkTiffUtility.h"
13 
14 #include <algorithm>
15 #include <cmath>
16 #include <cstring>
17 #include <memory>
18 #include <utility>
19 
20 namespace SkExif {
21 
22 constexpr uint16_t kSubIFDOffsetTag = 0x8769;
23 constexpr uint16_t kMarkerNoteTag = 0x927c;
24 
get_maker_note_hdr_headroom(sk_sp<SkData> data)25 static std::optional<float> get_maker_note_hdr_headroom(sk_sp<SkData> data) {
26     // No little endian images that specify this data have been observed. Do not add speculative
27     // support.
28     const bool kLittleEndian = false;
29     const uint8_t kSig[] = {
30             'A', 'p', 'p', 'l', 'e', ' ', 'i', 'O', 'S', 0, 0, 1, 'M', 'M',  //
31     };
32     if (!data || data->size() < sizeof(kSig)) {
33         return std::nullopt;
34     }
35     if (memcmp(data->data(), kSig, sizeof(kSig)) != 0) {
36         return std::nullopt;
37     }
38     auto ifd = SkTiff::ImageFileDirectory::MakeFromOffset(
39             std::move(data), kLittleEndian, sizeof(kSig));
40     if (!ifd) {
41         return std::nullopt;
42     }
43 
44     // See documentation at:
45     // https://developer.apple.com/documentation/appkit/images_and_pdf/applying_apple_hdr_effect_to_your_photos
46     bool hasMaker33 = false;
47     bool hasMaker48 = false;
48     float maker33 = 0.f;
49     float maker48 = 0.f;
50     for (uint32_t i = 0; i < ifd->getNumEntries(); ++i) {
51         switch (ifd->getEntryTag(i)) {
52             case 33:
53                 if (!hasMaker33) {
54                     hasMaker33 = ifd->getEntrySignedRational(i, 1, &maker33);
55                 }
56                 break;
57             case 48:
58                 if (!hasMaker48) {
59                     hasMaker48 = ifd->getEntrySignedRational(i, 1, &maker48);
60                 }
61                 break;
62             default:
63                 break;
64         }
65     }
66     // Many images have a maker33 but not a maker48. Treat them as having maker48 of 0.
67     if (!hasMaker33) {
68         return std::nullopt;
69     }
70     float stops = 0.f;
71     if (maker33 < 1.0f) {
72         if (maker48 <= 0.01f) {
73             stops = -20.0f * maker48 + 1.8f;
74         } else {
75             stops = -0.101f * maker48 + 1.601f;
76         }
77     } else {
78         if (maker48 <= 0.01f) {
79             stops = -70.0f * maker48 + 3.0f;
80         } else {
81             stops = -0.303f * maker48 + 2.303f;
82         }
83     }
84     return std::pow(2.f, std::max(stops, 0.f));
85 }
86 
parse_ifd(Metadata & exif,sk_sp<SkData> data,std::unique_ptr<SkTiff::ImageFileDirectory> ifd,bool littleEndian,bool isRoot)87 static void parse_ifd(Metadata& exif,
88                       sk_sp<SkData> data,
89                       std::unique_ptr<SkTiff::ImageFileDirectory> ifd,
90                       bool littleEndian,
91                       bool isRoot) {
92     if (!ifd) {
93         return;
94     }
95     for (uint32_t i = 0; i < ifd->getNumEntries(); ++i) {
96         switch (ifd->getEntryTag(i)) {
97             case kOriginTag: {
98                 uint16_t value = 0;
99                 if (!exif.fOrigin.has_value() && ifd->getEntryUnsignedShort(i, 1, &value)) {
100                     if (0 < value && value <= kLast_SkEncodedOrigin) {
101                         exif.fOrigin = static_cast<SkEncodedOrigin>(value);
102                     }
103                 }
104                 break;
105             }
106             case kMarkerNoteTag:
107                 if (!exif.fHdrHeadroom.has_value()) {
108                     if (auto makerNoteData = ifd->getEntryUndefinedData(i)) {
109                         exif.fHdrHeadroom = get_maker_note_hdr_headroom(std::move(makerNoteData));
110                     }
111                 }
112                 break;
113             case kSubIFDOffsetTag: {
114                 uint32_t subIfdOffset = 0;
115                 if (isRoot && ifd->getEntryUnsignedLong(i, 1, &subIfdOffset)) {
116                     auto subIfd = SkTiff::ImageFileDirectory::MakeFromOffset(
117                             data, littleEndian, subIfdOffset, /*allowTruncated=*/true);
118                     parse_ifd(exif,
119                               data,
120                               std::move(subIfd),
121                               littleEndian,
122                               /*isRoot=*/false);
123                 }
124                 break;
125             }
126             case kXResolutionTag: {
127                 float value = 0.f;
128                 if (!exif.fXResolution.has_value() && ifd->getEntryUnsignedRational(i, 1, &value)) {
129                     exif.fXResolution = value;
130                 }
131                 break;
132             }
133             case kYResolutionTag: {
134                 float value = 0.f;
135                 if (!exif.fYResolution.has_value() && ifd->getEntryUnsignedRational(i, 1, &value)) {
136                     exif.fYResolution = value;
137                 }
138                 break;
139             }
140             case kResolutionUnitTag: {
141                 uint16_t value = 0;
142                 if (!exif.fResolutionUnit.has_value() && ifd->getEntryUnsignedShort(i, 1, &value)) {
143                     exif.fResolutionUnit = value;
144                 }
145                 break;
146             }
147             case kPixelXDimensionTag: {
148                 // The type for this tag can be unsigned short or unsigned long (as per the Exif 2.3
149                 // spec, aka CIPA DC-008-2012). Support for unsigned long was added in
150                 // https://crrev.com/817600.
151                 uint16_t value16 = 0;
152                 if (!exif.fPixelXDimension.has_value() &&
153                     ifd->getEntryUnsignedShort(i, 1, &value16)) {
154                     exif.fPixelXDimension = value16;
155                 }
156                 uint32_t value32 = 0;
157                 if (!exif.fPixelXDimension.has_value() &&
158                     ifd->getEntryUnsignedLong(i, 1, &value32)) {
159                     exif.fPixelXDimension = value32;
160                 }
161                 break;
162             }
163             case kPixelYDimensionTag: {
164                 uint16_t value16 = 0;
165                 if (!exif.fPixelYDimension.has_value() &&
166                     ifd->getEntryUnsignedShort(i, 1, &value16)) {
167                     exif.fPixelYDimension = value16;
168                 }
169                 uint32_t value32 = 0;
170                 if (!exif.fPixelYDimension.has_value() &&
171                     ifd->getEntryUnsignedLong(i, 1, &value32)) {
172                     exif.fPixelYDimension = value32;
173                 }
174                 break;
175             }
176             default:
177                 break;
178         }
179     }
180 }
181 
Parse(Metadata & metadata,const SkData * data)182 void Parse(Metadata& metadata, const SkData* data) {
183     bool littleEndian = false;
184     uint32_t ifdOffset = 0;
185     if (data && SkTiff::ImageFileDirectory::ParseHeader(data, &littleEndian, &ifdOffset)) {
186         auto dataRef = SkData::MakeWithoutCopy(data->data(), data->size());
187         auto ifd = SkTiff::ImageFileDirectory::MakeFromOffset(
188                 dataRef, littleEndian, ifdOffset, /*allowTruncated=*/true);
189         parse_ifd(metadata, std::move(dataRef), std::move(ifd), littleEndian, /*isRoot=*/true);
190     }
191 }
192 
193 }  // namespace SkExif
194