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