Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
JpegGainmapTest.cpp
Go to the documentation of this file.
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
15#include "include/core/SkSize.h"
27#include "tests/Test.h"
28#include "tools/Resources.h"
29
30#include <cstdint>
31#include <cstring>
32#include <memory>
33#include <utility>
34#include <vector>
35
36namespace {
37
38// A test stream to stress the different SkJpegSourceMgr sub-classes.
39class TestStream : public SkStream {
40public:
41 enum class Type {
42 kUnseekable, // SkJpegUnseekableSourceMgr
43 kSeekable, // SkJpegBufferedSourceMgr
44 kMemoryMapped, // SkJpegMemorySourceMgr
45 };
46 TestStream(Type type, SkStream* stream)
47 : fStream(stream)
48 , fSeekable(type != Type::kUnseekable)
49 , fMemoryMapped(type == Type::kMemoryMapped) {}
50 ~TestStream() override {}
51
52 size_t read(void* buffer, size_t size) override { return fStream->read(buffer, size); }
53 size_t peek(void* buffer, size_t size) const override { return fStream->peek(buffer, size); }
54 bool isAtEnd() const override { return fStream->isAtEnd(); }
55 bool rewind() override {
56 if (!fSeekable) {
57 return false;
58 }
59 return fStream->rewind();
60 }
61 bool hasPosition() const override {
62 if (!fSeekable) {
63 return false;
64 }
65 return fStream->hasPosition();
66 }
67 size_t getPosition() const override {
68 if (!fSeekable) {
69 return 0;
70 }
71 return fStream->hasPosition();
72 }
73 bool seek(size_t position) override {
74 if (!fSeekable) {
75 return 0;
76 }
77 return fStream->seek(position);
78 }
79 bool move(long offset) override {
80 if (!fSeekable) {
81 return 0;
82 }
83 return fStream->move(offset);
84 }
85 bool hasLength() const override {
86 if (!fMemoryMapped) {
87 return false;
88 }
89 return fStream->hasLength();
90 }
91 size_t getLength() const override {
92 if (!fMemoryMapped) {
93 return 0;
94 }
95 return fStream->getLength();
96 }
97 const void* getMemoryBase() override {
98 if (!fMemoryMapped) {
99 return nullptr;
100 }
101 return fStream->getMemoryBase();
102 }
103
104private:
105 SkStream* const fStream;
106 bool fSeekable = false;
107 bool fMemoryMapped = false;
108};
109
110} // namespace
111
112DEF_TEST(Codec_jpegSegmentScan, r) {
113 const struct Rec {
114 const char* path;
115 size_t sosSegmentCount;
116 size_t eoiSegmentCount;
117 size_t testSegmentIndex;
118 uint8_t testSegmentMarker;
119 size_t testSegmentOffset;
120 uint16_t testSegmentParameterLength;
121 } recs[] = {
122 {"images/wide_gamut_yellow_224_224_64.jpeg", 11, 15, 10, 0xda, 9768, 12},
123 {"images/CMYK.jpg", 7, 8, 1, 0xee, 2, 14},
124 {"images/b78329453.jpeg", 10, 23, 3, 0xe2, 154, 540},
125 {"images/brickwork-texture.jpg", 8, 28, 12, 0xc4, 34183, 42},
126 {"images/brickwork_normal-map.jpg", 8, 28, 27, 0xd9, 180612, 0},
127 {"images/cmyk_yellow_224_224_32.jpg", 19, 23, 2, 0xed, 854, 2828},
128 {"images/color_wheel.jpg", 10, 11, 2, 0xdb, 20, 67},
129 {"images/cropped_mandrill.jpg", 10, 11, 4, 0xc0, 158, 17},
130 {"images/dog.jpg", 10, 11, 5, 0xc4, 177, 28},
131 {"images/ducky.jpg", 12, 13, 10, 0xc4, 3718, 181},
132 {"images/exif-orientation-2-ur.jpg", 11, 12, 2, 0xe1, 20, 130},
133 {"images/flutter_logo.jpg", 9, 27, 21, 0xda, 5731, 8},
134 {"images/grayscale.jpg", 6, 16, 9, 0xda, 327, 8},
135 {"images/icc-v2-gbr.jpg", 12, 25, 24, 0xd9, 43832, 0},
136 {"images/mandrill_512_q075.jpg", 10, 11, 7, 0xc4, 393, 31},
137 {"images/mandrill_cmyk.jpg", 19, 35, 16, 0xdd, 574336, 4},
138 {"images/mandrill_h1v1.jpg", 10, 11, 1, 0xe0, 2, 16},
139 {"images/mandrill_h2v1.jpg", 10, 11, 0, 0xd8, 0, 0},
140 {"images/randPixels.jpg", 10, 11, 6, 0xc4, 200, 30},
141 {"images/wide_gamut_yellow_224_224_64.jpeg", 11, 15, 10, 0xda, 9768, 12},
142 };
143
144 for (const auto& rec : recs) {
145 auto stream = GetResourceAsStream(rec.path);
146 if (!stream) {
147 continue;
148 }
149
150 // Scan all the way to EndOfImage.
151 auto sourceMgr = SkJpegSourceMgr::Make(stream.get());
152 const auto& segments = sourceMgr->getAllSegments();
153
154 // Verify we got the expected number of segments at EndOfImage
155 REPORTER_ASSERT(r, rec.eoiSegmentCount == segments.size());
156
157 // Verify we got the expected number of segments before StartOfScan
158 for (size_t i = 0; i < segments.size(); ++i) {
159 if (segments[i].marker == kJpegMarkerStartOfScan) {
160 REPORTER_ASSERT(r, rec.sosSegmentCount == i + 1);
161 break;
162 }
163 }
164
165 // Verify the values for a randomly pre-selected segment index.
166 const auto& segment = segments[rec.testSegmentIndex];
167 REPORTER_ASSERT(r, rec.testSegmentMarker == segment.marker);
168 REPORTER_ASSERT(r, rec.testSegmentOffset == segment.offset);
169 REPORTER_ASSERT(r, rec.testSegmentParameterLength == segment.parameterLength);
170 }
171}
172
174 std::unique_ptr<SkJpegMultiPictureParameters>* outMpParams,
175 SkJpegSegment* outMpParamsSegment) {
176 auto sourceMgr = SkJpegSourceMgr::Make(stream);
177 for (const auto& segment : sourceMgr->getAllSegments()) {
178 if (segment.marker != kMpfMarker) {
179 continue;
180 }
181 auto parameterData = sourceMgr->getSegmentParameters(segment);
182 if (!parameterData) {
183 continue;
184 }
185 *outMpParams = SkJpegMultiPictureParameters::Make(parameterData);
186 if (*outMpParams) {
187 *outMpParamsSegment = segment;
188 return true;
189 }
190 }
191 return false;
192}
193
194DEF_TEST(Codec_multiPictureParams, r) {
195 // Little-endian test.
196 {
197 const uint8_t bytes[] = {
198 0x4d, 0x50, 0x46, 0x00, 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03,
199 0x00, 0x00, 0xb0, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, 0x31, 0x30, 0x30,
200 0x01, 0xb0, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
201 0xb0, 0x07, 0x00, 0x20, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
202 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x20, 0xcf, 0x49, 0x00, 0x00, 0x00, 0x00,
203 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xee, 0x28, 0x01, 0x00,
204 0xf9, 0xb7, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
205 };
206 auto mpParams =
208 REPORTER_ASSERT(r, mpParams);
209 REPORTER_ASSERT(r, mpParams->images.size() == 2);
210 REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
211 REPORTER_ASSERT(r, mpParams->images[0].size == 4837152);
212 REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 3979257);
213 REPORTER_ASSERT(r, mpParams->images[1].size == 76014);
214 }
215
216 // Big-endian test.
217 {
218 const uint8_t bytes[] = {
219 0x4d, 0x50, 0x46, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00,
220 0x03, 0xb0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30,
221 0xb0, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0xb0,
222 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
223 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x56, 0xda, 0x2f, 0x00, 0x00, 0x00,
224 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xc6, 0x01,
225 0x00, 0x55, 0x7c, 0x1f, 0x00, 0x00, 0x00, 0x00,
226 };
227 auto mpParams =
229 REPORTER_ASSERT(r, mpParams);
230 REPORTER_ASSERT(r, mpParams->images.size() == 2);
231 REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
232 REPORTER_ASSERT(r, mpParams->images[0].size == 5691951);
233 REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 5602335);
234 REPORTER_ASSERT(r, mpParams->images[1].size == 1361409);
235 }
236
237 // Three entry test.
238 {
239 const uint8_t bytes[] = {
240 0x4d, 0x50, 0x46, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00,
241 0x03, 0xb0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30,
242 0xb0, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0xb0,
243 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
244 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x1c, 0xc2, 0x00, 0x00, 0x00,
245 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0xb0,
246 0x00, 0x1f, 0x12, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
247 0x00, 0x96, 0x6b, 0x00, 0x22, 0x18, 0x9c, 0x00, 0x00, 0x00, 0x00,
248 };
249 auto mpParams =
251 REPORTER_ASSERT(r, mpParams);
252 REPORTER_ASSERT(r, mpParams->images.size() == 3);
253 REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
254 REPORTER_ASSERT(r, mpParams->images[0].size == 2038978);
255 REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 2036460);
256 REPORTER_ASSERT(r, mpParams->images[1].size == 198064);
257 REPORTER_ASSERT(r, mpParams->images[2].dataOffset == 2234524);
258 REPORTER_ASSERT(r, mpParams->images[2].size == 38507);
259 }
260
261 // Inserting various corrupt values.
262 {
263 const uint8_t bytes[] = {
264 0x4d, 0x50, 0x46, 0x00, // 0: {'M', 'P', 'F', 0} signature
265 0x4d, 0x4d, 0x00, 0x2a, // 4: {'M', 'M', 0, '*'} big-endian
266 0x00, 0x00, 0x00, 0x08, // 8: Index IFD offset
267 0x00, 0x03, // 12: Number of tags
268 0xb0, 0x00, // 14: Version tag
269 0x00, 0x07, // 16: Undefined type
270 0x00, 0x00, 0x00, 0x04, // 18: Size
271 0x30, 0x31, 0x30, 0x30, // 22: Value
272 0xb0, 0x01, // 26: Number of images
273 0x00, 0x04, // 28: Unsigned long type
274 0x00, 0x00, 0x00, 0x01, // 30: Count
275 0x00, 0x00, 0x00, 0x02, // 34: Value
276 0xb0, 0x02, // 38: MP entry tag
277 0x00, 0x07, // 40: Undefined type
278 0x00, 0x00, 0x00, 0x20, // 42: Size
279 0x00, 0x00, 0x00, 0x32, // 46: Value (offset)
280 0x00, 0x00, 0x00, 0x00, // 50: Next IFD offset (null)
281 0x20, 0x03, 0x00, 0x00, // 54: MP Entry 0 attributes
282 0x00, 0x56, 0xda, 0x2f, // 58: MP Entry 0 size (5691951)
283 0x00, 0x00, 0x00, 0x00, // 62: MP Entry 0 offset (0)
284 0x00, 0x00, 0x00, 0x00, // 66: MP Entry 0 dependencies
285 0x00, 0x00, 0x00, 0x00, // 70: MP Entry 1 attributes.
286 0x00, 0x14, 0xc6, 0x01, // 74: MP Entry 1 size (1361409)
287 0x00, 0x55, 0x7c, 0x1f, // 78: MP Entry 1 offset (5602335)
288 0x00, 0x00, 0x00, 0x00, // 82: MP Entry 1 dependencies
289 };
290
291 // Verify the offsets labeled above.
292 REPORTER_ASSERT(r, bytes[22] == 0x30);
293 REPORTER_ASSERT(r, bytes[26] == 0xb0);
294 REPORTER_ASSERT(r, bytes[38] == 0xb0);
295 REPORTER_ASSERT(r, bytes[54] == 0x20);
296 REPORTER_ASSERT(r, bytes[81] == 0x1f);
297
298 {
299 // Change the version to {'0', '1', '0', '1'}.
300 auto bytesInvalid = SkData::MakeWithCopy(bytes, sizeof(bytes));
301 REPORTER_ASSERT(r, bytes[25] == '0');
302 reinterpret_cast<uint8_t*>(bytesInvalid->writable_data())[25] = '1';
303 REPORTER_ASSERT(r, SkJpegMultiPictureParameters::Make(bytesInvalid) == nullptr);
304 }
305
306 {
307 // Change the number of images to be undefined type instead of unsigned long type.
308 auto bytesInvalid = SkData::MakeWithCopy(bytes, sizeof(bytes));
309 REPORTER_ASSERT(r, bytes[29] == 0x04);
310 reinterpret_cast<uint8_t*>(bytesInvalid->writable_data())[29] = 0x07;
311 REPORTER_ASSERT(r, SkJpegMultiPictureParameters::Make(bytesInvalid) == nullptr);
312 }
313
314 {
315 // Make the MP entries point off of the end of the buffer.
316 auto bytesInvalid = SkData::MakeWithCopy(bytes, sizeof(bytes));
317 REPORTER_ASSERT(r, bytes[49] == 0x32);
318 reinterpret_cast<uint8_t*>(bytesInvalid->writable_data())[49] = 0xFE;
319 REPORTER_ASSERT(r, SkJpegMultiPictureParameters::Make(bytesInvalid) == nullptr);
320 }
321
322 {
323 // Make the MP entries too small.
324 auto bytesInvalid = SkData::MakeWithCopy(bytes, sizeof(bytes));
325 REPORTER_ASSERT(r, bytes[45] == 0x20);
326 reinterpret_cast<uint8_t*>(bytesInvalid->writable_data())[45] = 0x1F;
327 REPORTER_ASSERT(r, SkJpegMultiPictureParameters::Make(bytesInvalid) == nullptr);
328 }
329 }
330}
331
332DEF_TEST(Codec_jpegMultiPicture, r) {
333 const char* path = "images/iphone_13_pro.jpeg";
334 auto stream = GetResourceAsStream(path);
335 REPORTER_ASSERT(r, stream);
336
337 // Search and parse the MPF header.
338 std::unique_ptr<SkJpegMultiPictureParameters> mpParams;
339 SkJpegSegment mpParamsSegment;
340 REPORTER_ASSERT(r, find_mp_params_segment(stream.get(), &mpParams, &mpParamsSegment));
341
342 // Verify that we get the same parameters when we re-serialize and de-serialize them
343 {
344 auto mpParamsSerialized = mpParams->serialize();
345 REPORTER_ASSERT(r, mpParamsSerialized);
346 auto mpParamsRoundTripped = SkJpegMultiPictureParameters::Make(mpParamsSerialized);
347 REPORTER_ASSERT(r, mpParamsRoundTripped);
348 REPORTER_ASSERT(r, mpParamsRoundTripped->images.size() == mpParams->images.size());
349 for (size_t i = 0; i < mpParamsRoundTripped->images.size(); ++i) {
350 REPORTER_ASSERT(r, mpParamsRoundTripped->images[i].size == mpParams->images[i].size);
352 r,
353 mpParamsRoundTripped->images[i].dataOffset == mpParams->images[i].dataOffset);
354 }
355 }
356
357 const struct Rec {
358 const TestStream::Type streamType;
359 const bool skipFirstImage;
360 const size_t bufferSize;
361 } recs[] = {
362 {TestStream::Type::kMemoryMapped, false, 1024},
363 {TestStream::Type::kMemoryMapped, true, 1024},
364 {TestStream::Type::kSeekable, false, 1024},
365 {TestStream::Type::kSeekable, true, 1024},
366 {TestStream::Type::kSeekable, false, 7},
367 {TestStream::Type::kSeekable, true, 13},
368 {TestStream::Type::kSeekable, true, 1024 * 1024 * 16},
369 {TestStream::Type::kUnseekable, false, 1024},
370 {TestStream::Type::kUnseekable, true, 1024},
371 {TestStream::Type::kUnseekable, false, 1},
372 {TestStream::Type::kUnseekable, true, 1},
373 {TestStream::Type::kUnseekable, false, 7},
374 {TestStream::Type::kUnseekable, true, 13},
375 {TestStream::Type::kUnseekable, false, 1024 * 1024 * 16},
376 {TestStream::Type::kUnseekable, true, 1024 * 1024 * 16},
377 };
378 for (const auto& rec : recs) {
379 stream->rewind();
380 TestStream testStream(rec.streamType, stream.get());
381 auto sourceMgr = SkJpegSourceMgr::Make(&testStream, rec.bufferSize);
382
383 // Decode the images into bitmaps.
384 size_t numberOfImages = mpParams->images.size();
385 std::vector<SkBitmap> bitmaps(numberOfImages);
386 for (size_t i = 0; i < numberOfImages; ++i) {
387 if (i == 0) {
388 REPORTER_ASSERT(r, mpParams->images[i].dataOffset == 0);
389 continue;
390 }
391 if (i == 1 && rec.skipFirstImage) {
392 continue;
393 }
394 auto imageData = sourceMgr->getSubsetData(
395 SkJpegMultiPictureParameters::GetAbsoluteOffset(mpParams->images[i].dataOffset,
396 mpParamsSegment.offset),
397 mpParams->images[i].size);
398 REPORTER_ASSERT(r, imageData);
399
400 std::unique_ptr<SkCodec> codec =
402 REPORTER_ASSERT(r, codec);
403
404 SkBitmap bm;
405 bm.allocPixels(codec->getInfo());
408 codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes()));
409 bitmaps[i] = bm;
410 }
411
412 // Spot-check the image size and pixels.
413 if (!rec.skipFirstImage) {
414 REPORTER_ASSERT(r, bitmaps[1].dimensions() == SkISize::Make(1512, 2016));
415 REPORTER_ASSERT(r, bitmaps[1].getColor(0, 0) == 0xFF3B3B3B);
416 REPORTER_ASSERT(r, bitmaps[1].getColor(1511, 2015) == 0xFF101010);
417 }
418 REPORTER_ASSERT(r, bitmaps[2].dimensions() == SkISize::Make(576, 768));
419 REPORTER_ASSERT(r, bitmaps[2].getColor(0, 0) == 0xFF010101);
420 REPORTER_ASSERT(r, bitmaps[2].getColor(575, 767) == 0xFFB5B5B5);
421 }
422}
423
424// Decode an image and its gainmap.
425template <typename Reporter>
426void decode_all(Reporter& r,
427 std::unique_ptr<SkStream> stream,
428 SkBitmap& baseBitmap,
429 SkBitmap& gainmapBitmap,
430 SkGainmapInfo& gainmapInfo) {
431 // Decode the base bitmap.
433 std::unique_ptr<SkCodec> baseCodec = SkJpegCodec::MakeFromStream(std::move(stream), &result);
434 REPORTER_ASSERT(r, baseCodec);
435 baseBitmap.allocPixels(baseCodec->getInfo());
437 SkCodec::kSuccess == baseCodec->getPixels(baseBitmap.info(),
438 baseBitmap.getPixels(),
439 baseBitmap.rowBytes()));
440 std::unique_ptr<SkAndroidCodec> androidCodec =
441 SkAndroidCodec::MakeFromCodec(std::move(baseCodec));
442 REPORTER_ASSERT(r, androidCodec);
443
444 // Extract the gainmap info and stream.
445 std::unique_ptr<SkStream> gainmapStream;
446 REPORTER_ASSERT(r, androidCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream));
447 REPORTER_ASSERT(r, gainmapStream);
448
449 // Decode the gainmap bitmap.
450 std::unique_ptr<SkCodec> gainmapCodec = SkCodec::MakeFromStream(std::move(gainmapStream));
451 REPORTER_ASSERT(r, gainmapCodec);
452 SkBitmap bm;
453 bm.allocPixels(gainmapCodec->getInfo());
454 gainmapBitmap.allocPixels(gainmapCodec->getInfo());
456 SkCodec::kSuccess == gainmapCodec->getPixels(gainmapBitmap.info(),
457 gainmapBitmap.getPixels(),
458 gainmapBitmap.rowBytes()));
459}
460
461static bool approx_eq(float x, float y, float epsilon) { return std::abs(x - y) < epsilon; }
462
463DEF_TEST(AndroidCodec_jpegGainmapDecode, r) {
464 const struct Rec {
465 const char* path;
466 SkISize dimensions;
467 SkColor originColor;
468 SkColor farCornerColor;
469 float ratioMin;
470 float ratioMax;
471 float hdrRatioMin;
472 float hdrRatioMax;
473 } recs[] = {
474 {"images/iphone_13_pro.jpeg",
475 SkISize::Make(1512, 2016),
476 0xFF3B3B3B,
477 0xFF101010,
478 std::exp(0.f),
479 std::exp(1.f),
480 1.f,
481 2.71828f},
482 {"images/hdrgm.jpg",
483 SkISize::Make(188, 250),
484 0xFFE9E9E9,
485 0xFFAAAAAA,
486 std::exp(-2.209409f),
487 std::exp(2.209409f),
488 1.f,
489 9.110335f},
490 };
491
492 TestStream::Type kStreamTypes[] = {
493 TestStream::Type::kUnseekable,
494 TestStream::Type::kSeekable,
495 TestStream::Type::kMemoryMapped,
496 };
497 for (const auto& streamType : kStreamTypes) {
498 bool useFileStream = streamType != TestStream::Type::kMemoryMapped;
499 for (const auto& rec : recs) {
500 auto stream = GetResourceAsStream(rec.path, useFileStream);
501 REPORTER_ASSERT(r, stream);
502 auto testStream = std::make_unique<TestStream>(streamType, stream.get());
503
504 SkBitmap baseBitmap;
505 SkBitmap gainmapBitmap;
506 SkGainmapInfo gainmapInfo;
507 decode_all(r, std::move(testStream), baseBitmap, gainmapBitmap, gainmapInfo);
508
509 // Spot-check the image size and pixels.
510 REPORTER_ASSERT(r, gainmapBitmap.dimensions() == rec.dimensions);
511 REPORTER_ASSERT(r, gainmapBitmap.getColor(0, 0) == rec.originColor);
513 r,
514 gainmapBitmap.getColor(rec.dimensions.fWidth - 1, rec.dimensions.fHeight - 1) ==
515 rec.farCornerColor);
516
517 // Verify the gainmap rendering parameters.
518 constexpr float kEpsilon = 1e-3f;
519 REPORTER_ASSERT(r, approx_eq(gainmapInfo.fGainmapRatioMin.fR, rec.ratioMin, kEpsilon));
520 REPORTER_ASSERT(r, approx_eq(gainmapInfo.fGainmapRatioMin.fG, rec.ratioMin, kEpsilon));
521 REPORTER_ASSERT(r, approx_eq(gainmapInfo.fGainmapRatioMin.fB, rec.ratioMin, kEpsilon));
522
523 REPORTER_ASSERT(r, approx_eq(gainmapInfo.fGainmapRatioMax.fR, rec.ratioMax, kEpsilon));
524 REPORTER_ASSERT(r, approx_eq(gainmapInfo.fGainmapRatioMax.fG, rec.ratioMax, kEpsilon));
525 REPORTER_ASSERT(r, approx_eq(gainmapInfo.fGainmapRatioMax.fB, rec.ratioMax, kEpsilon));
526
527 REPORTER_ASSERT(r, approx_eq(gainmapInfo.fDisplayRatioSdr, rec.hdrRatioMin, kEpsilon));
528 REPORTER_ASSERT(r, approx_eq(gainmapInfo.fDisplayRatioHdr, rec.hdrRatioMax, kEpsilon));
529 }
530 }
531}
532
533DEF_TEST(AndroidCodec_jpegNoGainmap, r) {
534 // This test image has a large APP16 segment that will stress the various SkJpegSourceMgrs'
535 // data skipping paths.
536 const char* path = "images/icc-v2-gbr.jpg";
537
538 TestStream::Type kStreamTypes[] = {
539 TestStream::Type::kUnseekable,
540 TestStream::Type::kSeekable,
541 TestStream::Type::kMemoryMapped,
542 };
543 for (const auto& streamType : kStreamTypes) {
544 bool useFileStream = streamType != TestStream::Type::kMemoryMapped;
545 auto stream = GetResourceAsStream(path, useFileStream);
546 REPORTER_ASSERT(r, stream);
547 auto testStream = std::make_unique<TestStream>(streamType, stream.get());
548
549 // Decode the base bitmap.
551 std::unique_ptr<SkCodec> baseCodec =
552 SkJpegCodec::MakeFromStream(std::move(testStream), &result);
553 REPORTER_ASSERT(r, baseCodec);
554 SkBitmap baseBitmap;
555 baseBitmap.allocPixels(baseCodec->getInfo());
557 SkCodec::kSuccess == baseCodec->getPixels(baseBitmap.info(),
558 baseBitmap.getPixels(),
559 baseBitmap.rowBytes()));
560
561 std::unique_ptr<SkAndroidCodec> androidCodec =
562 SkAndroidCodec::MakeFromCodec(std::move(baseCodec));
563 REPORTER_ASSERT(r, androidCodec);
564
565 // Try to extract the gainmap info and stream. It should fail.
566 SkGainmapInfo gainmapInfo;
567 std::unique_ptr<SkStream> gainmapStream;
568 REPORTER_ASSERT(r, !androidCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream));
569 }
570}
571
572#if !defined(SK_ENABLE_NDK_IMAGES)
573
574static bool approx_eq_rgb(const SkColor4f& x, const SkColor4f& y, float epsilon) {
575 return approx_eq(x.fR, y.fR, epsilon) && approx_eq(x.fG, y.fG, epsilon) &&
576 approx_eq(x.fB, y.fB, epsilon);
577}
578
579DEF_TEST(AndroidCodec_gainmapInfoEncode, r) {
580 SkDynamicMemoryWStream encodeStream;
581 SkGainmapInfo gainmapInfo;
582
583 SkBitmap baseBitmap;
584 baseBitmap.allocPixels(SkImageInfo::MakeN32Premul(8, 8));
585
586 SkBitmap gainmapBitmap;
587 gainmapBitmap.allocPixels(SkImageInfo::MakeN32Premul(8, 8));
588
589 gainmapInfo.fGainmapRatioMin.fR = 1.f;
590 gainmapInfo.fGainmapRatioMin.fG = 2.f;
591 gainmapInfo.fGainmapRatioMin.fB = 4.f;
592 gainmapInfo.fGainmapRatioMax.fR = 8.f;
593 gainmapInfo.fGainmapRatioMax.fG = 16.f;
594 gainmapInfo.fGainmapRatioMax.fB = 32.f;
595 gainmapInfo.fGainmapGamma.fR = 64.f;
596 gainmapInfo.fGainmapGamma.fG = 128.f;
597 gainmapInfo.fGainmapGamma.fB = 256.f;
598 gainmapInfo.fEpsilonSdr.fR = 1 / 10.f;
599 gainmapInfo.fEpsilonSdr.fG = 1 / 11.f;
600 gainmapInfo.fEpsilonSdr.fB = 1 / 12.f;
601 gainmapInfo.fEpsilonHdr.fR = 1 / 13.f;
602 gainmapInfo.fEpsilonHdr.fG = 1 / 14.f;
603 gainmapInfo.fEpsilonHdr.fB = 1 / 15.f;
604 gainmapInfo.fDisplayRatioSdr = 4.f;
605 gainmapInfo.fDisplayRatioHdr = 32.f;
606
607 for (int i = 0; i < 2; ++i) {
608 // In the second iteration, change some of the lists to scalars.
609 if (i == 1) {
610 gainmapInfo.fGainmapRatioMax.fR = 32.f;
611 gainmapInfo.fGainmapRatioMax.fG = 32.f;
612 gainmapInfo.fGainmapRatioMax.fB = 32.f;
613 gainmapInfo.fEpsilonSdr.fR = 1 / 10.f;
614 gainmapInfo.fEpsilonSdr.fG = 1 / 10.f;
615 gainmapInfo.fEpsilonSdr.fB = 1 / 10.f;
616 }
617
618 // Encode |gainmapInfo|.
619 bool encodeResult = SkJpegGainmapEncoder::EncodeHDRGM(&encodeStream,
620 baseBitmap.pixmap(),
622 gainmapBitmap.pixmap(),
624 gainmapInfo);
625 REPORTER_ASSERT(r, encodeResult);
626
627 // Decode into |decodedGainmapInfo|.
628 SkGainmapInfo decodedGainmapInfo;
629 auto decodeStream = std::make_unique<SkMemoryStream>(encodeStream.detachAsData());
630 decode_all(r, std::move(decodeStream), baseBitmap, gainmapBitmap, decodedGainmapInfo);
631
632 // Verify they are |gainmapInfo| matches |decodedGainmapInfo|.
633 REPORTER_ASSERT(r, gainmapInfo == decodedGainmapInfo);
634 }
635}
636
637// Render an applied gainmap.
638static SkBitmap render_gainmap(const SkImageInfo& renderInfo,
639 float renderHdrRatio,
640 const SkBitmap& baseBitmap,
641 const SkBitmap& gainmapBitmap,
642 const SkGainmapInfo& gainmapInfo,
643 int x,
644 int y) {
645 SkRect baseRect = SkRect::MakeXYWH(x, y, renderInfo.width(), renderInfo.height());
646
647 float scaleX = gainmapBitmap.width() / static_cast<float>(baseBitmap.width());
648 float scaleY = gainmapBitmap.height() / static_cast<float>(baseBitmap.height());
649 SkRect gainmapRect = SkRect::MakeXYWH(baseRect.x() * scaleX,
650 baseRect.y() * scaleY,
651 baseRect.width() * scaleX,
652 baseRect.height() * scaleY);
653
654 SkRect dstRect = SkRect::Make(renderInfo.dimensions());
655
656 sk_sp<SkImage> baseImage = SkImages::RasterFromBitmap(baseBitmap);
657 sk_sp<SkImage> gainmapImage = SkImages::RasterFromBitmap(gainmapBitmap);
658 sk_sp<SkShader> shader = SkGainmapShader::Make(baseImage,
659 baseRect,
661 gainmapImage,
662 gainmapRect,
664 gainmapInfo,
665 dstRect,
666 renderHdrRatio,
667 renderInfo.refColorSpace());
668
670 result.allocPixels(renderInfo);
671 result.eraseColor(SK_ColorTRANSPARENT);
672 SkCanvas canvas(result);
673
675 paint.setShader(shader);
676 canvas.drawRect(dstRect, paint);
677
678 return result;
679}
680
681// Render a single pixel of an applied gainmap and return it.
682static SkColor4f render_gainmap_pixel(float renderHdrRatio,
683 const SkBitmap& baseBitmap,
684 const SkBitmap& gainmapBitmap,
685 const SkGainmapInfo& gainmapInfo,
686 int x,
687 int y) {
688 SkImageInfo testPixelInfo = SkImageInfo::Make(
689 /*width=*/1,
690 /*height=*/1,
694 SkBitmap testPixelBitmap = render_gainmap(
695 testPixelInfo, renderHdrRatio, baseBitmap, gainmapBitmap, gainmapInfo, x, y);
696 return testPixelBitmap.getColor4f(0, 0);
697}
698
699DEF_TEST(AndroidCodec_jpegGainmapTranscode, r) {
700 const char* path = "images/iphone_13_pro.jpeg";
701 SkBitmap baseBitmap[2];
702 SkBitmap gainmapBitmap[2];
703 SkGainmapInfo gainmapInfo[2];
704
705 // Decode an MPF-based gainmap image.
706 decode_all(r, GetResourceAsStream(path), baseBitmap[0], gainmapBitmap[0], gainmapInfo[0]);
707
708 constexpr float kEpsilon = 1e-2f;
709 {
710 SkDynamicMemoryWStream encodeStream;
711
712 // Transcode to UltraHDR.
713 bool encodeResult = SkJpegGainmapEncoder::EncodeHDRGM(&encodeStream,
714 baseBitmap[0].pixmap(),
716 gainmapBitmap[0].pixmap(),
718 gainmapInfo[0]);
719 REPORTER_ASSERT(r, encodeResult);
720 auto encodeData = encodeStream.detachAsData();
721
722 // Decode the just-encoded image.
723 auto decodeStream = std::make_unique<SkMemoryStream>(encodeData);
724 decode_all(r, std::move(decodeStream), baseBitmap[1], gainmapBitmap[1], gainmapInfo[1]);
725
726 // HDRGM will have the same rendering parameters.
728 r,
730 gainmapInfo[0].fGainmapRatioMin, gainmapInfo[1].fGainmapRatioMin,kEpsilon));
732 r,
734 gainmapInfo[0].fGainmapRatioMax, gainmapInfo[1].fGainmapRatioMax, kEpsilon));
736 r,
738 gainmapInfo[0].fGainmapGamma, gainmapInfo[1].fGainmapGamma, kEpsilon));
740 r,
741 approx_eq(gainmapInfo[0].fEpsilonSdr.fR, gainmapInfo[1].fEpsilonSdr.fR, kEpsilon));
743 r,
744 approx_eq(gainmapInfo[0].fEpsilonHdr.fR, gainmapInfo[1].fEpsilonHdr.fR, kEpsilon));
746 r,
747 approx_eq(
748 gainmapInfo[0].fDisplayRatioSdr,
749 gainmapInfo[1].fDisplayRatioSdr,
750 kEpsilon));
752 r,
753 approx_eq(
754 gainmapInfo[0].fDisplayRatioHdr,
755 gainmapInfo[1].fDisplayRatioHdr,
756 kEpsilon));
757
758 // Render a few pixels and verify that they come out the same. Rendering requires SkSL.
759 const struct Rec {
760 int x;
761 int y;
762 float hdrRatio;
763 SkColor4f expectedColor;
764 SkColorType forcedColorType;
765 } recs[] = {
766 {1446, 1603, 1.05f, {0.984375f, 1.004883f, 1.008789f, 1.f}, kUnknown_SkColorType},
767 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kUnknown_SkColorType},
768 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kGray_8_SkColorType},
769 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kAlpha_8_SkColorType},
770 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kR8_unorm_SkColorType},
771 };
772
773 for (const auto& rec : recs) {
774 SkBitmap gainmapBitmap0;
775 SkASSERT(gainmapBitmap[0].colorType() == kGray_8_SkColorType);
776
777 // Force various different single-channel formats, to ensure that they all work. Note
778 // that when the color type is forced to kAlpha_8_SkColorType, the shader will always
779 // read (0,0,0,1) if the alpha type is kOpaque_SkAlphaType.
780 if (rec.forcedColorType == kUnknown_SkColorType) {
781 gainmapBitmap0 = gainmapBitmap[0];
782 } else {
783 gainmapBitmap0.installPixels(gainmapBitmap[0]
784 .info()
785 .makeColorType(rec.forcedColorType)
786 .makeAlphaType(kPremul_SkAlphaType),
787 gainmapBitmap[0].getPixels(),
788 gainmapBitmap[0].rowBytes());
789 }
791 rec.hdrRatio, baseBitmap[0], gainmapBitmap0, gainmapInfo[0], rec.x, rec.y);
793 rec.hdrRatio, baseBitmap[1], gainmapBitmap[1], gainmapInfo[1], rec.x, rec.y);
794
796 }
797 }
798}
799#endif // !defined(SK_ENABLE_NDK_IMAGES)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
void decode_all(Reporter &r, std::unique_ptr< SkStream > stream, SkBitmap &baseBitmap, SkBitmap &gainmapBitmap, SkGainmapInfo &gainmapInfo)
static SkBitmap render_gainmap(const SkImageInfo &renderInfo, float renderHdrRatio, const SkBitmap &baseBitmap, const SkBitmap &gainmapBitmap, const SkGainmapInfo &gainmapInfo, int x, int y)
static bool find_mp_params_segment(SkStream *stream, std::unique_ptr< SkJpegMultiPictureParameters > *outMpParams, SkJpegSegment *outMpParamsSegment)
static bool approx_eq(float x, float y, float epsilon)
static bool approx_eq_rgb(const SkColor4f &x, const SkColor4f &y, float epsilon)
static SkColor4f render_gainmap_pixel(float renderHdrRatio, const SkBitmap &baseBitmap, const SkBitmap &gainmapBitmap, const SkGainmapInfo &gainmapInfo, int x, int y)
static const char marker[]
std::unique_ptr< SkStreamAsset > GetResourceAsStream(const char *resource, bool useFileStream)
Definition Resources.cpp:31
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
#define SkASSERT(cond)
Definition SkAssert.h:116
SkColorType
Definition SkColorType.h:19
@ kR8_unorm_SkColorType
Definition SkColorType.h:54
@ kRGBA_F16_SkColorType
pixel with half floats for red, green, blue, alpha;
Definition SkColorType.h:38
@ kAlpha_8_SkColorType
pixel with alpha in 8-bit byte
Definition SkColorType.h:21
@ kGray_8_SkColorType
pixel with grayscale level in 8-bit byte
Definition SkColorType.h:35
@ kUnknown_SkColorType
uninitialized
Definition SkColorType.h:20
uint32_t SkColor
Definition SkColor.h:37
constexpr SkColor SK_ColorTRANSPARENT
Definition SkColor.h:99
static constexpr double kEpsilon
static SkColorType colorType(AImageDecoder *decoder, const AImageDecoderHeaderInfo *headerInfo)
static constexpr uint32_t kMpfMarker
static constexpr uint8_t kJpegMarkerStartOfScan
#define DEF_TEST(name, reporter)
Definition Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
static std::unique_ptr< SkAndroidCodec > MakeFromCodec(std::unique_ptr< SkCodec >)
void allocPixels(const SkImageInfo &info, size_t rowBytes)
Definition SkBitmap.cpp:258
bool installPixels(const SkImageInfo &info, void *pixels, size_t rowBytes, void(*releaseProc)(void *addr, void *context), void *context)
Definition SkBitmap.cpp:323
SkISize dimensions() const
Definition SkBitmap.h:388
SkColor getColor(int x, int y) const
Definition SkBitmap.h:874
int width() const
Definition SkBitmap.h:149
const SkPixmap & pixmap() const
Definition SkBitmap.h:133
SkColor4f getColor4f(int x, int y) const
Definition SkBitmap.h:893
size_t rowBytes() const
Definition SkBitmap.h:238
void * getPixels() const
Definition SkBitmap.h:283
const SkImageInfo & info() const
Definition SkBitmap.h:139
int height() const
Definition SkBitmap.h:158
void drawRect(const SkRect &rect, const SkPaint &paint)
static std::unique_ptr< SkCodec > MakeFromStream(std::unique_ptr< SkStream >, SkSpan< const SkCodecs::Decoder > decoders, Result *=nullptr, SkPngChunkReader *=nullptr, SelectionPolicy selectionPolicy=SelectionPolicy::kPreferStillImage)
Definition SkCodec.cpp:163
@ kSuccess
Definition SkCodec.h:80
static sk_sp< SkColorSpace > MakeSRGB()
static sk_sp< SkData > MakeWithoutCopy(const void *data, size_t length)
Definition SkData.h:116
static sk_sp< SkData > MakeWithCopy(const void *data, size_t length)
Definition SkData.cpp:111
sk_sp< SkData > detachAsData()
Definition SkStream.cpp:707
static sk_sp< SkShader > Make(const sk_sp< const SkImage > &baseImage, const SkRect &baseRect, const SkSamplingOptions &baseSamplingOptions, const sk_sp< const SkImage > &gainmapImage, const SkRect &gainmapRect, const SkSamplingOptions &gainmapSamplingOptions, const SkGainmapInfo &gainmapInfo, const SkRect &dstRect, float dstHdrRatio, sk_sp< SkColorSpace > dstColorSpace)
static std::unique_ptr< SkCodec > MakeFromStream(std::unique_ptr< SkStream >, Result *)
static bool EncodeHDRGM(SkWStream *dst, const SkPixmap &base, const SkJpegEncoder::Options &baseOptions, const SkPixmap &gainmap, const SkJpegEncoder::Options &gainmapOptions, const SkGainmapInfo &gainmapInfo)
static std::unique_ptr< SkJpegSourceMgr > Make(SkStream *stream, size_t bufferSize=1024)
static std::unique_ptr< SkMemoryStream > Make(sk_sp< SkData > data)
Definition SkStream.cpp:314
virtual bool move(long)
Definition SkStream.h:131
virtual bool rewind()
Definition SkStream.h:100
virtual size_t peek(void *, size_t) const
Definition SkStream.h:68
virtual bool hasPosition() const
Definition SkStream.h:117
virtual size_t getPosition() const
Definition SkStream.h:119
virtual bool isAtEnd() const =0
virtual bool seek(size_t)
Definition SkStream.h:125
virtual size_t getLength() const
Definition SkStream.h:137
virtual const void * getMemoryBase()
Definition SkStream.h:141
virtual bool hasLength() const
Definition SkStream.h:135
virtual size_t read(void *buffer, size_t size)=0
const Paint & paint
static const uint8_t buffer[]
GAsyncResult * result
double y
double x
SK_API sk_sp< SkImage > RasterFromBitmap(const SkBitmap &bitmap)
Point offset
SkColor4f fGainmapRatioMax
SkColor4f fEpsilonSdr
SkColor4f fGainmapGamma
SkColor4f fGainmapRatioMin
float fDisplayRatioSdr
SkColor4f fEpsilonHdr
float fDisplayRatioHdr
static constexpr SkISize Make(int32_t w, int32_t h)
Definition SkSize.h:20
static SkImageInfo MakeN32Premul(int width, int height)
sk_sp< SkColorSpace > refColorSpace() const
SkISize dimensions() const
int width() const
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
int height() const
static size_t GetAbsoluteOffset(uint32_t dataOffset, size_t mpSegmentOffset)
static std::unique_ptr< SkJpegMultiPictureParameters > Make(const sk_sp< const SkData > &segmentParameters)
static SkRect Make(const SkISize &size)
Definition SkRect.h:669
constexpr float x() const
Definition SkRect.h:720
constexpr float y() const
Definition SkRect.h:727
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition SkRect.h:659
constexpr float height() const
Definition SkRect.h:769
constexpr float width() const
Definition SkRect.h:762