xref: /aosp_15_r20/external/skia/src/codec/SkJpegMetadataDecoderImpl.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2024 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 "src/codec/SkJpegMetadataDecoderImpl.h"
9 
10 #include "include/core/SkData.h"
11 #include "include/private/base/SkTemplates.h"
12 #include "src/codec/SkCodecPriv.h"
13 #include "src/codec/SkJpegConstants.h"
14 
15 #include <cstdint>
16 #include <cstring>
17 #include <memory>
18 #include <utility>
19 
20 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
21 #include "include/core/SkStream.h"
22 #include "include/private/SkExif.h"
23 #include "include/private/SkGainmapInfo.h"
24 #include "include/private/SkXmp.h"
25 #include "src/base/SkEndian.h"
26 #include "src/codec/SkJpegMultiPicture.h"
27 #include "src/codec/SkJpegSegmentScan.h"
28 #include "src/codec/SkJpegSourceMgr.h"
29 #include "src/codec/SkJpegXmp.h"
30 #else
31 struct SkGainmapInfo;
32 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
33 
34 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
getXmpMetadata() const35 std::unique_ptr<SkXmp> SkJpegMetadataDecoderImpl::getXmpMetadata() const {
36     std::vector<sk_sp<SkData>> decoderApp1Params;
37     for (const auto& marker : fMarkerList) {
38         if (marker.fMarker == kXMPMarker) {
39             decoderApp1Params.push_back(marker.fData);
40         }
41     }
42     return SkJpegMakeXmp(decoderApp1Params);
43 }
44 
45 // Extract the SkJpegMultiPictureParameters from this image (if they exist). If |sourceMgr| and
46 // |outMpParamsSegment| are non-nullptr, then also return the SkJpegSegment that the parameters came
47 // from (and return nullptr if one cannot be found).
find_mp_params(const SkJpegMarkerList & markerList,SkJpegSourceMgr * sourceMgr,SkJpegSegment * outMpParamsSegment)48 static std::unique_ptr<SkJpegMultiPictureParameters> find_mp_params(
49         const SkJpegMarkerList& markerList,
50         SkJpegSourceMgr* sourceMgr,
51         SkJpegSegment* outMpParamsSegment) {
52     std::unique_ptr<SkJpegMultiPictureParameters> mpParams;
53     size_t skippedSegmentCount = 0;
54 
55     // Search though the libjpeg segments until we find a segment that parses as MP parameters. Keep
56     // track of how many segments with the MPF marker we skipped over to get there.
57     for (const auto& marker : markerList) {
58         if (marker.fMarker != kMpfMarker) {
59             continue;
60         }
61         mpParams = SkJpegMultiPictureParameters::Make(marker.fData);
62         if (mpParams) {
63             break;
64         }
65         ++skippedSegmentCount;
66     }
67     if (!mpParams) {
68         return nullptr;
69     }
70 
71     // If |sourceMgr| is not specified, then do not try to find the SkJpegSegment.
72     if (!sourceMgr) {
73         SkASSERT(!outMpParamsSegment);
74         return mpParams;
75     }
76 
77     // Now, find the SkJpegSegmentScanner segment that corresponds to the libjpeg marker.
78     // TODO(ccameron): It may be preferable to make SkJpegSourceMgr save segments with certain
79     // markers to avoid this strangeness.
80     for (const auto& segment : sourceMgr->getAllSegments()) {
81         if (segment.marker != kMpfMarker) {
82             continue;
83         }
84         if (skippedSegmentCount == 0) {
85             *outMpParamsSegment = segment;
86             return mpParams;
87         }
88         skippedSegmentCount--;
89     }
90     return nullptr;
91 }
92 
93 // Attempt to extract a gainmap image from a specified offset and size within the decoder's stream.
94 // Returns true only if the extracted gainmap image includes XMP metadata that specifies HDR gainmap
95 // rendering parameters.
extract_gainmap(SkJpegSourceMgr * decoderSource,size_t offset,size_t size,bool baseImageHasIsoVersion,bool baseImageHasAdobeXmp,std::optional<float> baseImageAppleHdrHeadroom,SkGainmapInfo & outInfo,sk_sp<SkData> & outData)96 static bool extract_gainmap(SkJpegSourceMgr* decoderSource,
97                             size_t offset,
98                             size_t size,
99                             bool baseImageHasIsoVersion,
100                             bool baseImageHasAdobeXmp,
101                             std::optional<float> baseImageAppleHdrHeadroom,
102                             SkGainmapInfo& outInfo,
103                             sk_sp<SkData>& outData) {
104     // Extract the SkData for this image.
105     bool imageDataWasCopied = false;
106     auto imageData = decoderSource->getSubsetData(offset, size, &imageDataWasCopied);
107     if (!imageData) {
108         SkCodecPrintf("Failed to extract MP image.\n");
109         return false;
110     }
111 
112     // Parse the potential gainmap image's metadata.
113     SkJpegMetadataDecoderImpl metadataDecoder(imageData);
114 
115     // If this image identifies itself as a gainmap, then populate |info|.
116     bool didPopulateInfo = false;
117     SkGainmapInfo info;
118 
119     // Check for ISO 21496-1 gain map metadata.
120     if (baseImageHasIsoVersion) {
121         didPopulateInfo = SkGainmapInfo::Parse(
122                 metadataDecoder.getISOGainmapMetadata(/*copyData=*/false).get(), info);
123         if (didPopulateInfo && info.fGainmapMathColorSpace) {
124             auto iccData = metadataDecoder.getICCProfileData(/*copyData=*/false);
125             skcms_ICCProfile iccProfile;
126             if (iccData && skcms_Parse(iccData->data(), iccData->size(), &iccProfile)) {
127                 auto iccProfileSpace = SkColorSpace::Make(iccProfile);
128                 if (iccProfileSpace) {
129                     info.fGainmapMathColorSpace = std::move(iccProfileSpace);
130                 }
131             }
132         }
133     }
134 
135     if (!didPopulateInfo) {
136         // The Adobe and Apple gain map metadata require XMP. Parse it now.
137         auto xmp = metadataDecoder.getXmpMetadata();
138         if (!xmp) {
139             return false;
140         }
141 
142         // Check for Adobe gain map metadata only if the base image specified hdrgm:Version="1.0".
143         if (!didPopulateInfo && baseImageHasAdobeXmp) {
144             didPopulateInfo = xmp->getGainmapInfoAdobe(&info);
145         }
146 
147         // Next try for Apple gain map metadata. This does not require anything specific from the
148         // base image.
149         if (!didPopulateInfo && baseImageAppleHdrHeadroom.has_value()) {
150             didPopulateInfo = xmp->getGainmapInfoApple(baseImageAppleHdrHeadroom.value(), &info);
151         }
152     }
153 
154     // If none of the formats identified itself as a gainmap and populated |info| then fail.
155     if (!didPopulateInfo) {
156         return false;
157     }
158 
159     // This image is a gainmap.
160     outInfo = info;
161     if (imageDataWasCopied) {
162         outData = imageData;
163     } else {
164         outData = SkData::MakeWithCopy(imageData->data(), imageData->size());
165     }
166     return true;
167 }
168 #endif
169 
findGainmapImage(SkJpegSourceMgr * sourceMgr,sk_sp<SkData> & outData,SkGainmapInfo & outInfo) const170 bool SkJpegMetadataDecoderImpl::findGainmapImage(SkJpegSourceMgr* sourceMgr,
171                                                  sk_sp<SkData>& outData,
172                                                  SkGainmapInfo& outInfo) const {
173 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
174     SkExif::Metadata baseExif;
175     SkExif::Parse(baseExif, getExifMetadata(/*copyData=*/false).get());
176     auto xmp = getXmpMetadata();
177 
178     // Determine if a support ISO 21496-1 gain map version is present in the base image.
179     bool isoGainmapPresent =
180             SkGainmapInfo::ParseVersion(getISOGainmapMetadata(/*copyData=*/false).get());
181 
182     // Determine if Adobe HDR gain map is indicated in the base image.
183     bool adobeGainmapPresent = xmp && xmp->getGainmapInfoAdobe(nullptr);
184 
185     // Attempt to locate the gainmap from the container XMP.
186     size_t containerGainmapOffset = 0;
187     size_t containerGainmapSize = 0;
188     if (xmp && xmp->getContainerGainmapLocation(&containerGainmapOffset, &containerGainmapSize)) {
189         const auto& segments = sourceMgr->getAllSegments();
190         if (!segments.empty()) {
191             const auto& lastSegment = segments.back();
192             if (lastSegment.marker == kJpegMarkerEndOfImage) {
193                 containerGainmapOffset += lastSegment.offset + kJpegMarkerCodeSize;
194             }
195         }
196     }
197 
198     // Attempt to find MultiPicture parameters.
199     SkJpegSegment mpParamsSegment;
200     auto mpParams = find_mp_params(fMarkerList, sourceMgr, &mpParamsSegment);
201 
202     // First, search through the Multi-Picture images.
203     if (mpParams) {
204         for (size_t mpImageIndex = 1; mpImageIndex < mpParams->images.size(); ++mpImageIndex) {
205             size_t mpImageOffset = SkJpegMultiPictureParameters::GetImageAbsoluteOffset(
206                     mpParams->images[mpImageIndex].dataOffset, mpParamsSegment.offset);
207             size_t mpImageSize = mpParams->images[mpImageIndex].size;
208 
209             if (extract_gainmap(sourceMgr,
210                                 mpImageOffset,
211                                 mpImageSize,
212                                 isoGainmapPresent,
213                                 adobeGainmapPresent,
214                                 baseExif.fHdrHeadroom,
215                                 outInfo,
216                                 outData)) {
217                 // If the GContainer also suggested an offset and size, assert that we found the
218                 // image that the GContainer suggested.
219                 if (containerGainmapOffset) {
220                     SkASSERT(containerGainmapOffset == mpImageOffset);
221                     SkASSERT(containerGainmapSize == mpImageSize);
222                 }
223                 return true;
224             }
225         }
226     }
227 
228     // Next, try the location suggested by the container XMP.
229     if (containerGainmapOffset) {
230         if (extract_gainmap(sourceMgr,
231                             containerGainmapOffset,
232                             containerGainmapSize,
233                             /*baseImageHasIsoVersion=*/false,
234                             adobeGainmapPresent,
235                             /*baseImageAppleHdrHeadroom=*/std::nullopt,
236                             outInfo,
237                             outData)) {
238             return true;
239         }
240         SkCodecPrintf("Failed to extract container-specified gainmap.\n");
241     }
242 #endif
243     return false;
244 }
245 
246 /**
247  * Return true if the specified SkJpegMarker has marker |targetMarker| and begins with the specified
248  * signature.
249  */
marker_has_signature(const SkJpegMarker & marker,const uint32_t targetMarker,const uint8_t * signature,size_t signatureSize)250 static bool marker_has_signature(const SkJpegMarker& marker,
251                                  const uint32_t targetMarker,
252                                  const uint8_t* signature,
253                                  size_t signatureSize) {
254     if (targetMarker != marker.fMarker) {
255         return false;
256     }
257     if (marker.fData->size() <= signatureSize) {
258         return false;
259     }
260     if (memcmp(marker.fData->bytes(), signature, signatureSize) != 0) {
261         return false;
262     }
263     return true;
264 }
265 
266 /*
267  * Return metadata with a specific marker and signature.
268  *
269  * Search for segments that start with the specified targetMarker, followed by the specified
270  * signature, followed by (optional) padding.
271  *
272  * Some types of metadata (e.g, ICC profiles) are too big to fit into a single segment's data (which
273  * is limited to 64k), and come in multiple parts. For this type of data, bytesInIndex is >0. After
274  * the signature comes bytesInIndex bytes (big endian) for the index of the segment's part, followed
275  * by bytesInIndex bytes (big endian) for the total number of parts. If all parts are present,
276  * stitch them together and return the combined result. Return failure if parts are absent, there
277  * are duplicate parts, or parts disagree on the total number of parts.
278  *
279  * Visually, each segment is:
280  * [|signatureSize| bytes containing |signature|]
281  * [|signaturePadding| bytes that are unexamined]
282  * [|bytesInIndex] bytes listing the segment index for multi-segment metadata]
283  * [|bytesInIndex] bytes listing the segment count for multi-segment metadata]
284  * [the returned data]
285  *
286  * If alwaysCopyData is true, then return a copy of the data. If alwaysCopyData is false, then
287  * return a direct reference to the data pointed to by dinfo, if possible.
288  */
read_metadata(const SkJpegMarkerList & markerList,const uint32_t targetMarker,const uint8_t * signature,size_t signatureSize,size_t signaturePadding,size_t bytesInIndex,bool alwaysCopyData)289 static sk_sp<SkData> read_metadata(const SkJpegMarkerList& markerList,
290                                    const uint32_t targetMarker,
291                                    const uint8_t* signature,
292                                    size_t signatureSize,
293                                    size_t signaturePadding,
294                                    size_t bytesInIndex,
295                                    bool alwaysCopyData) {
296     // Compute the total size of the entire header (signature plus padding plus index plus count),
297     // since we'll use it often.
298     const size_t headerSize = signatureSize + signaturePadding + 2 * bytesInIndex;
299 
300     // A map from part index to the data in each part.
301     std::vector<sk_sp<SkData>> parts;
302 
303     // Running total of number of data in all parts.
304     size_t partsTotalSize = 0;
305 
306     // Running total number of parts found.
307     uint32_t foundPartCount = 0;
308 
309     // The expected number of parts (initialized at the first part we encounter).
310     uint32_t expectedPartCount = 0;
311 
312     // Iterate through the image's segments.
313     for (const auto& marker : markerList) {
314         // Skip segments that don't have the right marker or signature.
315         if (!marker_has_signature(marker, targetMarker, signature, signatureSize)) {
316             continue;
317         }
318 
319         // Skip segments that are too small to include the index and count.
320         const size_t dataLength = marker.fData->size();
321         if (dataLength <= headerSize) {
322             continue;
323         }
324 
325         // Read this part's index and count as big-endian (if they are present, otherwise hard-code
326         // them to 1).
327         const uint8_t* data = marker.fData->bytes();
328         uint32_t partIndex = 0;
329         uint32_t partCount = 0;
330         if (bytesInIndex == 0) {
331             partIndex = 1;
332             partCount = 1;
333         } else {
334             for (size_t i = 0; i < bytesInIndex; ++i) {
335                 const size_t offset = signatureSize + signaturePadding;
336                 partIndex = (partIndex << 8) + data[offset + i];
337                 partCount = (partCount << 8) + data[offset + bytesInIndex + i];
338             }
339         }
340 
341         // A part count of 0 is invalid.
342         if (!partCount) {
343             SkCodecPrintf("Invalid marker part count zero\n");
344             return nullptr;
345         }
346 
347         // The indices must in the range 1, ..., count.
348         if (partIndex <= 0 || partIndex > partCount) {
349             SkCodecPrintf("Invalid marker index %u for count %u\n", partIndex, partCount);
350             return nullptr;
351         }
352 
353         // If this is the first marker we've encountered set the expected part count to its count.
354         if (expectedPartCount == 0) {
355             expectedPartCount = partCount;
356             parts.resize(expectedPartCount);
357         }
358 
359         // If this does not match the expected part count, then fail.
360         if (partCount != expectedPartCount) {
361             SkCodecPrintf("Conflicting marker counts %u vs %u\n", partCount, expectedPartCount);
362             return nullptr;
363         }
364 
365         // Make an SkData directly referencing the decoder's data for this part.
366         auto partData = SkData::MakeWithoutCopy(data + headerSize, dataLength - headerSize);
367 
368         // Fail if duplicates are found.
369         if (parts[partIndex - 1]) {
370             SkCodecPrintf("Duplicate parts for index %u of %u\n", partIndex, expectedPartCount);
371             return nullptr;
372         }
373 
374         // Save part in the map.
375         partsTotalSize += partData->size();
376         parts[partIndex - 1] = std::move(partData);
377         foundPartCount += 1;
378 
379         // Stop as soon as we find all of the parts.
380         if (foundPartCount == expectedPartCount) {
381             break;
382         }
383     }
384 
385     // Return nullptr if we don't find the data (this is not an error).
386     if (expectedPartCount == 0) {
387         return nullptr;
388     }
389 
390     // Fail if we don't have all of the parts.
391     if (foundPartCount != expectedPartCount) {
392         SkCodecPrintf("Incomplete set of markers (expected %u got %u)\n",
393                       expectedPartCount,
394                       foundPartCount);
395         return nullptr;
396     }
397 
398     // Return a direct reference to the data if there is only one part and we're allowed to.
399     if (!alwaysCopyData && expectedPartCount == 1) {
400         return std::move(parts[0]);
401     }
402 
403     // Copy all of the markers and stitch them together.
404     auto result = SkData::MakeUninitialized(partsTotalSize);
405     void* copyDest = result->writable_data();
406     for (const auto& part : parts) {
407         memcpy(copyDest, part->data(), part->size());
408         copyDest = SkTAddOffset<void>(copyDest, part->size());
409     }
410     return result;
411 }
412 
SkJpegMetadataDecoderImpl(SkJpegMarkerList markerList)413 SkJpegMetadataDecoderImpl::SkJpegMetadataDecoderImpl(SkJpegMarkerList markerList)
414         : fMarkerList(std::move(markerList)) {}
415 
SkJpegMetadataDecoderImpl(sk_sp<SkData> data)416 SkJpegMetadataDecoderImpl::SkJpegMetadataDecoderImpl(sk_sp<SkData> data) {
417 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
418     SkJpegSegmentScanner scan(kJpegMarkerStartOfScan);
419     scan.onBytes(data->data(), data->size());
420     if (scan.hadError() || !scan.isDone()) {
421         SkCodecPrintf("Failed to scan header of MP image.\n");
422         return;
423     }
424     for (const auto& segment : scan.getSegments()) {
425         // Save the APP1 and APP2 parameters (which includes Exif, XMP, ICC, and MPF).
426         if (segment.marker != kJpegMarkerAPP0 + 1 && segment.marker != kJpegMarkerAPP0 + 2) {
427             continue;
428         }
429         auto parameters = SkJpegSegmentScanner::GetParameters(data.get(), segment);
430         if (!parameters) {
431             continue;
432         }
433         fMarkerList.emplace_back(segment.marker, std::move(parameters));
434     }
435 #endif
436 }
437 
getExifMetadata(bool copyData) const438 sk_sp<SkData> SkJpegMetadataDecoderImpl::getExifMetadata(bool copyData) const {
439     return read_metadata(fMarkerList,
440                          kExifMarker,
441                          kExifSig,
442                          sizeof(kExifSig),
443                          /*signaturePadding=*/1,
444                          /*bytesInIndex=*/0,
445                          copyData);
446 }
447 
getICCProfileData(bool copyData) const448 sk_sp<SkData> SkJpegMetadataDecoderImpl::getICCProfileData(bool copyData) const {
449     return read_metadata(fMarkerList,
450                          kICCMarker,
451                          kICCSig,
452                          sizeof(kICCSig),
453                          /*signaturePadding=*/0,
454                          kICCMarkerIndexSize,
455                          copyData);
456 }
457 
getISOGainmapMetadata(bool copyData) const458 sk_sp<SkData> SkJpegMetadataDecoderImpl::getISOGainmapMetadata(bool copyData) const {
459     return read_metadata(fMarkerList,
460                          kISOGainmapMarker,
461                          kISOGainmapSig,
462                          sizeof(kISOGainmapSig),
463                          /*signaturePadding=*/0,
464                          /*bytesInIndex=*/0,
465                          copyData);
466 }
467 
mightHaveGainmapImage() const468 bool SkJpegMetadataDecoderImpl::mightHaveGainmapImage() const {
469 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
470     // All supported gainmap formats require MPF. Reject images that do not have MPF.
471     return find_mp_params(fMarkerList, nullptr, nullptr) != nullptr;
472 #else
473     return false;
474 #endif
475 }
476 
findGainmapImage(sk_sp<SkData> baseImageData,sk_sp<SkData> & outGainmapImageData,SkGainmapInfo & outGainmapInfo)477 bool SkJpegMetadataDecoderImpl::findGainmapImage(sk_sp<SkData> baseImageData,
478                                                  sk_sp<SkData>& outGainmapImageData,
479                                                  SkGainmapInfo& outGainmapInfo) {
480 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
481     auto baseImageStream = SkMemoryStream::Make(baseImageData);
482     auto sourceMgr = SkJpegSourceMgr::Make(baseImageStream.get());
483     return findGainmapImage(sourceMgr.get(), outGainmapImageData, outGainmapInfo);
484 #else
485     return false;
486 #endif
487 }
488 
Make(std::vector<Segment> segments)489 std::unique_ptr<SkJpegMetadataDecoder> SkJpegMetadataDecoder::Make(std::vector<Segment> segments) {
490     return std::make_unique<SkJpegMetadataDecoderImpl>(std::move(segments));
491 }
492 
Make(sk_sp<SkData> data)493 std::unique_ptr<SkJpegMetadataDecoder> SkJpegMetadataDecoder::Make(sk_sp<SkData> data) {
494     return std::make_unique<SkJpegMetadataDecoderImpl>(std::move(data));
495 }
496