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