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) {
124 // SkGainmapInfo::Parse will set fGainmapMathColorSpace to a non-nullptr value if we are
125 // to use the alternate image's color space primaries (which are stored in the ICC
126 // profile of the gain map image) for the gain map application color space.
127 // the gain map image.
128 if (info.fGainmapMathColorSpace) {
129 sk_sp<SkColorSpace> imageColorSpace;
130 auto iccData = metadataDecoder.getICCProfileData(/*copyData=*/false);
131 skcms_ICCProfile iccProfile;
132 if (iccData && skcms_Parse(iccData->data(), iccData->size(), &iccProfile)) {
133 imageColorSpace = SkColorSpace::Make(iccProfile);
134 }
135 // Set fGainmapMathColorSpace to the gain map image's ICC profile's color space. If
136 // it is nullptr (because there was no ICC profile, or because it failed to parse),
137 // then leave fGainmapMathColorSpace to nullptr, indicating to use the base image's
138 // color space primaries for the gain map application color space.
139 // https://crbug.com/402033761
140 info.fGainmapMathColorSpace = std::move(imageColorSpace);
141 }
142 }
143 }
144
145 if (!didPopulateInfo) {
146 // The Adobe and Apple gain map metadata require XMP. Parse it now.
147 auto xmp = metadataDecoder.getXmpMetadata();
148 if (!xmp) {
149 return false;
150 }
151
152 // Check for Adobe gain map metadata only if the base image specified hdrgm:Version="1.0".
153 if (!didPopulateInfo && baseImageHasAdobeXmp) {
154 didPopulateInfo = xmp->getGainmapInfoAdobe(&info);
155 }
156
157 // Next try for Apple gain map metadata. This does not require anything specific from the
158 // base image.
159 if (!didPopulateInfo && baseImageAppleHdrHeadroom.has_value()) {
160 didPopulateInfo = xmp->getGainmapInfoApple(baseImageAppleHdrHeadroom.value(), &info);
161 }
162 }
163
164 // If none of the formats identified itself as a gainmap and populated |info| then fail.
165 if (!didPopulateInfo) {
166 return false;
167 }
168
169 // This image is a gainmap.
170 outInfo = info;
171 if (imageDataWasCopied) {
172 outData = imageData;
173 } else {
174 outData = SkData::MakeWithCopy(imageData->data(), imageData->size());
175 }
176 return true;
177 }
178 #endif
179
findGainmapImage(SkJpegSourceMgr * sourceMgr,sk_sp<SkData> & outData,SkGainmapInfo & outInfo) const180 bool SkJpegMetadataDecoderImpl::findGainmapImage(SkJpegSourceMgr* sourceMgr,
181 sk_sp<SkData>& outData,
182 SkGainmapInfo& outInfo) const {
183 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
184 SkExif::Metadata baseExif;
185 SkExif::Parse(baseExif, getExifMetadata(/*copyData=*/false).get());
186 auto xmp = getXmpMetadata();
187
188 // Determine if a support ISO 21496-1 gain map version is present in the base image.
189 bool isoGainmapPresent =
190 SkGainmapInfo::ParseVersion(getISOGainmapMetadata(/*copyData=*/false).get());
191
192 // Determine if Adobe HDR gain map is indicated in the base image.
193 bool adobeGainmapPresent = xmp && xmp->getGainmapInfoAdobe(nullptr);
194
195 // Attempt to locate the gainmap from the container XMP.
196 size_t containerGainmapOffset = 0;
197 size_t containerGainmapSize = 0;
198 if (xmp && xmp->getContainerGainmapLocation(&containerGainmapOffset, &containerGainmapSize)) {
199 const auto& segments = sourceMgr->getAllSegments();
200 if (!segments.empty()) {
201 const auto& lastSegment = segments.back();
202 if (lastSegment.marker == kJpegMarkerEndOfImage) {
203 containerGainmapOffset += lastSegment.offset + kJpegMarkerCodeSize;
204 }
205 }
206 }
207
208 // Attempt to find MultiPicture parameters.
209 SkJpegSegment mpParamsSegment;
210 auto mpParams = find_mp_params(fMarkerList, sourceMgr, &mpParamsSegment);
211
212 // First, search through the Multi-Picture images.
213 if (mpParams) {
214 for (size_t mpImageIndex = 1; mpImageIndex < mpParams->images.size(); ++mpImageIndex) {
215 size_t mpImageOffset = SkJpegMultiPictureParameters::GetImageAbsoluteOffset(
216 mpParams->images[mpImageIndex].dataOffset, mpParamsSegment.offset);
217 size_t mpImageSize = mpParams->images[mpImageIndex].size;
218
219 if (extract_gainmap(sourceMgr,
220 mpImageOffset,
221 mpImageSize,
222 isoGainmapPresent,
223 adobeGainmapPresent,
224 baseExif.fHdrHeadroom,
225 outInfo,
226 outData)) {
227 // If the GContainer also suggested an offset and size, assert that we found the
228 // image that the GContainer suggested.
229 if (containerGainmapOffset) {
230 SkASSERT(containerGainmapOffset == mpImageOffset);
231 SkASSERT(containerGainmapSize == mpImageSize);
232 }
233 return true;
234 }
235 }
236 }
237
238 // Next, try the location suggested by the container XMP.
239 if (containerGainmapOffset) {
240 if (extract_gainmap(sourceMgr,
241 containerGainmapOffset,
242 containerGainmapSize,
243 /*baseImageHasIsoVersion=*/false,
244 adobeGainmapPresent,
245 /*baseImageAppleHdrHeadroom=*/std::nullopt,
246 outInfo,
247 outData)) {
248 return true;
249 }
250 SkCodecPrintf("Failed to extract container-specified gainmap.\n");
251 }
252 #endif
253 return false;
254 }
255
256 /**
257 * Return true if the specified SkJpegMarker has marker |targetMarker| and begins with the specified
258 * signature.
259 */
marker_has_signature(const SkJpegMarker & marker,const uint32_t targetMarker,const uint8_t * signature,size_t signatureSize)260 static bool marker_has_signature(const SkJpegMarker& marker,
261 const uint32_t targetMarker,
262 const uint8_t* signature,
263 size_t signatureSize) {
264 if (targetMarker != marker.fMarker) {
265 return false;
266 }
267 if (marker.fData->size() <= signatureSize) {
268 return false;
269 }
270 if (memcmp(marker.fData->bytes(), signature, signatureSize) != 0) {
271 return false;
272 }
273 return true;
274 }
275
276 /*
277 * Return metadata with a specific marker and signature.
278 *
279 * Search for segments that start with the specified targetMarker, followed by the specified
280 * signature, followed by (optional) padding.
281 *
282 * Some types of metadata (e.g, ICC profiles) are too big to fit into a single segment's data (which
283 * is limited to 64k), and come in multiple parts. For this type of data, bytesInIndex is >0. After
284 * the signature comes bytesInIndex bytes (big endian) for the index of the segment's part, followed
285 * by bytesInIndex bytes (big endian) for the total number of parts. If all parts are present,
286 * stitch them together and return the combined result. Return failure if parts are absent, there
287 * are duplicate parts, or parts disagree on the total number of parts.
288 *
289 * Visually, each segment is:
290 * [|signatureSize| bytes containing |signature|]
291 * [|signaturePadding| bytes that are unexamined]
292 * [|bytesInIndex] bytes listing the segment index for multi-segment metadata]
293 * [|bytesInIndex] bytes listing the segment count for multi-segment metadata]
294 * [the returned data]
295 *
296 * If alwaysCopyData is true, then return a copy of the data. If alwaysCopyData is false, then
297 * return a direct reference to the data pointed to by dinfo, if possible.
298 */
read_metadata(const SkJpegMarkerList & markerList,const uint32_t targetMarker,const uint8_t * signature,size_t signatureSize,size_t signaturePadding,size_t bytesInIndex,bool alwaysCopyData)299 static sk_sp<SkData> read_metadata(const SkJpegMarkerList& markerList,
300 const uint32_t targetMarker,
301 const uint8_t* signature,
302 size_t signatureSize,
303 size_t signaturePadding,
304 size_t bytesInIndex,
305 bool alwaysCopyData) {
306 // Compute the total size of the entire header (signature plus padding plus index plus count),
307 // since we'll use it often.
308 const size_t headerSize = signatureSize + signaturePadding + 2 * bytesInIndex;
309
310 // A map from part index to the data in each part.
311 std::vector<sk_sp<SkData>> parts;
312
313 // Running total of number of data in all parts.
314 size_t partsTotalSize = 0;
315
316 // Running total number of parts found.
317 uint32_t foundPartCount = 0;
318
319 // The expected number of parts (initialized at the first part we encounter).
320 uint32_t expectedPartCount = 0;
321
322 // Iterate through the image's segments.
323 for (const auto& marker : markerList) {
324 // Skip segments that don't have the right marker or signature.
325 if (!marker_has_signature(marker, targetMarker, signature, signatureSize)) {
326 continue;
327 }
328
329 // Skip segments that are too small to include the index and count.
330 const size_t dataLength = marker.fData->size();
331 if (dataLength <= headerSize) {
332 continue;
333 }
334
335 // Read this part's index and count as big-endian (if they are present, otherwise hard-code
336 // them to 1).
337 const uint8_t* data = marker.fData->bytes();
338 uint32_t partIndex = 0;
339 uint32_t partCount = 0;
340 if (bytesInIndex == 0) {
341 partIndex = 1;
342 partCount = 1;
343 } else {
344 for (size_t i = 0; i < bytesInIndex; ++i) {
345 const size_t offset = signatureSize + signaturePadding;
346 partIndex = (partIndex << 8) + data[offset + i];
347 partCount = (partCount << 8) + data[offset + bytesInIndex + i];
348 }
349 }
350
351 // A part count of 0 is invalid.
352 if (!partCount) {
353 SkCodecPrintf("Invalid marker part count zero\n");
354 return nullptr;
355 }
356
357 // The indices must in the range 1, ..., count.
358 if (partIndex <= 0 || partIndex > partCount) {
359 SkCodecPrintf("Invalid marker index %u for count %u\n", partIndex, partCount);
360 return nullptr;
361 }
362
363 // If this is the first marker we've encountered set the expected part count to its count.
364 if (expectedPartCount == 0) {
365 expectedPartCount = partCount;
366 parts.resize(expectedPartCount);
367 }
368
369 // If this does not match the expected part count, then fail.
370 if (partCount != expectedPartCount) {
371 SkCodecPrintf("Conflicting marker counts %u vs %u\n", partCount, expectedPartCount);
372 return nullptr;
373 }
374
375 // Make an SkData directly referencing the decoder's data for this part.
376 auto partData = SkData::MakeWithoutCopy(data + headerSize, dataLength - headerSize);
377
378 // Fail if duplicates are found.
379 if (parts[partIndex - 1]) {
380 SkCodecPrintf("Duplicate parts for index %u of %u\n", partIndex, expectedPartCount);
381 return nullptr;
382 }
383
384 // Save part in the map.
385 partsTotalSize += partData->size();
386 parts[partIndex - 1] = std::move(partData);
387 foundPartCount += 1;
388
389 // Stop as soon as we find all of the parts.
390 if (foundPartCount == expectedPartCount) {
391 break;
392 }
393 }
394
395 // Return nullptr if we don't find the data (this is not an error).
396 if (expectedPartCount == 0) {
397 return nullptr;
398 }
399
400 // Fail if we don't have all of the parts.
401 if (foundPartCount != expectedPartCount) {
402 SkCodecPrintf("Incomplete set of markers (expected %u got %u)\n",
403 expectedPartCount,
404 foundPartCount);
405 return nullptr;
406 }
407
408 // Return a direct reference to the data if there is only one part and we're allowed to.
409 if (!alwaysCopyData && expectedPartCount == 1) {
410 return std::move(parts[0]);
411 }
412
413 // Copy all of the markers and stitch them together.
414 auto result = SkData::MakeUninitialized(partsTotalSize);
415 void* copyDest = result->writable_data();
416 for (const auto& part : parts) {
417 memcpy(copyDest, part->data(), part->size());
418 copyDest = SkTAddOffset<void>(copyDest, part->size());
419 }
420 return result;
421 }
422
SkJpegMetadataDecoderImpl(SkJpegMarkerList markerList)423 SkJpegMetadataDecoderImpl::SkJpegMetadataDecoderImpl(SkJpegMarkerList markerList)
424 : fMarkerList(std::move(markerList)) {}
425
SkJpegMetadataDecoderImpl(sk_sp<SkData> data)426 SkJpegMetadataDecoderImpl::SkJpegMetadataDecoderImpl(sk_sp<SkData> data) {
427 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
428 SkJpegSegmentScanner scan(kJpegMarkerStartOfScan);
429 scan.onBytes(data->data(), data->size());
430 if (scan.hadError() || !scan.isDone()) {
431 SkCodecPrintf("Failed to scan header of MP image.\n");
432 return;
433 }
434 for (const auto& segment : scan.getSegments()) {
435 // Save the APP1 and APP2 parameters (which includes Exif, XMP, ICC, and MPF).
436 if (segment.marker != kJpegMarkerAPP0 + 1 && segment.marker != kJpegMarkerAPP0 + 2) {
437 continue;
438 }
439 auto parameters = SkJpegSegmentScanner::GetParameters(data.get(), segment);
440 if (!parameters) {
441 continue;
442 }
443 fMarkerList.emplace_back(segment.marker, std::move(parameters));
444 }
445 #endif
446 }
447
getExifMetadata(bool copyData) const448 sk_sp<SkData> SkJpegMetadataDecoderImpl::getExifMetadata(bool copyData) const {
449 return read_metadata(fMarkerList,
450 kExifMarker,
451 kExifSig,
452 sizeof(kExifSig),
453 /*signaturePadding=*/1,
454 /*bytesInIndex=*/0,
455 copyData);
456 }
457
getICCProfileData(bool copyData) const458 sk_sp<SkData> SkJpegMetadataDecoderImpl::getICCProfileData(bool copyData) const {
459 return read_metadata(fMarkerList,
460 kICCMarker,
461 kICCSig,
462 sizeof(kICCSig),
463 /*signaturePadding=*/0,
464 kICCMarkerIndexSize,
465 copyData);
466 }
467
getISOGainmapMetadata(bool copyData) const468 sk_sp<SkData> SkJpegMetadataDecoderImpl::getISOGainmapMetadata(bool copyData) const {
469 return read_metadata(fMarkerList,
470 kISOGainmapMarker,
471 kISOGainmapSig,
472 sizeof(kISOGainmapSig),
473 /*signaturePadding=*/0,
474 /*bytesInIndex=*/0,
475 copyData);
476 }
477
mightHaveGainmapImage() const478 bool SkJpegMetadataDecoderImpl::mightHaveGainmapImage() const {
479 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
480 // All supported gainmap formats require MPF. Reject images that do not have MPF.
481 return find_mp_params(fMarkerList, nullptr, nullptr) != nullptr;
482 #else
483 return false;
484 #endif
485 }
486
findGainmapImage(sk_sp<SkData> baseImageData,sk_sp<SkData> & outGainmapImageData,SkGainmapInfo & outGainmapInfo)487 bool SkJpegMetadataDecoderImpl::findGainmapImage(sk_sp<SkData> baseImageData,
488 sk_sp<SkData>& outGainmapImageData,
489 SkGainmapInfo& outGainmapInfo) {
490 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
491 auto baseImageStream = SkMemoryStream::Make(baseImageData);
492 auto sourceMgr = SkJpegSourceMgr::Make(baseImageStream.get());
493 return findGainmapImage(sourceMgr.get(), outGainmapImageData, outGainmapInfo);
494 #else
495 return false;
496 #endif
497 }
498
getJUMBFMetadata(bool copyData) const499 sk_sp<SkData> SkJpegMetadataDecoderImpl::getJUMBFMetadata(bool copyData) const {
500 return read_metadata(fMarkerList,
501 kJumbfMarker,
502 kJumbfSig,
503 sizeof(kJumbfSig),
504 /*signaturePadding=*/0,
505 /*bytesInindex=*/0,
506 copyData);
507 }
508
Make(std::vector<Segment> segments)509 std::unique_ptr<SkJpegMetadataDecoder> SkJpegMetadataDecoder::Make(std::vector<Segment> segments) {
510 return std::make_unique<SkJpegMetadataDecoderImpl>(std::move(segments));
511 }
512
Make(sk_sp<SkData> data)513 std::unique_ptr<SkJpegMetadataDecoder> SkJpegMetadataDecoder::Make(sk_sp<SkData> data) {
514 return std::make_unique<SkJpegMetadataDecoderImpl>(std::move(data));
515 }
516