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