Flutter Engine
The Flutter Engine
SkJpegGainmapEncoder.cpp
Go to the documentation of this file.
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
9
24
25#include <vector>
26
27static bool is_single_channel(SkColor4f c) { return c.fR == c.fG && c.fG == c.fB; };
28
29////////////////////////////////////////////////////////////////////////////////////////////////////
30// HDRGM encoding
31
32// Generate the XMP metadata for an HDRGM file.
35 const float kLog2 = std::log(2.f);
36 const SkColor4f gainMapMin = {std::log(gainmapInfo.fGainmapRatioMin.fR) / kLog2,
37 std::log(gainmapInfo.fGainmapRatioMin.fG) / kLog2,
38 std::log(gainmapInfo.fGainmapRatioMin.fB) / kLog2,
39 1.f};
40 const SkColor4f gainMapMax = {std::log(gainmapInfo.fGainmapRatioMax.fR) / kLog2,
41 std::log(gainmapInfo.fGainmapRatioMax.fG) / kLog2,
42 std::log(gainmapInfo.fGainmapRatioMax.fB) / kLog2,
43 1.f};
44 const SkColor4f gamma = {1.f / gainmapInfo.fGainmapGamma.fR,
45 1.f / gainmapInfo.fGainmapGamma.fG,
46 1.f / gainmapInfo.fGainmapGamma.fB,
47 1.f};
48 // Write a scalar attribute.
49 auto write_scalar_attr = [&s](const char* attrib, SkScalar value) {
50 s.writeText(" ");
51 s.writeText(attrib);
52 s.writeText("=\"");
53 s.writeScalarAsText(value);
54 s.writeText("\"\n");
55 };
56
57 // Write a scalar attribute only if all channels of |value| are equal (otherwise, write
58 // nothing).
59 auto maybe_write_scalar_attr = [&write_scalar_attr](const char* attrib, SkColor4f value) {
61 return;
62 }
63 write_scalar_attr(attrib, value.fR);
64 };
65
66 // Write a float3 attribute as a list ony if not all channels of |value| are equal (otherwise,
67 // write nothing).
68 auto maybe_write_float3_attr = [&s](const char* attrib, SkColor4f value) {
70 return;
71 }
72 s.writeText(" <");
73 s.writeText(attrib);
74 s.writeText(">\n");
75 s.writeText(" <rdf:Seq>\n");
76 s.writeText(" <rdf:li>");
77 s.writeScalarAsText(value.fR);
78 s.writeText("</rdf:li>\n");
79 s.writeText(" <rdf:li>");
80 s.writeScalarAsText(value.fG);
81 s.writeText("</rdf:li>\n");
82 s.writeText(" <rdf:li>");
83 s.writeScalarAsText(value.fB);
84 s.writeText("</rdf:li>\n");
85 s.writeText(" </rdf:Seq>\n");
86 s.writeText(" </");
87 s.writeText(attrib);
88 s.writeText(">\n");
89 };
90
91 s.writeText(
92 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 5.5.0\">\n"
93 " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
94 " <rdf:Description rdf:about=\"\"\n"
95 " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\"\n"
96 " hdrgm:Version=\"1.0\"\n");
97 maybe_write_scalar_attr("hdrgm:GainMapMin", gainMapMin);
98 maybe_write_scalar_attr("hdrgm:GainMapMax", gainMapMax);
99 maybe_write_scalar_attr("hdrgm:Gamma", gamma);
100 maybe_write_scalar_attr("hdrgm:OffsetSDR", gainmapInfo.fEpsilonSdr);
101 maybe_write_scalar_attr("hdrgm:OffsetHDR", gainmapInfo.fEpsilonHdr);
102 write_scalar_attr("hdrgm:HDRCapacityMin", std::log(gainmapInfo.fDisplayRatioSdr) / kLog2);
103 write_scalar_attr("hdrgm:HDRCapacityMax", std::log(gainmapInfo.fDisplayRatioHdr) / kLog2);
104 switch (gainmapInfo.fBaseImageType) {
106 s.writeText(" hdrgm:BaseRenditionIsHDR=\"False\">\n");
107 break;
109 s.writeText(" hdrgm:BaseRenditionIsHDR=\"True\">\n");
110 break;
111 }
112
113 // Write any of the vector parameters that cannot be represented as scalars (and thus cannot
114 // be written inline as above).
115 maybe_write_float3_attr("hdrgm:GainMapMin", gainMapMin);
116 maybe_write_float3_attr("hdrgm:GainMapMax", gainMapMax);
117 maybe_write_float3_attr("hdrgm:Gamma", gamma);
118 maybe_write_float3_attr("hdrgm:OffsetSDR", gainmapInfo.fEpsilonSdr);
119 maybe_write_float3_attr("hdrgm:OffsetHDR", gainmapInfo.fEpsilonHdr);
120 s.writeText(
121 " </rdf:Description>\n"
122 " </rdf:RDF>\n"
123 "</x:xmpmeta>");
124 return s.detachAsData();
125}
126
127// Generate the GContainer metadata for an image with a JPEG gainmap.
128static sk_sp<SkData> get_base_image_xmp_metadata(size_t gainmapItemLength) {
130 s.writeText(
131 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 5.1.2\">\n"
132 " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
133 " <rdf:Description\n"
134 " xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"\n"
135 " xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\"\n"
136 " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\"\n"
137 " hdrgm:Version=\"1.0\">\n"
138 " <Container:Directory>\n"
139 " <rdf:Seq>\n"
140 " <rdf:li rdf:parseType=\"Resource\">\n"
141 " <Container:Item\n"
142 " Item:Semantic=\"Primary\"\n"
143 " Item:Mime=\"image/jpeg\"/>\n"
144 " </rdf:li>\n"
145 " <rdf:li rdf:parseType=\"Resource\">\n"
146 " <Container:Item\n"
147 " Item:Semantic=\"GainMap\"\n"
148 " Item:Mime=\"image/jpeg\"\n"
149 " Item:Length=\"");
150 s.writeDecAsText(gainmapItemLength);
151 s.writeText(
152 "\"/>\n"
153 " </rdf:li>\n"
154 " </rdf:Seq>\n"
155 " </Container:Directory>\n"
156 " </rdf:Description>\n"
157 " </rdf:RDF>\n"
158 "</x:xmpmeta>\n");
159 return s.detachAsData();
160}
161
164 const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
165 SkDynamicMemoryWStream encodeStream;
166 auto encoder = SkJpegEncoderImpl::MakeRGB(&encodeStream, pm, options, metadataSegments);
167 if (!encoder || !encoder->encodeRows(pm.height())) {
168 return nullptr;
169 }
170 return encodeStream.detachAsData();
171}
172
175
176 s.write(kExifSig, sizeof(kExifSig));
177 s.write8(0);
178
180 SkWStreamWriteU32BE(&s, 8); // Offset of index IFD
181
182 // Write the index IFD.
183 {
184 constexpr uint16_t kIndexIfdNumberOfTags = 1;
185 SkWStreamWriteU16BE(&s, kIndexIfdNumberOfTags);
186
187 constexpr uint16_t kSubIFDOffsetTag = 0x8769;
188 constexpr uint32_t kSubIfdCount = 1;
189 constexpr uint32_t kSubIfdOffset = 26;
192 SkWStreamWriteU32BE(&s, kSubIfdCount);
193 SkWStreamWriteU32BE(&s, kSubIfdOffset);
194
195 constexpr uint32_t kIndexIfdNextIfdOffset = 0;
196 SkWStreamWriteU32BE(&s, kIndexIfdNextIfdOffset);
197 }
198
199 // Write the sub-IFD.
200 {
201 constexpr uint16_t kSubIfdNumberOfTags = 1;
202 SkWStreamWriteU16BE(&s, kSubIfdNumberOfTags);
203
204 constexpr uint16_t kVersionTag = 0x9000;
205 constexpr uint32_t kVersionCount = 4;
206 constexpr uint8_t kVersion[kVersionCount] = {'0', '2', '3', '2'};
210 s.write(kVersion, sizeof(kVersion));
211
212 constexpr uint32_t kSubIfdNextIfdOffset = 0;
213 SkWStreamWriteU32BE(&s, kSubIfdNextIfdOffset);
214 }
215
216 return s.detachAsData();
217}
218
220 size_t imageNumber) {
222 auto segmentParameters = mpParams.serialize(static_cast<uint32_t>(imageNumber));
223 const size_t mpParameterLength = kJpegSegmentParameterLengthSize + segmentParameters->size();
224 s.write8(0xFF);
225 s.write8(kMpfMarker);
226 s.write8(mpParameterLength / 256);
227 s.write8(mpParameterLength % 256);
228 s.write(segmentParameters->data(), segmentParameters->size());
229 return s.detachAsData();
230}
231
234 s.write(kISOGainmapSig, sizeof(kISOGainmapSig));
235 s.write(data->data(), data->size());
236 return s.detachAsData();
237}
238
240 const SkPixmap& base,
241 const SkJpegEncoder::Options& baseOptions,
242 const SkPixmap& gainmap,
243 const SkJpegEncoder::Options& gainmapOptions,
244 const SkGainmapInfo& gainmapInfo) {
245 bool includeUltraHDRv1 = gainmapInfo.isUltraHDRv1Compatible();
246
247 // All images will have the same minimial Exif metadata.
248 auto exif_params = get_exif_params();
249
250 // Encode the gainmap image.
251 sk_sp<SkData> gainmapData;
252 {
253 SkJpegMetadataEncoder::SegmentList metadataSegments;
254
255 // Start with Exif metadata.
256 metadataSegments.emplace_back(kExifMarker, exif_params);
257
258 // MPF segment will be inserted after this.
259
260 // Add XMP metadata.
261 if (includeUltraHDRv1) {
263 metadataSegments, get_gainmap_image_xmp_metadata(gainmapInfo).get());
264 }
265
266 // Include the ICC profile of the alternate color space, if it is used.
267 if (gainmapInfo.fGainmapMathColorSpace) {
269 metadataSegments, gainmapOptions, gainmapInfo.fGainmapMathColorSpace.get());
270 }
271
272 // Add the ISO 21946-1 metadata.
273 metadataSegments.emplace_back(kISOGainmapMarker,
275
276 // Encode the gainmap image.
277 gainmapData = encode_to_data(gainmap, gainmapOptions, metadataSegments);
278 if (!gainmapData) {
279 SkCodecPrintf("Failed to encode gainmap image.\n");
280 return false;
281 }
282 }
283
284 // Encode the base image.
285 sk_sp<SkData> baseData;
286 {
287 SkJpegMetadataEncoder::SegmentList metadataSegments;
288
289 // Start with Exif metadata.
290 metadataSegments.emplace_back(kExifMarker, exif_params);
291
292 // MPF segment will be inserted after this.
293
294 // Include XMP.
295 if (includeUltraHDRv1) {
296 // Add to the gainmap image size the size of the MPF segment for image 1 of a 2-image
297 // file.
299 size_t gainmapImageSize = gainmapData->size() + get_mpf_segment(mpParams, 1)->size();
301 metadataSegments,
302 get_base_image_xmp_metadata(static_cast<int32_t>(gainmapImageSize)).get());
303 }
304
305 // Include ICC profile metadata.
306 SkJpegMetadataEncoder::AppendICC(metadataSegments, baseOptions, base.colorSpace());
307
308 // Include the ISO 21946-1 version metadata.
309 metadataSegments.emplace_back(
312
313 // Encode the base image.
314 baseData = encode_to_data(base, baseOptions, metadataSegments);
315 if (!baseData) {
316 SkCodecPrintf("Failed to encode base image.\n");
317 return false;
318 }
319 }
320
321 // Combine them into an MPF.
322 const SkData* images[] = {
323 baseData.get(),
324 gainmapData.get(),
325 };
326 return MakeMPF(dst, images, 2);
327}
328
329// Compute the offset into |image| at which the MP segment should be inserted. Return 0 on failure.
330static size_t mp_segment_offset(const SkData* image) {
331 // Scan the image until StartOfScan marker.
333 scan.onBytes(image->data(), image->size());
334 if (!scan.isDone()) {
335 SkCodecPrintf("Failed to scan image header.\n");
336 return 0;
337 }
338 const auto& segments = scan.getSegments();
339
340 // Search for the Exif segment and place the MP parameters immediately after. See 5.1.
341 // Basic MP File Structure, which indicates "The MP Extensions are specified in the APP2
342 // marker segment which follows immediately after the Exif Attributes in the APP1 marker
343 // segment except as specified in section 7".
344 for (size_t segmentIndex = 0; segmentIndex < segments.size() - 1; ++segmentIndex) {
345 const auto& segment = segments[segmentIndex];
346 if (segment.marker != kExifMarker) {
347 continue;
348 }
350 if (params->size() < sizeof(kExifSig) ||
351 memcmp(params->data(), kExifSig, sizeof(kExifSig)) != 0) {
352 continue;
353 }
354 // Insert the MPF segment at the offset of the next segment.
355 return segments[segmentIndex + 1].offset;
356 }
357
358 // If there is no Exif segment, then insert the MPF segment just before the StartOfScan.
359 return segments.back().offset;
360}
361
362bool SkJpegGainmapEncoder::MakeMPF(SkWStream* dst, const SkData** images, size_t imageCount) {
363 if (imageCount < 1) {
364 return true;
365 }
366
367 // The offset into each image at which the MP segment will be written.
368 std::vector<size_t> mpSegmentOffsets(imageCount);
369
370 // Populate the MP parameters (image sizes and offsets).
371 SkJpegMultiPictureParameters mpParams(imageCount);
372 size_t cumulativeSize = 0;
373 for (size_t i = 0; i < imageCount; ++i) {
374 // Compute the offset into the each image where we will write the MP parameters.
375 mpSegmentOffsets[i] = mp_segment_offset(images[i]);
376 if (!mpSegmentOffsets[i]) {
377 return false;
378 }
379
380 // Add the size of the MPF segment to image size. Note that the contents of
381 // get_mpf_segment() are incorrect (because we don't have the right offset values), but
382 // the size is correct.
383 const size_t imageSize = images[i]->size() + get_mpf_segment(mpParams, i)->size();
385 cumulativeSize, mpSegmentOffsets[0]);
386 mpParams.images[i].size = static_cast<uint32_t>(imageSize);
387 cumulativeSize += imageSize;
388 }
389
390 // Write the images.
391 for (size_t i = 0; i < imageCount; ++i) {
392 // Write up to the MP segment.
393 if (!dst->write(images[i]->bytes(), mpSegmentOffsets[i])) {
394 SkCodecPrintf("Failed to write image header.\n");
395 return false;
396 }
397
398 // Write the MP segment.
399 auto mpfSegment = get_mpf_segment(mpParams, i);
400 if (!dst->write(mpfSegment->data(), mpfSegment->size())) {
401 SkCodecPrintf("Failed to write MPF segment.\n");
402 return false;
403 }
404
405 // Write the rest of the image.
406 if (!dst->write(images[i]->bytes() + mpSegmentOffsets[i],
407 images[i]->size() - mpSegmentOffsets[i])) {
408 SkCodecPrintf("Failed to write image body.\n");
409 return false;
410 }
411 }
412
413 return true;
414}
const char * options
#define SkCodecPrintf(...)
Definition: SkCodecPriv.h:23
static constexpr uint32_t kExifMarker
static constexpr uint32_t kMpfMarker
static constexpr uint8_t kJpegMarkerStartOfScan
static constexpr uint32_t kISOGainmapMarker
static constexpr size_t kJpegSegmentParameterLengthSize
static constexpr uint8_t kISOGainmapSig[]
constexpr uint8_t kExifSig[]
static sk_sp< SkData > get_base_image_xmp_metadata(size_t gainmapItemLength)
sk_sp< SkData > get_gainmap_image_xmp_metadata(const SkGainmapInfo &gainmapInfo)
static bool is_single_channel(SkColor4f c)
static sk_sp< SkData > get_exif_params()
static sk_sp< SkData > encode_to_data(const SkPixmap &pm, const SkJpegEncoder::Options &options, const SkJpegMetadataEncoder::SegmentList &metadataSegments)
static size_t mp_segment_offset(const SkData *image)
static sk_sp< SkData > get_iso_gainmap_segment_params(sk_sp< SkData > data)
static sk_sp< SkData > get_mpf_segment(const SkJpegMultiPictureParameters &mpParams, size_t imageNumber)
constexpr uint16_t kVersionTag
constexpr uint32_t kVersionCount
bool SkWStreamWriteU32BE(SkWStream *s, uint32_t value)
Definition: SkStreamPriv.h:54
bool SkWStreamWriteU16BE(SkWStream *s, uint16_t value)
Definition: SkStreamPriv.h:49
Definition: SkData.h:25
size_t size() const
Definition: SkData.h:30
sk_sp< SkData > detachAsData()
Definition: SkStream.cpp:707
static std::unique_ptr< SkEncoder > MakeRGB(SkWStream *dst, const SkPixmap &src, const SkJpegEncoder::Options &options, const SkJpegMetadataEncoder::SegmentList &metadata)
static bool EncodeHDRGM(SkWStream *dst, const SkPixmap &base, const SkJpegEncoder::Options &baseOptions, const SkPixmap &gainmap, const SkJpegEncoder::Options &gainmapOptions, const SkGainmapInfo &gainmapInfo)
static bool MakeMPF(SkWStream *dst, const SkData **images, size_t imageCount)
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)
int height() const
Definition: SkPixmap.h:166
T * get() const
Definition: SkRefCnt.h:303
const EmbeddedViewParams * params
float SkScalar
Definition: extension.cpp:12
struct MyStruct s
uint8_t value
std::array< MockImage, 3 > images
Definition: mock_vulkan.cc:41
constexpr uint16_t kSubIFDOffsetTag
Definition: SkExif.cpp:22
void AppendICC(SegmentList &segmentList, const SkJpegEncoder::Options &options, const SkColorSpace *colorSpace)
void AppendXMPStandard(SegmentList &segmentList, const SkData *xmpMetadata)
std::vector< Segment > SegmentList
sk_sp< const SkImage > image
Definition: SkRecords.h:269
constexpr uint8_t kEndianBig[kEndianSize]
Definition: SkTiffUtility.h:22
constexpr uint16_t kTypeUnsignedLong
Definition: SkTiffUtility.h:29
constexpr uint16_t kTypeUndefined
Definition: SkTiffUtility.h:32
dst
Definition: cp.py:12
const myers::Point & get(const myers::Segment &)
SkColor4f fGainmapRatioMax
Definition: SkGainmapInfo.h:49
SkColor4f fEpsilonSdr
Definition: SkGainmapInfo.h:55
bool isUltraHDRv1Compatible() const
sk_sp< SkData > serialize() const
SkColor4f fGainmapGamma
Definition: SkGainmapInfo.h:50
sk_sp< SkColorSpace > fGainmapMathColorSpace
Definition: SkGainmapInfo.h:95
SkColor4f fGainmapRatioMin
Definition: SkGainmapInfo.h:48
BaseImageType fBaseImageType
Definition: SkGainmapInfo.h:75
float fDisplayRatioSdr
Definition: SkGainmapInfo.h:65
SkColor4f fEpsilonHdr
Definition: SkGainmapInfo.h:56
float fDisplayRatioHdr
Definition: SkGainmapInfo.h:66
static sk_sp< SkData > SerializeVersion()
static uint32_t GetImageDataOffset(size_t imageAbsoluteOffset, size_t mpSegmentOffset)
sk_sp< SkData > serialize(uint32_t individualImageNumber) const
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63