xref: /aosp_15_r20/external/skia/src/codec/SkExif.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 "include/core/SkStream.h"
13 #include "src/codec/SkCodecPriv.h"
14 #include "src/codec/SkTiffUtility.h"
15 #include "src/core/SkStreamPriv.h"
16 
17 #include <algorithm>
18 #include <cmath>
19 #include <cstring>
20 #include <memory>
21 #include <utility>
22 
23 namespace SkExif {
24 
25 constexpr uint16_t kSubIFDOffsetTag = 0x8769;
26 constexpr uint16_t kMarkerNoteTag = 0x927c;
27 
get_maker_note_hdr_headroom(sk_sp<SkData> data)28 static std::optional<float> get_maker_note_hdr_headroom(sk_sp<SkData> data) {
29     // No little endian images that specify this data have been observed. Do not add speculative
30     // support.
31     const bool kLittleEndian = false;
32     const uint8_t kSig[] = {
33             'A', 'p', 'p', 'l', 'e', ' ', 'i', 'O', 'S', 0, 0, 1, 'M', 'M',  //
34     };
35     if (!data || data->size() < sizeof(kSig)) {
36         return std::nullopt;
37     }
38     if (memcmp(data->data(), kSig, sizeof(kSig)) != 0) {
39         return std::nullopt;
40     }
41     auto ifd = SkTiff::ImageFileDirectory::MakeFromOffset(
42             std::move(data), kLittleEndian, sizeof(kSig));
43     if (!ifd) {
44         return std::nullopt;
45     }
46 
47     // See documentation at:
48     // https://developer.apple.com/documentation/appkit/images_and_pdf/applying_apple_hdr_effect_to_your_photos
49     bool hasMaker33 = false;
50     bool hasMaker48 = false;
51     float maker33 = 0.f;
52     float maker48 = 0.f;
53     for (uint32_t i = 0; i < ifd->getNumEntries(); ++i) {
54         switch (ifd->getEntryTag(i)) {
55             case 33:
56                 if (!hasMaker33) {
57                     hasMaker33 = ifd->getEntrySignedRational(i, 1, &maker33);
58                 }
59                 break;
60             case 48:
61                 if (!hasMaker48) {
62                     hasMaker48 = ifd->getEntrySignedRational(i, 1, &maker48);
63                 }
64                 break;
65             default:
66                 break;
67         }
68     }
69     // Many images have a maker33 but not a maker48. Treat them as having maker48 of 0.
70     if (!hasMaker33) {
71         return std::nullopt;
72     }
73     float stops = 0.f;
74     if (maker33 < 1.0f) {
75         if (maker48 <= 0.01f) {
76             stops = -20.0f * maker48 + 1.8f;
77         } else {
78             stops = -0.101f * maker48 + 1.601f;
79         }
80     } else {
81         if (maker48 <= 0.01f) {
82             stops = -70.0f * maker48 + 3.0f;
83         } else {
84             stops = -0.303f * maker48 + 2.303f;
85         }
86     }
87     return std::pow(2.f, std::max(stops, 0.f));
88 }
89 
parse_ifd(Metadata & exif,sk_sp<SkData> data,std::unique_ptr<SkTiff::ImageFileDirectory> ifd,bool littleEndian,bool isRoot)90 static void parse_ifd(Metadata& exif,
91                       sk_sp<SkData> data,
92                       std::unique_ptr<SkTiff::ImageFileDirectory> ifd,
93                       bool littleEndian,
94                       bool isRoot) {
95     if (!ifd) {
96         return;
97     }
98     for (uint32_t i = 0; i < ifd->getNumEntries(); ++i) {
99         switch (ifd->getEntryTag(i)) {
100             case kOriginTag: {
101                 uint16_t value = 0;
102                 if (!exif.fOrigin.has_value() && ifd->getEntryUnsignedShort(i, 1, &value)) {
103                     if (0 < value && value <= kLast_SkEncodedOrigin) {
104                         exif.fOrigin = static_cast<SkEncodedOrigin>(value);
105                     }
106                 }
107                 break;
108             }
109             case kMarkerNoteTag:
110                 if (!exif.fHdrHeadroom.has_value()) {
111                     if (auto makerNoteData = ifd->getEntryUndefinedData(i)) {
112                         exif.fHdrHeadroom = get_maker_note_hdr_headroom(std::move(makerNoteData));
113                     }
114                 }
115                 break;
116             case kSubIFDOffsetTag: {
117                 uint32_t subIfdOffset = 0;
118                 if (isRoot && ifd->getEntryUnsignedLong(i, 1, &subIfdOffset)) {
119                     auto subIfd = SkTiff::ImageFileDirectory::MakeFromOffset(
120                             data, littleEndian, subIfdOffset, /*allowTruncated=*/true);
121                     parse_ifd(exif,
122                               data,
123                               std::move(subIfd),
124                               littleEndian,
125                               /*isRoot=*/false);
126                 }
127                 break;
128             }
129             case kXResolutionTag: {
130                 float value = 0.f;
131                 if (!exif.fXResolution.has_value() && ifd->getEntryUnsignedRational(i, 1, &value)) {
132                     exif.fXResolution = value;
133                 }
134                 break;
135             }
136             case kYResolutionTag: {
137                 float value = 0.f;
138                 if (!exif.fYResolution.has_value() && ifd->getEntryUnsignedRational(i, 1, &value)) {
139                     exif.fYResolution = value;
140                 }
141                 break;
142             }
143             case kResolutionUnitTag: {
144                 uint16_t value = 0;
145                 if (!exif.fResolutionUnit.has_value() && ifd->getEntryUnsignedShort(i, 1, &value)) {
146                     exif.fResolutionUnit = value;
147                 }
148                 break;
149             }
150             case kPixelXDimensionTag: {
151                 // The type for this tag can be unsigned short or unsigned long (as per the Exif 2.3
152                 // spec, aka CIPA DC-008-2012). Support for unsigned long was added in
153                 // https://crrev.com/817600.
154                 uint16_t value16 = 0;
155                 if (!exif.fPixelXDimension.has_value() &&
156                     ifd->getEntryUnsignedShort(i, 1, &value16)) {
157                     exif.fPixelXDimension = value16;
158                 }
159                 uint32_t value32 = 0;
160                 if (!exif.fPixelXDimension.has_value() &&
161                     ifd->getEntryUnsignedLong(i, 1, &value32)) {
162                     exif.fPixelXDimension = value32;
163                 }
164                 break;
165             }
166             case kPixelYDimensionTag: {
167                 uint16_t value16 = 0;
168                 if (!exif.fPixelYDimension.has_value() &&
169                     ifd->getEntryUnsignedShort(i, 1, &value16)) {
170                     exif.fPixelYDimension = value16;
171                 }
172                 uint32_t value32 = 0;
173                 if (!exif.fPixelYDimension.has_value() &&
174                     ifd->getEntryUnsignedLong(i, 1, &value32)) {
175                     exif.fPixelYDimension = value32;
176                 }
177                 break;
178             }
179             default:
180                 break;
181         }
182     }
183 }
184 
Parse(Metadata & metadata,const SkData * data)185 void Parse(Metadata& metadata, const SkData* data) {
186     bool littleEndian = false;
187     uint32_t ifdOffset = 0;
188     if (data && SkTiff::ImageFileDirectory::ParseHeader(data, &littleEndian, &ifdOffset)) {
189         auto dataRef = SkData::MakeWithoutCopy(data->data(), data->size());
190         auto ifd = SkTiff::ImageFileDirectory::MakeFromOffset(
191                 dataRef, littleEndian, ifdOffset, /*allowTruncated=*/true);
192         parse_ifd(metadata, std::move(dataRef), std::move(ifd), littleEndian, /*isRoot=*/true);
193     }
194 }
195 
196 // Helper function to write a single IFD entry.
write_entry(uint16_t tag,uint16_t type,uint32_t count,uint32_t value,uint32_t * endOfData,SkWStream * stream,SkWStream * buffer)197 bool write_entry(uint16_t tag, uint16_t type, uint32_t count, uint32_t value,
198                 uint32_t* endOfData, SkWStream* stream, SkWStream* buffer) {
199     bool success = true;
200     success &= SkWStreamWriteU16BE(stream, tag);
201     success &= SkWStreamWriteU16BE(stream, type);
202     success &= SkWStreamWriteU32BE(stream, count);
203     switch (tag) {
204       case kOriginTag:
205       case kResolutionUnitTag:
206         success &= SkWStreamWriteU16BE(stream, value);
207         success &= SkWStreamWriteU16BE(stream, 0); // Complete the IFD entry.
208         return success;
209       case kPixelXDimensionTag:
210       case kPixelYDimensionTag:
211         success &= SkWStreamWriteU32BE(stream, value);
212         return success;
213       case kXResolutionTag:
214       case kYResolutionTag:
215         // If the number of bytes for the type of the entry is greater than 4, we have
216         // to append the value to the end and replace the value section with an offset
217         // to where the data can be found.
218         success &= SkWStreamWriteU32BE(stream, *endOfData);
219         *endOfData += 8;
220         success &= SkWStreamWriteU32BE(buffer, value); // Numerator
221         success &= SkWStreamWriteU32BE(buffer, 1); // Denominator
222         return success;
223       case kSubIFDOffsetTag:
224         // This does not write the subIFD itself, just the IFD0 entry that points
225         // to where it is located.
226         success &= SkWStreamWriteU32BE(stream, value);
227         return success;
228       default:
229         return false;
230     }
231 }
232 
WriteExif(Metadata & metadata)233 sk_sp<SkData> WriteExif(Metadata& metadata) {
234     // Cannot write an IFD entry for MakerNote from the HDR Headroom. Information
235     // about maker48 and maker33 is lost in encode.
236     // See documentation at:
237     // https://developer.apple.com/documentation/appkit/images_and_pdf/applying_apple_hdr_effect_to_your_photos
238     if (metadata.fHdrHeadroom.has_value()) {
239         SkCodecPrintf("Cannot encode maker noter from the headroom value.\n");
240         return nullptr;
241     }
242 
243     SkDynamicMemoryWStream stream;
244     // If there exists metadata that belongs in a subIFD, we will write that to a
245     // separate stream and append it to the end of the data, before |bufferForLargerValues|.
246     bool subIFDExists = false;
247     // This buffer will hold the values that are more than 4 bytes and will be
248     // appended to the end of the data after going through all available fields.
249     SkDynamicMemoryWStream bufferForLargerValues;
250     constexpr uint32_t kOffset = 8;
251 
252     // Write the IFD header.
253     if (!stream.write(SkTiff::kEndianBig, sizeof(SkTiff::kEndianBig))) {
254       return nullptr;
255     }
256     // Offset of index IFD.
257     if (!SkWStreamWriteU32BE(&stream, kOffset)) {
258         return nullptr;
259     }
260     // Count the number of valid metadata entries.
261     uint16_t numTags = 0;
262     uint16_t numSubIFDTags = 0;
263     if (metadata.fOrigin.has_value()) numTags++;
264     if (metadata.fResolutionUnit.has_value()) numTags++;
265     if (metadata.fXResolution.has_value()) numTags++;
266     if (metadata.fYResolution.has_value()) numTags++;
267     if (metadata.fPixelXDimension.has_value()) numSubIFDTags++;
268     if (metadata.fPixelYDimension.has_value()) numSubIFDTags++;
269     if (numSubIFDTags > 0) {
270       subIFDExists = true;
271       numTags++;
272     }
273 
274     // Offset that represents where data will be appended.
275     uint32_t endOfData = kOffset
276                         + SkTiff::kSizeShort  // Number of tags
277                         + (SkTiff::kSizeEntry * numTags) // Entries
278                         + SkTiff::kSizeLong; // Next IFD offset
279     // Offset that represents where the subIFD will start if it exists.
280     const uint32_t kSubIfdOffset = endOfData;
281     if (subIFDExists) {
282       endOfData += SkTiff::kSizeShort  // Number of subIFD tags
283                   + (SkTiff::kSizeEntry * numSubIFDTags) // SubIFD entries
284                   + SkTiff::kSizeLong; // SubIFD next offset;
285     }
286 
287     // Write the number of tags in the IFD.
288     SkWStreamWriteU16BE(&stream, numTags);
289 
290     // Write the IFD entries.
291     if (metadata.fOrigin.has_value()
292         && !write_entry(kOriginTag, SkTiff::kTypeUnsignedShort, 1,
293                         metadata.fOrigin.value(), &endOfData, &stream,
294                         &bufferForLargerValues)) {
295           return nullptr;
296         }
297 
298     if (metadata.fResolutionUnit.has_value()
299         && !write_entry(kResolutionUnitTag, SkTiff::kTypeUnsignedShort, 1,
300                         metadata.fResolutionUnit.value(), &endOfData, &stream,
301                         &bufferForLargerValues)) {
302           return nullptr;
303         }
304 
305     if (metadata.fXResolution.has_value()
306         && !write_entry(kXResolutionTag, SkTiff::kTypeUnsignedRational, 1,
307                         metadata.fXResolution.value(), &endOfData, &stream,
308                         &bufferForLargerValues)) {
309           return nullptr;
310         }
311 
312     if (metadata.fYResolution.has_value()
313         && !write_entry(kYResolutionTag, SkTiff::kTypeUnsignedRational, 1,
314                         metadata.fYResolution.value(), &endOfData, &stream,
315                         &bufferForLargerValues)) {
316           return nullptr;
317         }
318 
319     if (subIFDExists && !write_entry(kSubIFDOffsetTag, SkTiff::kTypeUnsignedLong, 1,
320                       kSubIfdOffset, &endOfData, &stream, &bufferForLargerValues)) {
321           return nullptr;
322         }
323 
324     // Next IFD offset (0 for no next IFD).
325     if (!SkWStreamWriteU32BE(&stream, 0)) {
326         return nullptr;
327     }
328 
329     // After all IFD0 data has been written, then write the SubIFD (ExifIFD).
330     if (subIFDExists) {
331       // Write the number of tags in the subIFD.
332       if (!SkWStreamWriteU16BE(&stream, numSubIFDTags)) {
333         return nullptr;
334       }
335 
336       if (metadata.fPixelXDimension.has_value()
337           && !write_entry(kPixelXDimensionTag, SkTiff::kTypeUnsignedLong, 1,
338                           metadata.fPixelXDimension.value(), &endOfData, &stream,
339                           &bufferForLargerValues)) {
340             return nullptr;
341           }
342 
343       if (metadata.fPixelYDimension.has_value()
344           && !write_entry(kPixelYDimensionTag, SkTiff::kTypeUnsignedLong, 1,
345                           metadata.fPixelYDimension.value(), &endOfData, &stream,
346                           &bufferForLargerValues)) {
347             return nullptr;
348           }
349 
350       // Write the SubIFD next offset (0).
351       if (!SkWStreamWriteU32BE(&stream, 0)) {
352         return nullptr;
353       }
354     }
355 
356     // Append the data buffer to the end of the stream.
357     if (!bufferForLargerValues.writeToStream(&stream)) {
358         return nullptr;
359     }
360 
361     return stream.detachAsData();
362 }
363 
364 }  // namespace SkExif
365