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