xref: /aosp_15_r20/external/skia/src/encode/SkJpegGainmapEncoder.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 #include "include/core/SkBitmap.h"
11 #include "include/core/SkPixmap.h"
12 #include "include/core/SkStream.h"
13 #include "include/encode/SkEncoder.h"
14 #include "include/encode/SkJpegEncoder.h"
15 #include "include/private/SkGainmapInfo.h"
16 #include "src/codec/SkCodecPriv.h"
17 #include "src/codec/SkJpegConstants.h"
18 #include "src/codec/SkJpegMultiPicture.h"
19 #include "src/codec/SkJpegPriv.h"
20 #include "src/codec/SkJpegSegmentScan.h"
21 #include "src/codec/SkTiffUtility.h"
22 #include "src/core/SkStreamPriv.h"
23 #include "src/encode/SkJpegEncoderImpl.h"
24 
25 #include <vector>
26 
is_single_channel(SkColor4f c)27 static 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.
get_gainmap_image_xmp_metadata(const SkGainmapInfo & gainmapInfo)33 sk_sp<SkData> get_gainmap_image_xmp_metadata(const SkGainmapInfo& gainmapInfo) {
34     SkDynamicMemoryWStream s;
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) {
60         if (!is_single_channel(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) {
69         if (is_single_channel(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) {
105         case SkGainmapInfo::BaseImageType::kSDR:
106             s.writeText("        hdrgm:BaseRenditionIsHDR=\"False\">\n");
107             break;
108         case SkGainmapInfo::BaseImageType::kHDR:
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.
get_base_image_xmp_metadata(size_t gainmapItemLength)128 static sk_sp<SkData> get_base_image_xmp_metadata(size_t gainmapItemLength) {
129     SkDynamicMemoryWStream s;
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 
encode_to_data(const SkPixmap & pm,const SkJpegEncoder::Options & options,const SkJpegMetadataEncoder::SegmentList & metadataSegments)162 static sk_sp<SkData> encode_to_data(const SkPixmap& pm,
163                                     const SkJpegEncoder::Options& options,
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 
get_exif_params()173 static sk_sp<SkData> get_exif_params() {
174     SkDynamicMemoryWStream s;
175 
176     s.write(kExifSig, sizeof(kExifSig));
177     s.write8(0);
178 
179     s.write(SkTiff::kEndianBig, sizeof(SkTiff::kEndianBig));
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;
190         SkWStreamWriteU16BE(&s, kSubIFDOffsetTag);
191         SkWStreamWriteU16BE(&s, SkTiff::kTypeUnsignedLong);
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'};
207         SkWStreamWriteU16BE(&s, kVersionTag);
208         SkWStreamWriteU16BE(&s, SkTiff::kTypeUndefined);
209         SkWStreamWriteU32BE(&s, kVersionCount);
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 
get_mpf_segment(const SkJpegMultiPictureParameters & mpParams,size_t imageNumber)219 static sk_sp<SkData> get_mpf_segment(const SkJpegMultiPictureParameters& mpParams,
220                                      size_t imageNumber) {
221     SkDynamicMemoryWStream s;
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 
get_iso_gainmap_segment_params(sk_sp<SkData> data)232 static sk_sp<SkData> get_iso_gainmap_segment_params(sk_sp<SkData> data) {
233     SkDynamicMemoryWStream s;
234     s.write(kISOGainmapSig, sizeof(kISOGainmapSig));
235     s.write(data->data(), data->size());
236     return s.detachAsData();
237 }
238 
EncodeHDRGM(SkWStream * dst,const SkPixmap & base,const SkJpegEncoder::Options & baseOptions,const SkPixmap & gainmap,const SkJpegEncoder::Options & gainmapOptions,const SkGainmapInfo & gainmapInfo)239 bool SkJpegGainmapEncoder::EncodeHDRGM(SkWStream* dst,
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) {
262             SkJpegMetadataEncoder::AppendXMPStandard(
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) {
268             SkJpegMetadataEncoder::AppendICC(
269                     metadataSegments, gainmapOptions, gainmapInfo.fGainmapMathColorSpace.get());
270         }
271 
272         // Add the ISO 21946-1 metadata.
273         metadataSegments.emplace_back(kISOGainmapMarker,
274                                       get_iso_gainmap_segment_params(gainmapInfo.serialize()));
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.
298             SkJpegMultiPictureParameters mpParams(2);
299             size_t gainmapImageSize = gainmapData->size() + get_mpf_segment(mpParams, 1)->size();
300             SkJpegMetadataEncoder::AppendXMPStandard(
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(
310                 kISOGainmapMarker,
311                 get_iso_gainmap_segment_params(SkGainmapInfo::SerializeVersion()));
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.
mp_segment_offset(const SkData * image)330 static size_t mp_segment_offset(const SkData* image) {
331     // Scan the image until StartOfScan marker.
332     SkJpegSegmentScanner scan(kJpegMarkerStartOfScan);
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         }
349         auto params = SkJpegSegmentScanner::GetParameters(image, segment);
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 
MakeMPF(SkWStream * dst,const SkData ** images,size_t imageCount)362 bool 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();
384         mpParams.images[i].dataOffset = SkJpegMultiPictureParameters::GetImageDataOffset(
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 }
415