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