• 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/codec/SkAndroidCodec.h"
9 #include "include/codec/SkCodec.h"
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkImageEncoder.h"
14 #include "include/core/SkShader.h"
15 #include "include/core/SkSize.h"
16 #include "include/core/SkStream.h"
17 #include "include/core/SkTypes.h"
18 #include "include/encode/SkJpegEncoder.h"
19 #include "include/private/SkGainmapInfo.h"
20 #include "include/private/SkGainmapShader.h"
21 #include "include/private/SkJpegGainmapEncoder.h"
22 #include "src/codec/SkJpegCodec.h"
23 #include "src/codec/SkJpegConstants.h"
24 #include "src/codec/SkJpegMultiPicture.h"
25 #include "src/codec/SkJpegSegmentScan.h"
26 #include "src/codec/SkJpegSourceMgr.h"
27 #include "src/codec/SkJpegXmp.h"
28 #include "tests/Test.h"
29 #include "tools/Resources.h"
30 
31 #include <cstdint>
32 #include <cstring>
33 #include <memory>
34 #include <utility>
35 #include <vector>
36 
37 namespace {
38 
39 // A test stream to stress the different SkJpegSourceMgr sub-classes.
40 class TestStream : public SkStream {
41 public:
42     enum class Type {
43         kUnseekable,    // SkJpegUnseekableSourceMgr
44         kSeekable,      // SkJpegBufferedSourceMgr
45         kMemoryMapped,  // SkJpegMemorySourceMgr
46     };
TestStream(Type type,SkStream * stream)47     TestStream(Type type, SkStream* stream)
48             : fStream(stream)
49             , fSeekable(type != Type::kUnseekable)
50             , fMemoryMapped(type == Type::kMemoryMapped) {}
~TestStream()51     ~TestStream() override {}
52 
read(void * buffer,size_t size)53     size_t read(void* buffer, size_t size) override { return fStream->read(buffer, size); }
peek(void * buffer,size_t size) const54     size_t peek(void* buffer, size_t size) const override { return fStream->peek(buffer, size); }
isAtEnd() const55     bool isAtEnd() const override { return fStream->isAtEnd(); }
rewind()56     bool rewind() override {
57         if (!fSeekable) {
58             return false;
59         }
60         return fStream->rewind();
61     }
hasPosition() const62     bool hasPosition() const override {
63         if (!fSeekable) {
64             return false;
65         }
66         return fStream->hasPosition();
67     }
getPosition() const68     size_t getPosition() const override {
69         if (!fSeekable) {
70             return 0;
71         }
72         return fStream->hasPosition();
73     }
seek(size_t position)74     bool seek(size_t position) override {
75         if (!fSeekable) {
76             return 0;
77         }
78         return fStream->seek(position);
79     }
move(long offset)80     bool move(long offset) override {
81         if (!fSeekable) {
82             return 0;
83         }
84         return fStream->move(offset);
85     }
hasLength() const86     bool hasLength() const override {
87         if (!fMemoryMapped) {
88             return false;
89         }
90         return fStream->hasLength();
91     }
getLength() const92     size_t getLength() const override {
93         if (!fMemoryMapped) {
94             return 0;
95         }
96         return fStream->getLength();
97     }
getMemoryBase()98     const void* getMemoryBase() override {
99         if (!fMemoryMapped) {
100             return nullptr;
101         }
102         return fStream->getMemoryBase();
103     }
104 
105 private:
106     SkStream* const fStream;
107     bool fSeekable = false;
108     bool fMemoryMapped = false;
109 };
110 
111 }  // namespace
112 
DEF_TEST(Codec_jpegSegmentScan,r)113 DEF_TEST(Codec_jpegSegmentScan, r) {
114     const struct Rec {
115         const char* path;
116         size_t sosSegmentCount;
117         size_t eoiSegmentCount;
118         size_t testSegmentIndex;
119         uint8_t testSegmentMarker;
120         size_t testSegmentOffset;
121         uint16_t testSegmentParameterLength;
122     } recs[] = {
123             {"images/wide_gamut_yellow_224_224_64.jpeg", 11, 15, 10, 0xda, 9768, 12},
124             {"images/CMYK.jpg", 7, 8, 1, 0xee, 2, 14},
125             {"images/b78329453.jpeg", 10, 23, 3, 0xe2, 154, 540},
126             {"images/brickwork-texture.jpg", 8, 28, 12, 0xc4, 34183, 42},
127             {"images/brickwork_normal-map.jpg", 8, 28, 27, 0xd9, 180612, 0},
128             {"images/cmyk_yellow_224_224_32.jpg", 19, 23, 2, 0xed, 854, 2828},
129             {"images/color_wheel.jpg", 10, 11, 2, 0xdb, 20, 67},
130             {"images/cropped_mandrill.jpg", 10, 11, 4, 0xc0, 158, 17},
131             {"images/dog.jpg", 10, 11, 5, 0xc4, 177, 28},
132             {"images/ducky.jpg", 12, 13, 10, 0xc4, 3718, 181},
133             {"images/exif-orientation-2-ur.jpg", 11, 12, 2, 0xe1, 20, 130},
134             {"images/flutter_logo.jpg", 9, 27, 21, 0xda, 5731, 8},
135             {"images/grayscale.jpg", 6, 16, 9, 0xda, 327, 8},
136             {"images/icc-v2-gbr.jpg", 12, 25, 24, 0xd9, 43832, 0},
137             {"images/mandrill_512_q075.jpg", 10, 11, 7, 0xc4, 393, 31},
138             {"images/mandrill_cmyk.jpg", 19, 35, 16, 0xdd, 574336, 4},
139             {"images/mandrill_h1v1.jpg", 10, 11, 1, 0xe0, 2, 16},
140             {"images/mandrill_h2v1.jpg", 10, 11, 0, 0xd8, 0, 0},
141             {"images/randPixels.jpg", 10, 11, 6, 0xc4, 200, 30},
142             {"images/wide_gamut_yellow_224_224_64.jpeg", 11, 15, 10, 0xda, 9768, 12},
143     };
144 
145     for (const auto& rec : recs) {
146         auto stream = GetResourceAsStream(rec.path);
147         if (!stream) {
148             continue;
149         }
150 
151         // Scan all the way to EndOfImage.
152         auto sourceMgr = SkJpegSourceMgr::Make(stream.get());
153         const auto& segments = sourceMgr->getAllSegments();
154 
155         // Verify we got the expected number of segments at EndOfImage
156         REPORTER_ASSERT(r, rec.eoiSegmentCount == segments.size());
157 
158         // Verify we got the expected number of segments before StartOfScan
159         for (size_t i = 0; i < segments.size(); ++i) {
160             if (segments[i].marker == kJpegMarkerStartOfScan) {
161                 REPORTER_ASSERT(r, rec.sosSegmentCount == i + 1);
162                 break;
163             }
164         }
165 
166         // Verify the values for a randomly pre-selected segment index.
167         const auto& segment = segments[rec.testSegmentIndex];
168         REPORTER_ASSERT(r, rec.testSegmentMarker == segment.marker);
169         REPORTER_ASSERT(r, rec.testSegmentOffset == segment.offset);
170         REPORTER_ASSERT(r, rec.testSegmentParameterLength == segment.parameterLength);
171     }
172 }
173 
find_mp_params_segment(SkStream * stream,std::unique_ptr<SkJpegMultiPictureParameters> * outMpParams,SkJpegSegment * outMpParamsSegment)174 static bool find_mp_params_segment(SkStream* stream,
175                                    std::unique_ptr<SkJpegMultiPictureParameters>* outMpParams,
176                                    SkJpegSegment* outMpParamsSegment) {
177     auto sourceMgr = SkJpegSourceMgr::Make(stream);
178     for (const auto& segment : sourceMgr->getAllSegments()) {
179         if (segment.marker != kMpfMarker) {
180             continue;
181         }
182         auto parameterData = sourceMgr->getSegmentParameters(segment);
183         if (!parameterData) {
184             continue;
185         }
186         *outMpParams = SkJpegMultiPictureParameters::Make(parameterData);
187         if (*outMpParams) {
188             *outMpParamsSegment = segment;
189             return true;
190         }
191     }
192     return false;
193 }
194 
DEF_TEST(Codec_multiPictureParams,r)195 DEF_TEST(Codec_multiPictureParams, r) {
196     // Little-endian test.
197     {
198         const uint8_t bytes[] = {
199                 0x4d, 0x50, 0x46, 0x00, 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03,
200                 0x00, 0x00, 0xb0, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, 0x31, 0x30, 0x30,
201                 0x01, 0xb0, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
202                 0xb0, 0x07, 0x00, 0x20, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
203                 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x20, 0xcf, 0x49, 0x00, 0x00, 0x00, 0x00,
204                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xee, 0x28, 0x01, 0x00,
205                 0xf9, 0xb7, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
206         };
207         auto mpParams =
208                 SkJpegMultiPictureParameters::Make(SkData::MakeWithoutCopy(bytes, sizeof(bytes)));
209         REPORTER_ASSERT(r, mpParams);
210         REPORTER_ASSERT(r, mpParams->images.size() == 2);
211         REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
212         REPORTER_ASSERT(r, mpParams->images[0].size == 4837152);
213         REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 3979257);
214         REPORTER_ASSERT(r, mpParams->images[1].size == 76014);
215     }
216 
217     // Big-endian test.
218     {
219         const uint8_t bytes[] = {
220                 0x4d, 0x50, 0x46, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00,
221                 0x03, 0xb0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30,
222                 0xb0, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0xb0,
223                 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
224                 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x56, 0xda, 0x2f, 0x00, 0x00, 0x00,
225                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xc6, 0x01,
226                 0x00, 0x55, 0x7c, 0x1f, 0x00, 0x00, 0x00, 0x00,
227         };
228         auto mpParams =
229                 SkJpegMultiPictureParameters::Make(SkData::MakeWithoutCopy(bytes, sizeof(bytes)));
230         REPORTER_ASSERT(r, mpParams);
231         REPORTER_ASSERT(r, mpParams->images.size() == 2);
232         REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
233         REPORTER_ASSERT(r, mpParams->images[0].size == 5691951);
234         REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 5602335);
235         REPORTER_ASSERT(r, mpParams->images[1].size == 1361409);
236     }
237 
238     // Three entry test.
239     {
240         const uint8_t bytes[] = {
241                 0x4d, 0x50, 0x46, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00,
242                 0x03, 0xb0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30,
243                 0xb0, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0xb0,
244                 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
245                 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x1c, 0xc2, 0x00, 0x00, 0x00,
246                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0xb0,
247                 0x00, 0x1f, 0x12, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
248                 0x00, 0x96, 0x6b, 0x00, 0x22, 0x18, 0x9c, 0x00, 0x00, 0x00, 0x00,
249         };
250         auto mpParams =
251                 SkJpegMultiPictureParameters::Make(SkData::MakeWithoutCopy(bytes, sizeof(bytes)));
252         REPORTER_ASSERT(r, mpParams);
253         REPORTER_ASSERT(r, mpParams->images.size() == 3);
254         REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
255         REPORTER_ASSERT(r, mpParams->images[0].size == 2038978);
256         REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 2036460);
257         REPORTER_ASSERT(r, mpParams->images[1].size == 198064);
258         REPORTER_ASSERT(r, mpParams->images[2].dataOffset == 2234524);
259         REPORTER_ASSERT(r, mpParams->images[2].size == 38507);
260     }
261 }
262 
DEF_TEST(Codec_jpegMultiPicture,r)263 DEF_TEST(Codec_jpegMultiPicture, r) {
264     const char* path = "images/iphone_13_pro.jpeg";
265     auto stream = GetResourceAsStream(path);
266     REPORTER_ASSERT(r, stream);
267 
268     // Search and parse the MPF header.
269     std::unique_ptr<SkJpegMultiPictureParameters> mpParams;
270     SkJpegSegment mpParamsSegment;
271     REPORTER_ASSERT(r, find_mp_params_segment(stream.get(), &mpParams, &mpParamsSegment));
272 
273     // Verify that we get the same parameters when we re-serialize and de-serialize them
274     {
275         auto mpParamsSerialized = mpParams->serialize();
276         REPORTER_ASSERT(r, mpParamsSerialized);
277         auto mpParamsRoundTripped = SkJpegMultiPictureParameters::Make(mpParamsSerialized);
278         REPORTER_ASSERT(r, mpParamsRoundTripped);
279         REPORTER_ASSERT(r, mpParamsRoundTripped->images.size() == mpParams->images.size());
280         for (size_t i = 0; i < mpParamsRoundTripped->images.size(); ++i) {
281             REPORTER_ASSERT(r, mpParamsRoundTripped->images[i].size == mpParams->images[i].size);
282             REPORTER_ASSERT(
283                     r,
284                     mpParamsRoundTripped->images[i].dataOffset == mpParams->images[i].dataOffset);
285         }
286     }
287 
288     const struct Rec {
289         const TestStream::Type streamType;
290         const bool skipFirstImage;
291         const size_t bufferSize;
292     } recs[] = {
293             {TestStream::Type::kMemoryMapped, false, 1024},
294             {TestStream::Type::kMemoryMapped, true, 1024},
295             {TestStream::Type::kSeekable, false, 1024},
296             {TestStream::Type::kSeekable, true, 1024},
297             {TestStream::Type::kSeekable, false, 7},
298             {TestStream::Type::kSeekable, true, 13},
299             {TestStream::Type::kSeekable, true, 1024 * 1024 * 16},
300             {TestStream::Type::kUnseekable, false, 1024},
301             {TestStream::Type::kUnseekable, true, 1024},
302             {TestStream::Type::kUnseekable, false, 1},
303             {TestStream::Type::kUnseekable, true, 1},
304             {TestStream::Type::kUnseekable, false, 7},
305             {TestStream::Type::kUnseekable, true, 13},
306             {TestStream::Type::kUnseekable, false, 1024 * 1024 * 16},
307             {TestStream::Type::kUnseekable, true, 1024 * 1024 * 16},
308     };
309     for (const auto& rec : recs) {
310         stream->rewind();
311         TestStream testStream(rec.streamType, stream.get());
312         auto sourceMgr = SkJpegSourceMgr::Make(&testStream, rec.bufferSize);
313 
314         // Decode the images into bitmaps.
315         size_t numberOfImages = mpParams->images.size();
316         std::vector<SkBitmap> bitmaps(numberOfImages);
317         for (size_t i = 0; i < numberOfImages; ++i) {
318             if (i == 0) {
319                 REPORTER_ASSERT(r, mpParams->images[i].dataOffset == 0);
320                 continue;
321             }
322             if (i == 1 && rec.skipFirstImage) {
323                 continue;
324             }
325             auto imageData = sourceMgr->getSubsetData(
326                     SkJpegMultiPictureParameters::GetAbsoluteOffset(mpParams->images[i].dataOffset,
327                                                                     mpParamsSegment.offset),
328                     mpParams->images[i].size);
329             REPORTER_ASSERT(r, imageData);
330 
331             std::unique_ptr<SkCodec> codec =
332                     SkCodec::MakeFromStream(SkMemoryStream::Make(imageData));
333             REPORTER_ASSERT(r, codec);
334 
335             SkBitmap bm;
336             bm.allocPixels(codec->getInfo());
337             REPORTER_ASSERT(r,
338                             SkCodec::kSuccess ==
339                                     codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes()));
340             bitmaps[i] = bm;
341         }
342 
343         // Spot-check the image size and pixels.
344         if (!rec.skipFirstImage) {
345             REPORTER_ASSERT(r, bitmaps[1].dimensions() == SkISize::Make(1512, 2016));
346             REPORTER_ASSERT(r, bitmaps[1].getColor(0, 0) == 0xFF3B3B3B);
347             REPORTER_ASSERT(r, bitmaps[1].getColor(1511, 2015) == 0xFF101010);
348         }
349         REPORTER_ASSERT(r, bitmaps[2].dimensions() == SkISize::Make(576, 768));
350         REPORTER_ASSERT(r, bitmaps[2].getColor(0, 0) == 0xFF010101);
351         REPORTER_ASSERT(r, bitmaps[2].getColor(575, 767) == 0xFFB5B5B5);
352     }
353 }
354 
355 // Decode an image and its gainmap.
356 template <typename Reporter>
decode_all(Reporter & r,std::unique_ptr<SkStream> stream,SkBitmap & baseBitmap,SkBitmap & gainmapBitmap,SkGainmapInfo & gainmapInfo)357 void decode_all(Reporter& r,
358                 std::unique_ptr<SkStream> stream,
359                 SkBitmap& baseBitmap,
360                 SkBitmap& gainmapBitmap,
361                 SkGainmapInfo& gainmapInfo) {
362     // Decode the base bitmap.
363     SkCodec::Result result = SkCodec::kSuccess;
364     std::unique_ptr<SkCodec> baseCodec = SkJpegCodec::MakeFromStream(std::move(stream), &result);
365     REPORTER_ASSERT(r, baseCodec);
366     baseBitmap.allocPixels(baseCodec->getInfo());
367     REPORTER_ASSERT(r,
368                     SkCodec::kSuccess == baseCodec->getPixels(baseBitmap.info(),
369                                                               baseBitmap.getPixels(),
370                                                               baseBitmap.rowBytes()));
371     std::unique_ptr<SkAndroidCodec> androidCodec =
372             SkAndroidCodec::MakeFromCodec(std::move(baseCodec));
373     REPORTER_ASSERT(r, androidCodec);
374 
375     // Extract the gainmap info and stream.
376     std::unique_ptr<SkStream> gainmapStream;
377     REPORTER_ASSERT(r, androidCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream));
378     REPORTER_ASSERT(r, gainmapStream);
379 
380     // Decode the gainmap bitmap.
381     std::unique_ptr<SkCodec> gainmapCodec = SkCodec::MakeFromStream(std::move(gainmapStream));
382     REPORTER_ASSERT(r, gainmapCodec);
383     SkBitmap bm;
384     bm.allocPixels(gainmapCodec->getInfo());
385     gainmapBitmap.allocPixels(gainmapCodec->getInfo());
386     REPORTER_ASSERT(r,
387                     SkCodec::kSuccess == gainmapCodec->getPixels(gainmapBitmap.info(),
388                                                                  gainmapBitmap.getPixels(),
389                                                                  gainmapBitmap.rowBytes()));
390 }
391 
392 // Render an applied gainmap.
render_gainmap(const SkImageInfo & renderInfo,float renderHdrRatio,const SkBitmap & baseBitmap,const SkBitmap & gainmapBitmap,const SkGainmapInfo & gainmapInfo,int x,int y)393 SkBitmap render_gainmap(const SkImageInfo& renderInfo,
394                         float renderHdrRatio,
395                         const SkBitmap& baseBitmap,
396                         const SkBitmap& gainmapBitmap,
397                         const SkGainmapInfo& gainmapInfo,
398                         int x,
399                         int y) {
400     SkRect baseRect = SkRect::MakeXYWH(x, y, renderInfo.width(), renderInfo.height());
401 
402     float scaleX = gainmapBitmap.width() / static_cast<float>(baseBitmap.width());
403     float scaleY = gainmapBitmap.height() / static_cast<float>(baseBitmap.height());
404     SkRect gainmapRect = SkRect::MakeXYWH(baseRect.x() * scaleX,
405                                           baseRect.y() * scaleY,
406                                           baseRect.width() * scaleX,
407                                           baseRect.height() * scaleY);
408 
409     SkRect dstRect = SkRect::Make(renderInfo.dimensions());
410 
411     sk_sp<SkImage> baseImage = SkImage::MakeFromBitmap(baseBitmap);
412     sk_sp<SkImage> gainmapImage = SkImage::MakeFromBitmap(gainmapBitmap);
413     sk_sp<SkShader> shader = SkGainmapShader::Make(baseImage,
414                                                    baseRect,
415                                                    SkSamplingOptions(),
416                                                    gainmapImage,
417                                                    gainmapRect,
418                                                    SkSamplingOptions(),
419                                                    gainmapInfo,
420                                                    dstRect,
421                                                    renderHdrRatio,
422                                                    renderInfo.refColorSpace());
423 
424     SkBitmap result;
425     result.allocPixels(renderInfo);
426     result.eraseColor(SK_ColorTRANSPARENT);
427     SkCanvas canvas(result);
428 
429     SkPaint paint;
430     paint.setShader(shader);
431     canvas.drawRect(dstRect, paint);
432 
433     return result;
434 }
435 
DEF_TEST(AndroidCodec_xmpHdrgmAsFieldValue,r)436 DEF_TEST(AndroidCodec_xmpHdrgmAsFieldValue, r) {
437     // Expose HDRM values as fields. Also place the HDRGM namespace in the rdf:RDF node.
438     const char xmpData[] =
439             "http://ns.adobe.com/xap/1.0/\0"
440             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
441             "   <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
442             "            xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\">\n"
443             "      <rdf:Description rdf:about=\"\">\n"
444             "         <hdrgm:Version>1.0</hdrgm:Version>\n"
445             "         <hdrgm:GainMapMax>3</hdrgm:GainMapMax>\n"
446             "         <hdrgm:HDRCapacityMax>4</hdrgm:HDRCapacityMax>\n"
447             "      </rdf:Description>\n"
448             "   </rdf:RDF>\n"
449             "</x:xmpmeta>\n";
450 
451     std::vector<sk_sp<SkData>> app1Params;
452     app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
453 
454     auto xmp = SkJpegXmp::Make(app1Params);
455     REPORTER_ASSERT(r, xmp);
456 
457     SkGainmapInfo info;
458     REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
459     REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
460     REPORTER_ASSERT(r, info.fDisplayRatioHdr == 16.f);
461 }
462 
DEF_TEST(AndroidCodec_xmpHdrgmRequiresVersion,r)463 DEF_TEST(AndroidCodec_xmpHdrgmRequiresVersion, r) {
464     // Same as the above, except with Version being absent.
465     const char xmpData[] =
466             "http://ns.adobe.com/xap/1.0/\0"
467             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
468             "   <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
469             "            xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\">\n"
470             "      <rdf:Description rdf:about=\"\">\n"
471             "         <hdrgm:GainMapMax>3</hdrgm:GainMapMax>\n"
472             "         <hdrgm:HDRCapacityMax>4</hdrgm:HDRCapacityMax>\n"
473             "      </rdf:Description>\n"
474             "   </rdf:RDF>\n"
475             "</x:xmpmeta>\n";
476 
477     std::vector<sk_sp<SkData>> app1Params;
478     app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
479 
480     auto xmp = SkJpegXmp::Make(app1Params);
481     REPORTER_ASSERT(r, xmp);
482 
483     SkGainmapInfo info;
484     REPORTER_ASSERT(r, !xmp->getGainmapInfoHDRGM(&info));
485 }
486 
DEF_TEST(AndroidCodec_xmpHdrgmRequiresHdrCapacityMax,r)487 DEF_TEST(AndroidCodec_xmpHdrgmRequiresHdrCapacityMax, r) {
488     // Same as the above, except with HDRCapacityMax being absent.
489     const char xmpData[] =
490             "http://ns.adobe.com/xap/1.0/\0"
491             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
492             "   <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
493             "            xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\">\n"
494             "      <rdf:Description rdf:about=\"\">\n"
495             "         <hdrgm:Version>1.0</hdrgm:Version>\n"
496             "         <hdrgm:GainMapMax>3</hdrgm:GainMapMax>\n"
497             "      </rdf:Description>\n"
498             "   </rdf:RDF>\n"
499             "</x:xmpmeta>\n";
500 
501     std::vector<sk_sp<SkData>> app1Params;
502     app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
503 
504     auto xmp = SkJpegXmp::Make(app1Params);
505     REPORTER_ASSERT(r, xmp);
506 
507     SkGainmapInfo info;
508     // The version check works.
509     REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(nullptr));
510     // Populating the gainmap metadata doesn't.
511     REPORTER_ASSERT(r, !xmp->getGainmapInfoHDRGM(&info));
512 }
513 
DEF_TEST(AndroidCodec_xmpHdrgmAsDescriptionPropertyAttributes,r)514 DEF_TEST(AndroidCodec_xmpHdrgmAsDescriptionPropertyAttributes, r) {
515     // Expose HDRGM values as attributes on an rdf:Description node.
516     const char xmpData[] =
517             "http://ns.adobe.com/xap/1.0/\0"
518             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
519             "   <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
520             "      <rdf:Description rdf:about=\"\"\n"
521             "            xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\"\n"
522             "         hdrgm:Version=\"1.0\"\n"
523             "         hdrgm:GainMapMax=\"3\"\n"
524             "         hdrgm:HDRCapacityMax=\"4\"/>\n"
525             "   </rdf:RDF>\n"
526             "</x:xmpmeta>\n";
527 
528     std::vector<sk_sp<SkData>> app1Params;
529     app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
530 
531     auto xmp = SkJpegXmp::Make(app1Params);
532     REPORTER_ASSERT(r, xmp);
533 
534     SkGainmapInfo info;
535     REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
536     REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
537     REPORTER_ASSERT(r, info.fDisplayRatioHdr == 16.f);
538 }
539 
540 // Test mixed list and non-list entries.
DEF_TEST(AndroidCodec_xmpHdrgmList,r)541 DEF_TEST(AndroidCodec_xmpHdrgmList, r) {
542     const char xmpData[] =
543             "http://ns.adobe.com/xap/1.0/\0"
544             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\">\n"
545             "   <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
546             "            xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\">\n"
547             "      <rdf:Description rdf:about=\"\"\n"
548             "         hdrgm:Version=\"1.0\"\n"
549             "         hdrgm:HDRCapacityMax=\"4\"\n"
550             "         hdrgm:GainMapMin=\"2.0\"\n"
551             "         hdrgm:OffsetSDR=\"0.1\">\n"
552             "         <hdrgm:GainMapMax>\n"
553             "           <rdf:Seq>\n"
554             "             <rdf:li>3</rdf:li>\n"
555             "             <rdf:li>4</rdf:li>\n"
556             "             <rdf:li>5</rdf:li>\n"
557             "           </rdf:Seq>\n"
558             "         </hdrgm:GainMapMax>\n"
559             "         <hdrgm:Gamma>\n"
560             "           1.2\n"
561             "         </hdrgm:Gamma>\n"
562             "         <hdrgm:OffsetHDR>\n"
563             "           <rdf:Seq>\n"
564             "             <rdf:li>\n"
565             "               0.2\n"
566             "             </rdf:li>\n"
567             "             <rdf:li>\n"
568             "               0.3\n"
569             "             </rdf:li>\n"
570             "             <rdf:li>\n"
571             "               0.4\n"
572             "             </rdf:li>\n"
573             "           </rdf:Seq>\n"
574             "         </hdrgm:OffsetHDR>\n"
575             "      </rdf:Description>\n"
576             "   </rdf:RDF>\n"
577             "</x:xmpmeta>\n";
578 
579     std::vector<sk_sp<SkData>> app1Params;
580     app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
581 
582     auto xmp = SkJpegXmp::Make(app1Params);
583     REPORTER_ASSERT(r, xmp);
584 
585     SkGainmapInfo info;
586     REPORTER_ASSERT(r, xmp->getGainmapInfoHDRGM(&info));
587     REPORTER_ASSERT(r, info.fGainmapRatioMin.fR == 4.f);
588     REPORTER_ASSERT(r, info.fGainmapRatioMin.fG == 4.f);
589     REPORTER_ASSERT(r, info.fGainmapRatioMin.fB == 4.f);
590     REPORTER_ASSERT(r, info.fGainmapRatioMax.fR == 8.f);
591     REPORTER_ASSERT(r, info.fGainmapRatioMax.fG == 16.f);
592     REPORTER_ASSERT(r, info.fGainmapRatioMax.fB == 32.f);
593 
594     REPORTER_ASSERT(r, info.fGainmapGamma.fR == 1.f/1.2f);
595     REPORTER_ASSERT(r, info.fGainmapGamma.fG == 1.f/1.2f);
596     REPORTER_ASSERT(r, info.fGainmapGamma.fB == 1.f/1.2f);
597 
598     REPORTER_ASSERT(r, info.fEpsilonSdr.fR == 0.1f);
599     REPORTER_ASSERT(r, info.fEpsilonSdr.fG == 0.1f);
600     REPORTER_ASSERT(r, info.fEpsilonSdr.fB == 0.1f);
601 
602     REPORTER_ASSERT(r, info.fEpsilonHdr.fR == 0.2f);
603     REPORTER_ASSERT(r, info.fEpsilonHdr.fG == 0.3f);
604     REPORTER_ASSERT(r, info.fEpsilonHdr.fB == 0.4f);
605 }
606 
DEF_TEST(AndroidCodec_xmpContainerTypedNode,r)607 DEF_TEST(AndroidCodec_xmpContainerTypedNode, r) {
608     // Container and Item using a node of type Container:Item.
609     const char xmpData[] =
610             "http://ns.adobe.com/xap/1.0/\0"
611             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 5.5.0\">\n"
612             " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
613             "  <rdf:Description rdf:about=\"\"\n"
614             "    xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"\n"
615             "    xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\">\n"
616             "   <Container:Directory>\n"
617             "    <rdf:Seq>\n"
618             "     <rdf:li rdf:parseType=\"Resource\">\n"
619             "      <Container:Item>\n"
620             "       <Item:Mime>image/jpeg</Item:Mime>\n"
621             "       <Item:Semantic>Primary</Item:Semantic>\n"
622             "      </Container:Item>\n"
623             "     </rdf:li>\n"
624             "     <rdf:li rdf:parseType=\"Resource\">\n"
625             "      <Container:Item\n"
626             "         Item:Semantic=\"GainMap\"\n"
627             "         Item:Mime=\"image/jpeg\"\n"
628             "         Item:Length=\"49035\"/>\n"
629             "     </rdf:li>\n"
630             "    </rdf:Seq>\n"
631             "   </Container:Directory>\n"
632             "  </rdf:Description>\n"
633             " </rdf:RDF>\n"
634             "</x:xmpmeta>\n";
635     std::vector<sk_sp<SkData>> app1Params;
636     app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
637 
638     auto xmp = SkJpegXmp::Make(app1Params);
639     REPORTER_ASSERT(r, xmp);
640 
641     size_t offset = 999;
642     size_t size = 999;
643     REPORTER_ASSERT(r, xmp->getContainerGainmapLocation(&offset, &size));
644     REPORTER_ASSERT(r, size == 49035);
645 }
646 
DEF_TEST(AndroidCodec_xmpContainerTypedNodeRdfEquivalent,r)647 DEF_TEST(AndroidCodec_xmpContainerTypedNodeRdfEquivalent, r) {
648     // Container and Item using rdf:value and rdf:type pairs.
649     const char xmpData[] =
650             "http://ns.adobe.com/xap/1.0/\0"
651             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 5.5.0\">\n"
652             " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
653             "  <rdf:Description rdf:about=\"\"\n"
654             "    xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"\n"
655             "    xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\">\n"
656             "   <Container:Directory>\n"
657             "    <rdf:Seq>\n"
658             "     <rdf:li rdf:parseType=\"Resource\">\n"
659             "      <rdf:value rdf:parseType=\"Resource\">\n"
660             "       <Item:Mime>image/jpeg</Item:Mime>\n"
661             "       <Item:Semantic>Primary</Item:Semantic>\n"
662             "      </rdf:value>\n"
663             "      <rdf:type rdf:resource=\"Item\"/>\n"
664             "     </rdf:li>\n"
665             "     <rdf:li rdf:parseType=\"Resource\">\n"
666             "      <rdf:value rdf:parseType=\"Resource\">\n"
667             "       <Item:Semantic>GainMap</Item:Semantic>\n"
668             "       <Item:Mime>image/jpeg</Item:Mime>\n"
669             "       <Item:Length>49035</Item:Length>\n"
670             "      </rdf:value>\n"
671             "      <rdf:type rdf:resource=\"Item\"/>\n"
672             "     </rdf:li>\n"
673             "    </rdf:Seq>\n"
674             "   </Container:Directory>\n"
675             "  </rdf:Description>\n"
676             " </rdf:RDF>\n"
677             "</x:xmpmeta>\n";
678     std::vector<sk_sp<SkData>> app1Params;
679     app1Params.push_back(SkData::MakeWithoutCopy(xmpData, sizeof(xmpData) - 1));
680 
681     auto xmp = SkJpegXmp::Make(app1Params);
682     REPORTER_ASSERT(r, xmp);
683 
684     size_t offset = 999;
685     size_t size = 999;
686     REPORTER_ASSERT(r, xmp->getContainerGainmapLocation(&offset, &size));
687     REPORTER_ASSERT(r, size == 49035);
688 }
689 
690 // Render a single pixel of an applied gainmap and return it.
render_gainmap_pixel(float renderHdrRatio,const SkBitmap & baseBitmap,const SkBitmap & gainmapBitmap,const SkGainmapInfo & gainmapInfo,int x,int y)691 SkColor4f render_gainmap_pixel(float renderHdrRatio,
692                                const SkBitmap& baseBitmap,
693                                const SkBitmap& gainmapBitmap,
694                                const SkGainmapInfo& gainmapInfo,
695                                int x,
696                                int y) {
697     SkImageInfo testPixelInfo = SkImageInfo::Make(
698             1, 1, kRGBA_F16_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
699     SkBitmap testPixelBitmap = render_gainmap(
700             testPixelInfo, renderHdrRatio, baseBitmap, gainmapBitmap, gainmapInfo, x, y);
701     return testPixelBitmap.getColor4f(0, 0);
702 }
703 
approx_eq(float x,float y,float epsilon)704 static bool approx_eq(float x, float y, float epsilon) { return std::abs(x - y) < epsilon; }
705 
approx_eq_rgb(const SkColor4f & x,const SkColor4f & y,float epsilon)706 static bool approx_eq_rgb(const SkColor4f& x, const SkColor4f& y, float epsilon) {
707     return approx_eq(x.fR, y.fR, epsilon) && approx_eq(x.fG, y.fG, epsilon) &&
708            approx_eq(x.fB, y.fB, epsilon);
709 }
710 
DEF_TEST(AndroidCodec_jpegGainmapDecode,r)711 DEF_TEST(AndroidCodec_jpegGainmapDecode, r) {
712     const struct Rec {
713         const char* path;
714         SkISize dimensions;
715         SkColor originColor;
716         SkColor farCornerColor;
717         float logRatioMin;
718         float logRatioMax;
719         float hdrRatioMin;
720         float hdrRatioMax;
721         SkGainmapInfo::Type type;
722     } recs[] = {
723             {"images/iphone_13_pro.jpeg",
724              SkISize::Make(1512, 2016),
725              0xFF3B3B3B,
726              0xFF101010,
727              0.f,
728              1.f,
729              1.f,
730              2.71828f,
731              SkGainmapInfo::Type::kMultiPicture},
732             {"images/hdrgm.jpg",
733              SkISize::Make(188, 250),
734              0xFFE9E9E9,
735              0xFFAAAAAA,
736              -2.209409f,
737              2.209409f,
738              1.f,
739              9.110335f,
740              SkGainmapInfo::Type::kHDRGM},
741     };
742 
743     TestStream::Type kStreamTypes[] = {
744             TestStream::Type::kUnseekable,
745             TestStream::Type::kSeekable,
746             TestStream::Type::kMemoryMapped,
747     };
748     for (const auto& streamType : kStreamTypes) {
749         bool useFileStream = streamType != TestStream::Type::kMemoryMapped;
750         for (const auto& rec : recs) {
751             auto stream = GetResourceAsStream(rec.path, useFileStream);
752             REPORTER_ASSERT(r, stream);
753             auto testStream = std::make_unique<TestStream>(streamType, stream.get());
754 
755             SkBitmap baseBitmap;
756             SkBitmap gainmapBitmap;
757             SkGainmapInfo gainmapInfo;
758             decode_all(r, std::move(testStream), baseBitmap, gainmapBitmap, gainmapInfo);
759 
760             // Spot-check the image size and pixels.
761             REPORTER_ASSERT(r, gainmapBitmap.dimensions() == rec.dimensions);
762             REPORTER_ASSERT(r, gainmapBitmap.getColor(0, 0) == rec.originColor);
763             REPORTER_ASSERT(
764                     r,
765                     gainmapBitmap.getColor(rec.dimensions.fWidth - 1, rec.dimensions.fHeight - 1) ==
766                             rec.farCornerColor);
767 
768             // Verify the gainmap rendering parameters.
769             constexpr float kEpsilon = 1e-3f;
770             REPORTER_ASSERT(r, approx_eq(gainmapInfo.fLogRatioMin.fR, rec.logRatioMin, kEpsilon));
771             REPORTER_ASSERT(r, approx_eq(gainmapInfo.fLogRatioMin.fG, rec.logRatioMin, kEpsilon));
772             REPORTER_ASSERT(r, approx_eq(gainmapInfo.fLogRatioMin.fB, rec.logRatioMin, kEpsilon));
773 
774             REPORTER_ASSERT(r, approx_eq(gainmapInfo.fLogRatioMax.fR, rec.logRatioMax, kEpsilon));
775             REPORTER_ASSERT(r, approx_eq(gainmapInfo.fLogRatioMax.fG, rec.logRatioMax, kEpsilon));
776             REPORTER_ASSERT(r, approx_eq(gainmapInfo.fLogRatioMax.fB, rec.logRatioMax, kEpsilon));
777 
778             REPORTER_ASSERT(r, approx_eq(gainmapInfo.fHdrRatioMin, rec.hdrRatioMin, kEpsilon));
779             REPORTER_ASSERT(r, approx_eq(gainmapInfo.fHdrRatioMax, rec.hdrRatioMax, kEpsilon));
780 
781             REPORTER_ASSERT(r, gainmapInfo.fType == rec.type);
782         }
783     }
784 }
785 
DEF_TEST(AndroidCodec_jpegNoGainmap,r)786 DEF_TEST(AndroidCodec_jpegNoGainmap, r) {
787     // This test image has a large APP16 segment that will stress the various SkJpegSourceMgrs'
788     // data skipping paths.
789     const char* path = "images/icc-v2-gbr.jpg";
790 
791     TestStream::Type kStreamTypes[] = {
792             TestStream::Type::kUnseekable,
793             TestStream::Type::kSeekable,
794             TestStream::Type::kMemoryMapped,
795     };
796     for (const auto& streamType : kStreamTypes) {
797         bool useFileStream = streamType != TestStream::Type::kMemoryMapped;
798         auto stream = GetResourceAsStream(path, useFileStream);
799         REPORTER_ASSERT(r, stream);
800         auto testStream = std::make_unique<TestStream>(streamType, stream.get());
801 
802         // Decode the base bitmap.
803         SkCodec::Result result = SkCodec::kSuccess;
804         std::unique_ptr<SkCodec> baseCodec =
805                 SkJpegCodec::MakeFromStream(std::move(testStream), &result);
806         REPORTER_ASSERT(r, baseCodec);
807         SkBitmap baseBitmap;
808         baseBitmap.allocPixels(baseCodec->getInfo());
809         REPORTER_ASSERT(r,
810                         SkCodec::kSuccess == baseCodec->getPixels(baseBitmap.info(),
811                                                                   baseBitmap.getPixels(),
812                                                                   baseBitmap.rowBytes()));
813 
814         std::unique_ptr<SkAndroidCodec> androidCodec =
815                 SkAndroidCodec::MakeFromCodec(std::move(baseCodec));
816         REPORTER_ASSERT(r, androidCodec);
817 
818         // Try to extract the gainmap info and stream. It should fail.
819         SkGainmapInfo gainmapInfo;
820         std::unique_ptr<SkStream> gainmapStream;
821         REPORTER_ASSERT(r, !androidCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream));
822     }
823 }
824 
825 #ifdef SK_ENCODE_JPEG
826 
DEF_TEST(AndroidCodec_gainmapInfoEncode,r)827 DEF_TEST(AndroidCodec_gainmapInfoEncode, r) {
828     SkDynamicMemoryWStream encodeStream;
829     SkGainmapInfo gainmapInfo;
830 
831     SkBitmap baseBitmap;
832     baseBitmap.allocPixels(SkImageInfo::MakeN32Premul(8, 8));
833 
834     SkBitmap gainmapBitmap;
835     gainmapBitmap.allocPixels(SkImageInfo::MakeN32Premul(8, 8));
836 
837     gainmapInfo.fGainmapRatioMin.fR = 1.f;
838     gainmapInfo.fGainmapRatioMin.fG = 2.f;
839     gainmapInfo.fGainmapRatioMin.fB = 4.f;
840     gainmapInfo.fGainmapRatioMax.fR = 8.f;
841     gainmapInfo.fGainmapRatioMax.fG = 16.f;
842     gainmapInfo.fGainmapRatioMax.fB = 32.f;
843     gainmapInfo.fGainmapGamma.fR = 64.f;
844     gainmapInfo.fGainmapGamma.fG = 128.f;
845     gainmapInfo.fGainmapGamma.fB = 256.f;
846     gainmapInfo.fEpsilonSdr.fR = 1 / 10.f;
847     gainmapInfo.fEpsilonSdr.fG = 1 / 11.f;
848     gainmapInfo.fEpsilonSdr.fB = 1 / 12.f;
849     gainmapInfo.fEpsilonHdr.fR = 1 / 13.f;
850     gainmapInfo.fEpsilonHdr.fG = 1 / 14.f;
851     gainmapInfo.fEpsilonHdr.fB = 1 / 15.f;
852     gainmapInfo.fDisplayRatioSdr = 4.f;
853     gainmapInfo.fDisplayRatioHdr = 32.f;
854 
855     for (int i = 0; i < 2; ++i) {
856         // In the second iteration, change some of the lists to scalars.
857         if (i == 1) {
858             gainmapInfo.fGainmapRatioMax.fR = 32.f;
859             gainmapInfo.fGainmapRatioMax.fG = 32.f;
860             gainmapInfo.fGainmapRatioMax.fB = 32.f;
861             gainmapInfo.fEpsilonSdr.fR = 1 / 10.f;
862             gainmapInfo.fEpsilonSdr.fG = 1 / 10.f;
863             gainmapInfo.fEpsilonSdr.fB = 1 / 10.f;
864         }
865 
866         // Encode |gainmapInfo|.
867         bool encodeResult = SkJpegGainmapEncoder::EncodeJpegR(&encodeStream,
868                                                               baseBitmap.pixmap(),
869                                                               SkJpegEncoder::Options(),
870                                                               gainmapBitmap.pixmap(),
871                                                               SkJpegEncoder::Options(),
872                                                               gainmapInfo);
873         REPORTER_ASSERT(r, encodeResult);
874 
875         // Decode into |decodedGainmapInfo|.
876         SkGainmapInfo decodedGainmapInfo;
877         auto decodeStream = std::make_unique<SkMemoryStream>(encodeStream.detachAsData());
878         decode_all(r, std::move(decodeStream), baseBitmap, gainmapBitmap, decodedGainmapInfo);
879 
880         // Verify they are |gainmapInfo| matches |decodedGainmapInfo|.
881         REPORTER_ASSERT(r, gainmapInfo == decodedGainmapInfo);
882     }
883 }
884 
DEF_TEST(AndroidCodec_jpegGainmapTranscode,r)885 DEF_TEST(AndroidCodec_jpegGainmapTranscode, r) {
886     const char* path = "images/iphone_13_pro.jpeg";
887     SkBitmap baseBitmap[2];
888     SkBitmap gainmapBitmap[2];
889     SkGainmapInfo gainmapInfo[2];
890 
891     // Decode an MPF-based gainmap image.
892     decode_all(r, GetResourceAsStream(path), baseBitmap[0], gainmapBitmap[0], gainmapInfo[0]);
893 
894     constexpr float kEpsilon = 1e-2f;
895     for (size_t i = 0; i < 2; ++i) {
896         SkDynamicMemoryWStream encodeStream;
897         bool encodeResult = false;
898 
899         if (i == 0) {
900             // Transcode to JpegR.
901             encodeResult = SkJpegGainmapEncoder::EncodeJpegR(&encodeStream,
902                                                              baseBitmap[0].pixmap(),
903                                                              SkJpegEncoder::Options(),
904                                                              gainmapBitmap[0].pixmap(),
905                                                              SkJpegEncoder::Options(),
906                                                              gainmapInfo[0]);
907         } else {
908             // Transcode to HDRGM.
909             encodeResult = SkJpegGainmapEncoder::EncodeHDRGM(&encodeStream,
910                                                              baseBitmap[0].pixmap(),
911                                                              SkJpegEncoder::Options(),
912                                                              gainmapBitmap[0].pixmap(),
913                                                              SkJpegEncoder::Options(),
914                                                              gainmapInfo[0]);
915         }
916         REPORTER_ASSERT(r, encodeResult);
917         auto encodeData = encodeStream.detachAsData();
918 
919         // Decode the just-encoded image.
920         auto decodeStream = std::make_unique<SkMemoryStream>(encodeData);
921         decode_all(r, std::move(decodeStream), baseBitmap[1], gainmapBitmap[1], gainmapInfo[1]);
922 
923         // HDRGM will have the same rendering parameters.
924         REPORTER_ASSERT(
925                 r,
926                 approx_eq_rgb(gainmapInfo[0].fLogRatioMin, gainmapInfo[1].fLogRatioMin, kEpsilon));
927         REPORTER_ASSERT(
928                 r,
929                 approx_eq_rgb(gainmapInfo[0].fLogRatioMax, gainmapInfo[1].fLogRatioMax, kEpsilon));
930         REPORTER_ASSERT(
931                 r,
932                 approx_eq_rgb(
933                         gainmapInfo[0].fGainmapGamma, gainmapInfo[1].fGainmapGamma, kEpsilon));
934         REPORTER_ASSERT(
935                 r,
936                 approx_eq(gainmapInfo[0].fEpsilonSdr.fR, gainmapInfo[1].fEpsilonSdr.fR, kEpsilon));
937         REPORTER_ASSERT(
938                 r,
939                 approx_eq(gainmapInfo[0].fEpsilonHdr.fR, gainmapInfo[1].fEpsilonHdr.fR, kEpsilon));
940         REPORTER_ASSERT(
941                 r, approx_eq(gainmapInfo[0].fHdrRatioMin, gainmapInfo[1].fHdrRatioMin, kEpsilon));
942         REPORTER_ASSERT(
943                 r, approx_eq(gainmapInfo[0].fHdrRatioMax, gainmapInfo[1].fHdrRatioMax, kEpsilon));
944 
945 #ifdef SK_ENABLE_SKSL
946         // Render a few pixels and verify that they come out the same. Rendering requires SkSL.
947         const struct Rec {
948             int x;
949             int y;
950             float hdrRatio;
951             SkColor4f expectedColor;
952             SkColorType forcedColorType;
953         } recs[] = {
954                 {1446, 1603, 1.05f, {0.984375f, 1.004883f, 1.008789f, 1.f}, kUnknown_SkColorType},
955                 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kUnknown_SkColorType},
956                 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kGray_8_SkColorType},
957                 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kAlpha_8_SkColorType},
958                 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kR8_unorm_SkColorType},
959         };
960 
961         for (const auto& rec : recs) {
962             SkBitmap gainmapBitmap0;
963             SkASSERT(gainmapBitmap[0].colorType() == kGray_8_SkColorType);
964 
965             // Force various different single-channel formats, to ensure that they all work. Note
966             // that when the color type is forced to kAlpha_8_SkColorType, the shader will always
967             // read (0,0,0,1) if the alpha type is kOpaque_SkAlphaType.
968             if (rec.forcedColorType == kUnknown_SkColorType) {
969                 gainmapBitmap0 = gainmapBitmap[0];
970             } else {
971                 gainmapBitmap0.installPixels(gainmapBitmap[0]
972                                                      .info()
973                                                      .makeColorType(rec.forcedColorType)
974                                                      .makeAlphaType(kPremul_SkAlphaType),
975                                              gainmapBitmap[0].getPixels(),
976                                              gainmapBitmap[0].rowBytes());
977             }
978             SkColor4f p0 = render_gainmap_pixel(
979                     rec.hdrRatio, baseBitmap[0], gainmapBitmap0, gainmapInfo[0], rec.x, rec.y);
980             SkColor4f p1 = render_gainmap_pixel(
981                     rec.hdrRatio, baseBitmap[1], gainmapBitmap[1], gainmapInfo[1], rec.x, rec.y);
982 
983             REPORTER_ASSERT(r, approx_eq_rgb(p0, p1, kEpsilon));
984         }
985 #endif  // SK_ENABLE_SKSL
986     }
987 }
988 #endif  // SK_ENCODE_JPEG
989