Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
21
22#include <vector>
23
24static bool is_single_channel(SkColor4f c) { return c.fR == c.fG && c.fG == c.fB; };
25
26////////////////////////////////////////////////////////////////////////////////////////////////////
27// HDRGM encoding
28
29// Generate the XMP metadata for an HDRGM file.
32 const float kLog2 = std::log(2.f);
33 const SkColor4f gainMapMin = {std::log(gainmapInfo.fGainmapRatioMin.fR) / kLog2,
34 std::log(gainmapInfo.fGainmapRatioMin.fG) / kLog2,
35 std::log(gainmapInfo.fGainmapRatioMin.fB) / kLog2,
36 1.f};
37 const SkColor4f gainMapMax = {std::log(gainmapInfo.fGainmapRatioMax.fR) / kLog2,
38 std::log(gainmapInfo.fGainmapRatioMax.fG) / kLog2,
39 std::log(gainmapInfo.fGainmapRatioMax.fB) / kLog2,
40 1.f};
41 const SkColor4f gamma = {1.f / gainmapInfo.fGainmapGamma.fR,
42 1.f / gainmapInfo.fGainmapGamma.fG,
43 1.f / gainmapInfo.fGainmapGamma.fB,
44 1.f};
45 // Write a scalar attribute.
46 auto write_scalar_attr = [&s](const char* attrib, SkScalar value) {
47 s.writeText(" ");
48 s.writeText(attrib);
49 s.writeText("=\"");
50 s.writeScalarAsText(value);
51 s.writeText("\"\n");
52 };
53
54 // Write a scalar attribute only if all channels of |value| are equal (otherwise, write
55 // nothing).
56 auto maybe_write_scalar_attr = [&write_scalar_attr](const char* attrib, SkColor4f value) {
58 return;
59 }
60 write_scalar_attr(attrib, value.fR);
61 };
62
63 // Write a float3 attribute as a list ony if not all channels of |value| are equal (otherwise,
64 // write nothing).
65 auto maybe_write_float3_attr = [&s](const char* attrib, SkColor4f value) {
67 return;
68 }
69 s.writeText(" <");
70 s.writeText(attrib);
71 s.writeText(">\n");
72 s.writeText(" <rdf:Seq>\n");
73 s.writeText(" <rdf:li>");
74 s.writeScalarAsText(value.fR);
75 s.writeText("</rdf:li>\n");
76 s.writeText(" <rdf:li>");
77 s.writeScalarAsText(value.fG);
78 s.writeText("</rdf:li>\n");
79 s.writeText(" <rdf:li>");
80 s.writeScalarAsText(value.fB);
81 s.writeText("</rdf:li>\n");
82 s.writeText(" </rdf:Seq>\n");
83 s.writeText(" </");
84 s.writeText(attrib);
85 s.writeText(">\n");
86 };
87
88 s.writeText(
89 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 5.5.0\">\n"
90 " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
91 " <rdf:Description rdf:about=\"\"\n"
92 " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\"\n"
93 " hdrgm:Version=\"1.0\"\n");
94 maybe_write_scalar_attr("hdrgm:GainMapMin", gainMapMin);
95 maybe_write_scalar_attr("hdrgm:GainMapMax", gainMapMax);
96 maybe_write_scalar_attr("hdrgm:Gamma", gamma);
97 maybe_write_scalar_attr("hdrgm:OffsetSDR", gainmapInfo.fEpsilonSdr);
98 maybe_write_scalar_attr("hdrgm:OffsetHDR", gainmapInfo.fEpsilonHdr);
99 write_scalar_attr("hdrgm:HDRCapacityMin", std::log(gainmapInfo.fDisplayRatioSdr) / kLog2);
100 write_scalar_attr("hdrgm:HDRCapacityMax", std::log(gainmapInfo.fDisplayRatioHdr) / kLog2);
101 switch (gainmapInfo.fBaseImageType) {
103 s.writeText(" hdrgm:BaseRenditionIsHDR=\"False\">\n");
104 break;
106 s.writeText(" hdrgm:BaseRenditionIsHDR=\"True\">\n");
107 break;
108 }
109
110 // Write any of the vector parameters that cannot be represented as scalars (and thus cannot
111 // be written inline as above).
112 maybe_write_float3_attr("hdrgm:GainMapMin", gainMapMin);
113 maybe_write_float3_attr("hdrgm:GainMapMax", gainMapMax);
114 maybe_write_float3_attr("hdrgm:Gamma", gamma);
115 maybe_write_float3_attr("hdrgm:OffsetSDR", gainmapInfo.fEpsilonSdr);
116 maybe_write_float3_attr("hdrgm:OffsetHDR", gainmapInfo.fEpsilonHdr);
117 s.writeText(
118 " </rdf:Description>\n"
119 " </rdf:RDF>\n"
120 "</x:xmpmeta>");
121 return s.detachAsData();
122}
123
124// Generate the GContainer metadata for an image with a JPEG gainmap.
125static sk_sp<SkData> get_gcontainer_xmp_data(size_t gainmapItemLength) {
127 s.writeText(
128 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 5.1.2\">\n"
129 " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
130 " <rdf:Description\n"
131 " xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"\n"
132 " xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\"\n"
133 " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\"\n"
134 " hdrgm:Version=\"1.0\">\n"
135 " <Container:Directory>\n"
136 " <rdf:Seq>\n"
137 " <rdf:li rdf:parseType=\"Resource\">\n"
138 " <Container:Item\n"
139 " Item:Semantic=\"Primary\"\n"
140 " Item:Mime=\"image/jpeg\"/>\n"
141 " </rdf:li>\n"
142 " <rdf:li rdf:parseType=\"Resource\">\n"
143 " <Container:Item\n"
144 " Item:Semantic=\"GainMap\"\n"
145 " Item:Mime=\"image/jpeg\"\n"
146 " Item:Length=\"");
147 s.writeDecAsText(gainmapItemLength);
148 s.writeText(
149 "\"/>\n"
150 " </rdf:li>\n"
151 " </rdf:Seq>\n"
152 " </Container:Directory>\n"
153 " </rdf:Description>\n"
154 " </rdf:RDF>\n"
155 "</x:xmpmeta>\n");
156 return s.detachAsData();
157}
158
159// Split an SkData into segments.
161 size_t segmentMaxDataSize) {
162 // Compute the total size of the header to a gainmap image segment (not including the 2 bytes
163 // for the segment size, which the encoder is responsible for writing).
164 constexpr size_t kGainmapHeaderSize = sizeof(kGainmapSig) + 2 * kGainmapMarkerIndexSize;
165
166 // Compute the payload size for each segment.
167 const size_t kGainmapPayloadSize = segmentMaxDataSize - kGainmapHeaderSize;
168
169 // Compute the number of segments we'll need.
170 const size_t segmentCount = (image->size() + kGainmapPayloadSize - 1) / kGainmapPayloadSize;
171 std::vector<sk_sp<SkData>> result;
172 result.reserve(segmentCount);
173
174 // Move |imageData| through |image| until it hits |imageDataEnd|.
175 const uint8_t* imageData = image->bytes();
176 const uint8_t* imageDataEnd = image->bytes() + image->size();
177 while (imageData < imageDataEnd) {
178 SkDynamicMemoryWStream segmentStream;
179
180 // Write the signature.
181 segmentStream.write(kGainmapSig, sizeof(kGainmapSig));
182
183 // Write the segment index as big-endian.
184 size_t segmentIndex = result.size() + 1;
185 uint8_t segmentIndexBytes[2] = {
186 static_cast<uint8_t>(segmentIndex / 256u),
187 static_cast<uint8_t>(segmentIndex % 256u),
188 };
189 segmentStream.write(segmentIndexBytes, sizeof(segmentIndexBytes));
190
191 // Write the segment count as big-endian.
192 uint8_t segmentCountBytes[2] = {
193 static_cast<uint8_t>(segmentCount / 256u),
194 static_cast<uint8_t>(segmentCount % 256u),
195 };
196 segmentStream.write(segmentCountBytes, sizeof(segmentCountBytes));
197
198 // Verify that our header size math is correct.
199 SkASSERT(segmentStream.bytesWritten() == kGainmapHeaderSize);
200
201 // Write the rest of the segment.
202 size_t bytesToWrite =
203 std::min(imageDataEnd - imageData, static_cast<intptr_t>(kGainmapPayloadSize));
204 segmentStream.write(imageData, bytesToWrite);
205 imageData += bytesToWrite;
206
207 // Verify that our data size math is correct.
208 if (segmentIndex == segmentCount) {
209 SkASSERT(segmentStream.bytesWritten() <= segmentMaxDataSize);
210 } else {
211 SkASSERT(segmentStream.bytesWritten() == segmentMaxDataSize);
212 }
213 result.push_back(segmentStream.detachAsData());
214 }
215
216 // Verify that our segment count math was correct.
217 SkASSERT(imageData == imageDataEnd);
218 SkASSERT(result.size() == segmentCount);
219 return result;
220}
221
224 SkData* xmpMetadata) {
225 SkJpegEncoder::Options optionsWithXmp = options;
226 optionsWithXmp.xmpMetadata = xmpMetadata;
227 SkDynamicMemoryWStream encodeStream;
228 auto encoder = SkJpegEncoder::Make(&encodeStream, pm, optionsWithXmp);
229 if (!encoder || !encoder->encodeRows(pm.height())) {
230 return nullptr;
231 }
232 return encodeStream.detachAsData();
233}
234
237 auto segmentParameters = mpParams.serialize();
238 const size_t mpParameterLength = kJpegSegmentParameterLengthSize + segmentParameters->size();
239 s.write8(0xFF);
240 s.write8(kMpfMarker);
241 s.write8(mpParameterLength / 256);
242 s.write8(mpParameterLength % 256);
243 s.write(segmentParameters->data(), segmentParameters->size());
244 return s.detachAsData();
245}
246
248 const SkPixmap& base,
249 const SkJpegEncoder::Options& baseOptions,
250 const SkPixmap& gainmap,
251 const SkJpegEncoder::Options& gainmapOptions,
252 const SkGainmapInfo& gainmapInfo) {
253 // Encode the gainmap image with the HDRGM XMP metadata.
254 sk_sp<SkData> gainmapData;
255 {
256 // We will include the HDRGM XMP metadata in the gainmap image.
257 auto hdrgmXmp = get_hdrgm_xmp_data(gainmapInfo);
258 gainmapData = encode_to_data(gainmap, gainmapOptions, hdrgmXmp.get());
259 if (!gainmapData) {
260 SkCodecPrintf("Failed to encode gainmap image.\n");
261 return false;
262 }
263 }
264
265 // Encode the base image with the Container XMP metadata.
266 sk_sp<SkData> baseData;
267 {
268 auto containerXmp = get_gcontainer_xmp_data(static_cast<int32_t>(gainmapData->size()));
269 baseData = encode_to_data(base, baseOptions, containerXmp.get());
270 if (!baseData) {
271 SkCodecPrintf("Failed to encode base image.\n");
272 return false;
273 }
274 }
275
276 // Combine them into an MPF.
277 const SkData* images[] = {
278 baseData.get(),
279 gainmapData.get(),
280 };
281 return MakeMPF(dst, images, 2);
282}
283
284bool SkJpegGainmapEncoder::MakeMPF(SkWStream* dst, const SkData** images, size_t imageCount) {
285 if (imageCount < 1) {
286 return true;
287 }
288
289 // Create a scan of the primary image.
290 SkJpegSegmentScanner primaryScan;
291 primaryScan.onBytes(images[0]->data(), images[0]->size());
292 if (!primaryScan.isDone()) {
293 SkCodecPrintf("Failed to scan encoded primary image header.\n");
294 return false;
295 }
296
297 // Copy the primary image up to its StartOfScan, then insert the MPF segment, then copy the rest
298 // of the primary image, and all other images.
299 size_t bytesRead = 0;
300 size_t bytesWritten = 0;
301 for (const auto& segment : primaryScan.getSegments()) {
302 // Write all ECD before this segment.
303 {
304 size_t ecdBytesToWrite = segment.offset - bytesRead;
305 if (!dst->write(images[0]->bytes() + bytesRead, ecdBytesToWrite)) {
306 SkCodecPrintf("Failed to write entropy coded data.\n");
307 return false;
308 }
309 bytesWritten += ecdBytesToWrite;
310 bytesRead = segment.offset;
311 }
312
313 // If this isn't a StartOfScan, write just the segment.
314 if (segment.marker != kJpegMarkerStartOfScan) {
315 const size_t bytesToWrite = kJpegMarkerCodeSize + segment.parameterLength;
316 if (!dst->write(images[0]->bytes() + bytesRead, bytesToWrite)) {
317 SkCodecPrintf("Failed to copy segment.\n");
318 return false;
319 }
320 bytesWritten += bytesToWrite;
321 bytesRead += bytesToWrite;
322 continue;
323 }
324
325 // We're now at the StartOfScan.
326 const size_t bytesRemaining = images[0]->size() - bytesRead;
327
328 // Compute the MPF offsets for the images.
330 {
331 mpParams.images.resize(imageCount);
332 const size_t mpSegmentSize = kJpegMarkerCodeSize + kJpegSegmentParameterLengthSize +
333 mpParams.serialize()->size();
334 mpParams.images[0].size =
335 static_cast<uint32_t>(bytesWritten + mpSegmentSize + bytesRemaining);
336 uint32_t offset =
337 static_cast<uint32_t>(bytesRemaining + mpSegmentSize - kJpegMarkerCodeSize -
339 for (size_t i = 1; i < imageCount; ++i) {
340 mpParams.images[i].dataOffset = offset;
341 mpParams.images[i].size = static_cast<uint32_t>(images[i]->size());
342 offset += mpParams.images[i].size;
343 }
344 }
345
346 // Write the MPF segment.
347 auto mpfSegment = get_mpf_segment(mpParams);
348 if (!dst->write(mpfSegment->data(), mpfSegment->size())) {
349 SkCodecPrintf("Failed to write MPF segment.\n");
350 return false;
351 }
352
353 // Write the rest of the primary file.
354 if (!dst->write(images[0]->bytes() + bytesRead, bytesRemaining)) {
355 SkCodecPrintf("Failed to write remainder of primary image.\n");
356 return false;
357 }
358 bytesRead += bytesRemaining;
359 SkASSERT(bytesRead == images[0]->size());
360 break;
361 }
362
363 // Write the remaining files.
364 for (size_t i = 1; i < imageCount; ++i) {
365 if (!dst->write(images[i]->data(), images[i]->size())) {
366 SkCodecPrintf("Failed to write auxiliary image.\n");
367 }
368 }
369 return true;
370}
const char * options
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkCodecPrintf(...)
Definition SkCodecPriv.h:23
static constexpr uint32_t kMpfMarker
static constexpr uint8_t kMpfSig[]
static constexpr uint32_t kGainmapMarkerIndexSize
static constexpr uint8_t kJpegMarkerStartOfScan
static constexpr size_t kJpegSegmentParameterLengthSize
static constexpr size_t kJpegMarkerCodeSize
static constexpr uint8_t kGainmapSig[]
static bool is_single_channel(SkColor4f c)
static sk_sp< SkData > get_mpf_segment(const SkJpegMultiPictureParameters &mpParams)
static sk_sp< SkData > get_gcontainer_xmp_data(size_t gainmapItemLength)
static sk_sp< SkData > encode_to_data(const SkPixmap &pm, const SkJpegEncoder::Options &options, SkData *xmpMetadata)
sk_sp< SkData > get_hdrgm_xmp_data(const SkGainmapInfo &gainmapInfo)
std::vector< sk_sp< SkData > > get_hdrgm_image_segments(sk_sp< SkData > image, size_t segmentMaxDataSize)
size_t size() const
Definition SkData.h:30
size_t bytesWritten() const override
Definition SkStream.cpp:526
bool write(const void *buffer, size_t size) override
Definition SkStream.cpp:535
sk_sp< SkData > detachAsData()
Definition SkStream.cpp:707
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
void onBytes(const void *data, size_t size)
int height() const
Definition SkPixmap.h:166
T * get() const
Definition SkRefCnt.h:303
sk_sp< SkImage > image
Definition examples.cpp:29
float SkScalar
Definition extension.cpp:12
struct MyStruct s
uint8_t value
GAsyncResult * result
std::array< MockImage, 3 > images
SK_API std::unique_ptr< SkEncoder > Make(SkWStream *dst, const SkPixmap &src, const Options &options)
Point offset
SkColor4f fGainmapRatioMax
SkColor4f fEpsilonSdr
SkColor4f fGainmapGamma
SkColor4f fGainmapRatioMin
BaseImageType fBaseImageType
float fDisplayRatioSdr
SkColor4f fEpsilonHdr
float fDisplayRatioHdr
const SkData * xmpMetadata
sk_sp< SkData > serialize() const