Flutter Engine
The Flutter Engine
SkJpegMetadataDecoderImpl.cpp
Go to the documentation of this file.
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
9
10#include "include/core/SkData.h"
14
15#include <cstdint>
16#include <cstring>
17#include <memory>
18#include <utility>
19
20#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
25#include "src/base/SkEndian.h"
29#include "src/codec/SkJpegXmp.h"
30#else
31struct SkGainmapInfo;
32#endif // SK_CODEC_DECODES_JPEG_GAINMAPS
33
34#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
35std::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).
48static 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 }
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.
96static 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;
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
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 =
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) {
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 */
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 */
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
414 : fMarkerList(std::move(markerList)) {}
415
417#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
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
439 return read_metadata(fMarkerList,
441 kExifSig,
442 sizeof(kExifSig),
443 /*signaturePadding=*/1,
444 /*bytesInIndex=*/0,
445 copyData);
446}
447
449 return read_metadata(fMarkerList,
451 kICCSig,
452 sizeof(kICCSig),
453 /*signaturePadding=*/0,
455 copyData);
456}
457
459 return read_metadata(fMarkerList,
462 sizeof(kISOGainmapSig),
463 /*signaturePadding=*/0,
464 /*bytesInIndex=*/0,
465 copyData);
466}
467
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
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
489std::unique_ptr<SkJpegMetadataDecoder> SkJpegMetadataDecoder::Make(std::vector<Segment> segments) {
490 return std::make_unique<SkJpegMetadataDecoderImpl>(std::move(segments));
491}
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
static const char marker[]
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SkCodecPrintf(...)
Definition: SkCodecPriv.h:23
static constexpr uint32_t kExifMarker
static constexpr uint8_t kICCSig[]
static constexpr uint32_t kMpfMarker
static constexpr uint8_t kJpegMarkerStartOfScan
static constexpr uint32_t kICCMarker
static constexpr uint32_t kISOGainmapMarker
static constexpr uint32_t kXMPMarker
static constexpr uint8_t kJpegMarkerEndOfImage
static constexpr uint8_t kJpegMarkerAPP0
static constexpr size_t kJpegMarkerCodeSize
static constexpr uint8_t kISOGainmapSig[]
constexpr uint8_t kExifSig[]
static constexpr uint32_t kICCMarkerIndexSize
static sk_sp< SkData > read_metadata(const SkJpegMarkerList &markerList, const uint32_t targetMarker, const uint8_t *signature, size_t signatureSize, size_t signaturePadding, size_t bytesInIndex, bool alwaysCopyData)
static bool marker_has_signature(const SkJpegMarker &marker, const uint32_t targetMarker, const uint8_t *signature, size_t signatureSize)
std::vector< SkJpegMarker > SkJpegMarkerList
std::unique_ptr< SkXmp > SkJpegMakeXmp(const std::vector< sk_sp< SkData > > &decoderApp1Params)
Definition: SkJpegXmp.cpp:181
static sk_sp< SkColorSpace > Make(const skcms_ICCProfile &)
static sk_sp< SkData > MakeWithoutCopy(const void *data, size_t length)
Definition: SkData.h:116
static sk_sp< SkData > MakeUninitialized(size_t length)
Definition: SkData.cpp:116
static sk_sp< SkData > MakeWithCopy(const void *data, size_t length)
Definition: SkData.cpp:111
sk_sp< SkData > getICCProfileData(bool copyData) const override
sk_sp< SkData > getISOGainmapMetadata(bool copyData) const override
bool mightHaveGainmapImage() const override
sk_sp< SkData > getExifMetadata(bool copyData) const override
SkJpegMetadataDecoderImpl(SkJpegMarkerList markerList)
bool findGainmapImage(SkJpegSourceMgr *sourceMgr, sk_sp< SkData > &outGainmapImageData, SkGainmapInfo &outGainmapInfo) const
static std::unique_ptr< SkJpegMetadataDecoder > Make(std::vector< Segment > headerSegments)
const std::vector< SkJpegSegment > & getSegments() const
static sk_sp< SkData > GetParameters(const SkData *scannedData, const SkJpegSegment &segment)
void onBytes(const void *data, size_t size)
static std::unique_ptr< SkJpegSourceMgr > Make(SkStream *stream, size_t bufferSize=1024)
static std::unique_ptr< SkMemoryStream > Make(sk_sp< SkData > data)
Definition: SkStream.cpp:314
GAsyncResult * result
void SK_API Parse(Metadata &metadata, const SkData *data)
Definition: SkExif.cpp:182
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
const myers::Point & get(const myers::Segment &)
Definition: ref_ptr.h:256
static bool skcms_Parse(const void *buf, size_t len, skcms_ICCProfile *profile)
Definition: skcms_public.h:245
SeparatedVector2 offset
std::optional< float > fHdrHeadroom
Definition: SkExif.h:35
static bool ParseVersion(const SkData *data)
static bool Parse(const SkData *data, SkGainmapInfo &info)
static size_t GetImageAbsoluteOffset(uint32_t dataOffset, size_t mpSegmentOffset)
static std::unique_ptr< SkJpegMultiPictureParameters > Make(const sk_sp< const SkData > &segmentParameters)
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63