• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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