Flutter Engine
The Flutter Engine
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"
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
37namespace {
38
39// Return true if the relative difference between x and y is less than epsilon.
40static bool approx_eq(float x, float y, float epsilon) {
41 float numerator = std::abs(x - y);
42 // To avoid being too sensitive around zero, set the minimum denominator to epsilon.
43 float denominator = std::max(std::min(std::abs(x), std::abs(y)), epsilon);
44 if (numerator / denominator > epsilon) {
45 return false;
46 }
47 return true;
48}
49
50static bool approx_eq(const SkColor4f& x, const SkColor4f& y, float epsilon) {
51 return approx_eq(x.fR, y.fR, epsilon) && approx_eq(x.fG, y.fG, epsilon) &&
52 approx_eq(x.fB, y.fB, epsilon);
53}
54
55template <typename Reporter>
56void expect_approx_eq_info(Reporter& r, const SkGainmapInfo& a, const SkGainmapInfo& b) {
57 float kEpsilon = 1e-4f;
58 REPORTER_ASSERT(r, approx_eq(a.fGainmapRatioMin, b.fGainmapRatioMin, kEpsilon));
59 REPORTER_ASSERT(r, approx_eq(a.fGainmapRatioMin, b.fGainmapRatioMin, kEpsilon));
60 REPORTER_ASSERT(r, approx_eq(a.fGainmapGamma, b.fGainmapGamma, kEpsilon));
61 REPORTER_ASSERT(r, approx_eq(a.fEpsilonSdr, b.fEpsilonSdr, kEpsilon));
62 REPORTER_ASSERT(r, approx_eq(a.fEpsilonHdr, b.fEpsilonHdr, kEpsilon));
63 REPORTER_ASSERT(r, approx_eq(a.fDisplayRatioSdr, b.fDisplayRatioSdr, kEpsilon));
64 REPORTER_ASSERT(r, approx_eq(a.fDisplayRatioHdr, b.fDisplayRatioHdr, kEpsilon));
65 REPORTER_ASSERT(r, a.fType == b.fType);
66 REPORTER_ASSERT(r, a.fBaseImageType == b.fBaseImageType);
67
68 REPORTER_ASSERT(r, !!a.fGainmapMathColorSpace == !!b.fGainmapMathColorSpace);
69 if (a.fGainmapMathColorSpace) {
72 a.fGainmapMathColorSpace->transferFn(&a_fn);
73 a.fGainmapMathColorSpace->toXYZD50(&a_m);
76 b.fGainmapMathColorSpace->transferFn(&b_fn);
77 b.fGainmapMathColorSpace->toXYZD50(&b_m);
78
79 REPORTER_ASSERT(r, approx_eq(a_fn.g, b_fn.g, kEpsilon));
80 REPORTER_ASSERT(r, approx_eq(a_fn.a, b_fn.a, kEpsilon));
81 REPORTER_ASSERT(r, approx_eq(a_fn.b, b_fn.b, kEpsilon));
82 REPORTER_ASSERT(r, approx_eq(a_fn.c, b_fn.c, kEpsilon));
83 REPORTER_ASSERT(r, approx_eq(a_fn.d, b_fn.d, kEpsilon));
84 REPORTER_ASSERT(r, approx_eq(a_fn.e, b_fn.e, kEpsilon));
85 REPORTER_ASSERT(r, approx_eq(a_fn.f, b_fn.f, kEpsilon));
86
87 // The round-trip of the color space through the ICC profile loses significant precision.
88 // Use a larger epsilon for it.
89 const float kMatrixEpsilon = 1e-2f;
90 for (int i = 0; i < 3; ++i) {
91 for (int j = 0; j < 3; ++j) {
92 REPORTER_ASSERT(r, approx_eq(a_m.vals[i][j], b_m.vals[i][j], kMatrixEpsilon));
93 }
94 }
95 }
96}
97
98// A test stream to stress the different SkJpegSourceMgr sub-classes.
99class TestStream : public SkStream {
100public:
101 enum class Type {
102 kUnseekable, // SkJpegUnseekableSourceMgr
103 kSeekable, // SkJpegBufferedSourceMgr
104 kMemoryMapped, // SkJpegMemorySourceMgr
105 };
106 TestStream(Type type, SkStream* stream)
107 : fStream(stream)
108 , fSeekable(type != Type::kUnseekable)
109 , fMemoryMapped(type == Type::kMemoryMapped) {}
110 ~TestStream() override {}
111
112 size_t read(void* buffer, size_t size) override { return fStream->read(buffer, size); }
113 size_t peek(void* buffer, size_t size) const override { return fStream->peek(buffer, size); }
114 bool isAtEnd() const override { return fStream->isAtEnd(); }
115 bool rewind() override {
116 if (!fSeekable) {
117 return false;
118 }
119 return fStream->rewind();
120 }
121 bool hasPosition() const override {
122 if (!fSeekable) {
123 return false;
124 }
125 return fStream->hasPosition();
126 }
127 size_t getPosition() const override {
128 if (!fSeekable) {
129 return 0;
130 }
131 return fStream->hasPosition();
132 }
133 bool seek(size_t position) override {
134 if (!fSeekable) {
135 return 0;
136 }
137 return fStream->seek(position);
138 }
139 bool move(long offset) override {
140 if (!fSeekable) {
141 return 0;
142 }
143 return fStream->move(offset);
144 }
145 bool hasLength() const override {
146 if (!fMemoryMapped) {
147 return false;
148 }
149 return fStream->hasLength();
150 }
151 size_t getLength() const override {
152 if (!fMemoryMapped) {
153 return 0;
154 }
155 return fStream->getLength();
156 }
157 const void* getMemoryBase() override {
158 if (!fMemoryMapped) {
159 return nullptr;
160 }
161 return fStream->getMemoryBase();
162 }
163
164private:
165 SkStream* const fStream;
166 bool fSeekable = false;
167 bool fMemoryMapped = false;
168};
169
170} // namespace
171
172DEF_TEST(Codec_jpegSegmentScan, r) {
173 const struct Rec {
174 const char* path;
175 size_t sosSegmentCount;
176 size_t eoiSegmentCount;
177 size_t testSegmentIndex;
178 uint8_t testSegmentMarker;
179 size_t testSegmentOffset;
180 uint16_t testSegmentParameterLength;
181 } recs[] = {
182 {"images/wide_gamut_yellow_224_224_64.jpeg", 11, 15, 10, 0xda, 9768, 12},
183 {"images/CMYK.jpg", 7, 8, 1, 0xee, 2, 14},
184 {"images/b78329453.jpeg", 10, 23, 3, 0xe2, 154, 540},
185 {"images/brickwork-texture.jpg", 8, 28, 12, 0xc4, 34183, 42},
186 {"images/brickwork_normal-map.jpg", 8, 28, 27, 0xd9, 180612, 0},
187 {"images/cmyk_yellow_224_224_32.jpg", 19, 23, 2, 0xed, 854, 2828},
188 {"images/color_wheel.jpg", 10, 11, 2, 0xdb, 20, 67},
189 {"images/cropped_mandrill.jpg", 10, 11, 4, 0xc0, 158, 17},
190 {"images/dog.jpg", 10, 11, 5, 0xc4, 177, 28},
191 {"images/ducky.jpg", 12, 13, 10, 0xc4, 3718, 181},
192 {"images/exif-orientation-2-ur.jpg", 11, 12, 2, 0xe1, 20, 130},
193 {"images/flutter_logo.jpg", 9, 27, 21, 0xda, 5731, 8},
194 {"images/grayscale.jpg", 6, 16, 9, 0xda, 327, 8},
195 {"images/icc-v2-gbr.jpg", 12, 25, 24, 0xd9, 43832, 0},
196 {"images/mandrill_512_q075.jpg", 10, 11, 7, 0xc4, 393, 31},
197 {"images/mandrill_cmyk.jpg", 19, 35, 16, 0xdd, 574336, 4},
198 {"images/mandrill_h1v1.jpg", 10, 11, 1, 0xe0, 2, 16},
199 {"images/mandrill_h2v1.jpg", 10, 11, 0, 0xd8, 0, 0},
200 {"images/randPixels.jpg", 10, 11, 6, 0xc4, 200, 30},
201 {"images/wide_gamut_yellow_224_224_64.jpeg", 11, 15, 10, 0xda, 9768, 12},
202 };
203
204 for (const auto& rec : recs) {
205 auto stream = GetResourceAsStream(rec.path);
206 if (!stream) {
207 continue;
208 }
209
210 // Scan all the way to EndOfImage.
211 auto sourceMgr = SkJpegSourceMgr::Make(stream.get());
212 const auto& segments = sourceMgr->getAllSegments();
213
214 // Verify we got the expected number of segments at EndOfImage
215 REPORTER_ASSERT(r, rec.eoiSegmentCount == segments.size());
216
217 // Verify we got the expected number of segments before StartOfScan
218 for (size_t i = 0; i < segments.size(); ++i) {
219 if (segments[i].marker == kJpegMarkerStartOfScan) {
220 REPORTER_ASSERT(r, rec.sosSegmentCount == i + 1);
221 break;
222 }
223 }
224
225 // Verify the values for a randomly pre-selected segment index.
226 const auto& segment = segments[rec.testSegmentIndex];
227 REPORTER_ASSERT(r, rec.testSegmentMarker == segment.marker);
228 REPORTER_ASSERT(r, rec.testSegmentOffset == segment.offset);
229 REPORTER_ASSERT(r, rec.testSegmentParameterLength == segment.parameterLength);
230 }
231}
232
234 std::unique_ptr<SkJpegMultiPictureParameters>* outMpParams,
235 SkJpegSegment* outMpParamsSegment) {
236 auto sourceMgr = SkJpegSourceMgr::Make(stream);
237 for (const auto& segment : sourceMgr->getAllSegments()) {
238 if (segment.marker != kMpfMarker) {
239 continue;
240 }
241 auto parameterData = sourceMgr->getSegmentParameters(segment);
242 if (!parameterData) {
243 continue;
244 }
245 *outMpParams = SkJpegMultiPictureParameters::Make(parameterData);
246 if (*outMpParams) {
247 *outMpParamsSegment = segment;
248 return true;
249 }
250 }
251 return false;
252}
253
254DEF_TEST(Codec_multiPictureParams, r) {
255 // Little-endian test.
256 {
257 const uint8_t bytes[] = {
258 0x4d, 0x50, 0x46, 0x00, 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03,
259 0x00, 0x00, 0xb0, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, 0x31, 0x30, 0x30,
260 0x01, 0xb0, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
261 0xb0, 0x07, 0x00, 0x20, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
262 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x20, 0xcf, 0x49, 0x00, 0x00, 0x00, 0x00,
263 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xee, 0x28, 0x01, 0x00,
264 0xf9, 0xb7, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
265 };
266 auto mpParams =
268 REPORTER_ASSERT(r, mpParams);
269 REPORTER_ASSERT(r, mpParams->images.size() == 2);
270 REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
271 REPORTER_ASSERT(r, mpParams->images[0].size == 4837152);
272 REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 3979257);
273 REPORTER_ASSERT(r, mpParams->images[1].size == 76014);
274 }
275
276 // Big-endian test.
277 {
278 const uint8_t bytes[] = {
279 0x4d, 0x50, 0x46, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00,
280 0x03, 0xb0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30,
281 0xb0, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0xb0,
282 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
283 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x56, 0xda, 0x2f, 0x00, 0x00, 0x00,
284 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xc6, 0x01,
285 0x00, 0x55, 0x7c, 0x1f, 0x00, 0x00, 0x00, 0x00,
286 };
287 auto mpParams =
289 REPORTER_ASSERT(r, mpParams);
290 REPORTER_ASSERT(r, mpParams->images.size() == 2);
291 REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
292 REPORTER_ASSERT(r, mpParams->images[0].size == 5691951);
293 REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 5602335);
294 REPORTER_ASSERT(r, mpParams->images[1].size == 1361409);
295 }
296
297 // Three entry test.
298 {
299 const uint8_t bytes[] = {
300 0x4d, 0x50, 0x46, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00,
301 0x03, 0xb0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30,
302 0xb0, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0xb0,
303 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
304 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x1c, 0xc2, 0x00, 0x00, 0x00,
305 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0xb0,
306 0x00, 0x1f, 0x12, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
307 0x00, 0x96, 0x6b, 0x00, 0x22, 0x18, 0x9c, 0x00, 0x00, 0x00, 0x00,
308 };
309 auto mpParams =
311 REPORTER_ASSERT(r, mpParams);
312 REPORTER_ASSERT(r, mpParams->images.size() == 3);
313 REPORTER_ASSERT(r, mpParams->images[0].dataOffset == 0);
314 REPORTER_ASSERT(r, mpParams->images[0].size == 2038978);
315 REPORTER_ASSERT(r, mpParams->images[1].dataOffset == 2036460);
316 REPORTER_ASSERT(r, mpParams->images[1].size == 198064);
317 REPORTER_ASSERT(r, mpParams->images[2].dataOffset == 2234524);
318 REPORTER_ASSERT(r, mpParams->images[2].size == 38507);
319 }
320
321 // Inserting various corrupt values.
322 {
323 const uint8_t bytes[] = {
324 0x4d, 0x50, 0x46, 0x00, // 0: {'M', 'P', 'F', 0} signature
325 0x4d, 0x4d, 0x00, 0x2a, // 4: {'M', 'M', 0, '*'} big-endian
326 0x00, 0x00, 0x00, 0x08, // 8: Index IFD offset
327 0x00, 0x03, // 12: Number of tags
328 0xb0, 0x00, // 14: Version tag
329 0x00, 0x07, // 16: Undefined type
330 0x00, 0x00, 0x00, 0x04, // 18: Size
331 0x30, 0x31, 0x30, 0x30, // 22: Value
332 0xb0, 0x01, // 26: Number of images
333 0x00, 0x04, // 28: Unsigned long type
334 0x00, 0x00, 0x00, 0x01, // 30: Count
335 0x00, 0x00, 0x00, 0x02, // 34: Value
336 0xb0, 0x02, // 38: MP entry tag
337 0x00, 0x07, // 40: Undefined type
338 0x00, 0x00, 0x00, 0x20, // 42: Size
339 0x00, 0x00, 0x00, 0x32, // 46: Value (offset)
340 0x00, 0x00, 0x00, 0x00, // 50: Next IFD offset (null)
341 0x20, 0x03, 0x00, 0x00, // 54: MP Entry 0 attributes
342 0x00, 0x56, 0xda, 0x2f, // 58: MP Entry 0 size (5691951)
343 0x00, 0x00, 0x00, 0x00, // 62: MP Entry 0 offset (0)
344 0x00, 0x00, 0x00, 0x00, // 66: MP Entry 0 dependencies
345 0x00, 0x00, 0x00, 0x00, // 70: MP Entry 1 attributes.
346 0x00, 0x14, 0xc6, 0x01, // 74: MP Entry 1 size (1361409)
347 0x00, 0x55, 0x7c, 0x1f, // 78: MP Entry 1 offset (5602335)
348 0x00, 0x00, 0x00, 0x00, // 82: MP Entry 1 dependencies
349 };
350
351 // Verify the offsets labeled above.
352 REPORTER_ASSERT(r, bytes[22] == 0x30);
353 REPORTER_ASSERT(r, bytes[26] == 0xb0);
354 REPORTER_ASSERT(r, bytes[38] == 0xb0);
355 REPORTER_ASSERT(r, bytes[54] == 0x20);
356 REPORTER_ASSERT(r, bytes[81] == 0x1f);
357
358 {
359 // Change the version to {'0', '1', '0', '1'}.
360 auto bytesInvalid = SkData::MakeWithCopy(bytes, sizeof(bytes));
361 REPORTER_ASSERT(r, bytes[25] == '0');
362 reinterpret_cast<uint8_t*>(bytesInvalid->writable_data())[25] = '1';
363 REPORTER_ASSERT(r, SkJpegMultiPictureParameters::Make(bytesInvalid) == nullptr);
364 }
365
366 {
367 // Change the number of images to be undefined type instead of unsigned long type.
368 auto bytesInvalid = SkData::MakeWithCopy(bytes, sizeof(bytes));
369 REPORTER_ASSERT(r, bytes[29] == 0x04);
370 reinterpret_cast<uint8_t*>(bytesInvalid->writable_data())[29] = 0x07;
371 REPORTER_ASSERT(r, SkJpegMultiPictureParameters::Make(bytesInvalid) == nullptr);
372 }
373
374 {
375 // Make the MP entries point off of the end of the buffer.
376 auto bytesInvalid = SkData::MakeWithCopy(bytes, sizeof(bytes));
377 REPORTER_ASSERT(r, bytes[49] == 0x32);
378 reinterpret_cast<uint8_t*>(bytesInvalid->writable_data())[49] = 0xFE;
379 REPORTER_ASSERT(r, SkJpegMultiPictureParameters::Make(bytesInvalid) == nullptr);
380 }
381
382 {
383 // Make the MP entries too small.
384 auto bytesInvalid = SkData::MakeWithCopy(bytes, sizeof(bytes));
385 REPORTER_ASSERT(r, bytes[45] == 0x20);
386 reinterpret_cast<uint8_t*>(bytesInvalid->writable_data())[45] = 0x1F;
387 REPORTER_ASSERT(r, SkJpegMultiPictureParameters::Make(bytesInvalid) == nullptr);
388 }
389 }
390}
391
392DEF_TEST(Codec_jpegMultiPicture, r) {
393 const char* path = "images/iphone_13_pro.jpeg";
396
397 // Search and parse the MPF header.
398 std::unique_ptr<SkJpegMultiPictureParameters> mpParams;
399 SkJpegSegment mpParamsSegment;
400 REPORTER_ASSERT(r, find_mp_params_segment(stream.get(), &mpParams, &mpParamsSegment));
401
402 // Verify that we get the same parameters when we re-serialize and de-serialize them
403 {
404 auto mpParamsSerialized = mpParams->serialize(0);
405 REPORTER_ASSERT(r, mpParamsSerialized);
406 auto mpParamsRoundTripped = SkJpegMultiPictureParameters::Make(mpParamsSerialized);
407 REPORTER_ASSERT(r, mpParamsRoundTripped);
408 REPORTER_ASSERT(r, mpParamsRoundTripped->images.size() == mpParams->images.size());
409 for (size_t i = 0; i < mpParamsRoundTripped->images.size(); ++i) {
410 REPORTER_ASSERT(r, mpParamsRoundTripped->images[i].size == mpParams->images[i].size);
412 r,
413 mpParamsRoundTripped->images[i].dataOffset == mpParams->images[i].dataOffset);
414 }
415 }
416
417 const struct Rec {
418 const TestStream::Type streamType;
419 const bool skipFirstImage;
420 const size_t bufferSize;
421 } recs[] = {
422 {TestStream::Type::kMemoryMapped, false, 1024},
423 {TestStream::Type::kMemoryMapped, true, 1024},
424 {TestStream::Type::kSeekable, false, 1024},
425 {TestStream::Type::kSeekable, true, 1024},
426 {TestStream::Type::kSeekable, false, 7},
427 {TestStream::Type::kSeekable, true, 13},
428 {TestStream::Type::kSeekable, true, 1024 * 1024 * 16},
429 {TestStream::Type::kUnseekable, false, 1024},
430 {TestStream::Type::kUnseekable, true, 1024},
431 {TestStream::Type::kUnseekable, false, 1},
432 {TestStream::Type::kUnseekable, true, 1},
433 {TestStream::Type::kUnseekable, false, 7},
434 {TestStream::Type::kUnseekable, true, 13},
435 {TestStream::Type::kUnseekable, false, 1024 * 1024 * 16},
436 {TestStream::Type::kUnseekable, true, 1024 * 1024 * 16},
437 };
438 for (const auto& rec : recs) {
439 stream->rewind();
440 TestStream testStream(rec.streamType, stream.get());
441 auto sourceMgr = SkJpegSourceMgr::Make(&testStream, rec.bufferSize);
442
443 // Decode the images into bitmaps.
444 size_t numberOfImages = mpParams->images.size();
445 std::vector<SkBitmap> bitmaps(numberOfImages);
446 for (size_t i = 0; i < numberOfImages; ++i) {
447 if (i == 0) {
448 REPORTER_ASSERT(r, mpParams->images[i].dataOffset == 0);
449 continue;
450 }
451 if (i == 1 && rec.skipFirstImage) {
452 continue;
453 }
454 auto imageData = sourceMgr->getSubsetData(
456 mpParams->images[i].dataOffset, mpParamsSegment.offset),
457 mpParams->images[i].size);
458 REPORTER_ASSERT(r, imageData);
459
460 std::unique_ptr<SkCodec> codec =
462 REPORTER_ASSERT(r, codec);
463
464 SkBitmap bm;
465 bm.allocPixels(codec->getInfo());
468 codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes()));
469 bitmaps[i] = bm;
470 }
471
472 // Spot-check the image size and pixels.
473 if (!rec.skipFirstImage) {
474 REPORTER_ASSERT(r, bitmaps[1].dimensions() == SkISize::Make(1512, 2016));
475 REPORTER_ASSERT(r, bitmaps[1].getColor(0, 0) == 0xFF3B3B3B);
476 REPORTER_ASSERT(r, bitmaps[1].getColor(1511, 2015) == 0xFF101010);
477 }
478 REPORTER_ASSERT(r, bitmaps[2].dimensions() == SkISize::Make(576, 768));
479 REPORTER_ASSERT(r, bitmaps[2].getColor(0, 0) == 0xFF010101);
480 REPORTER_ASSERT(r, bitmaps[2].getColor(575, 767) == 0xFFB5B5B5);
481 }
482}
483
484// Decode an image and its gainmap.
485template <typename Reporter>
486void decode_all(Reporter& r,
487 std::unique_ptr<SkStream> stream,
488 SkBitmap& baseBitmap,
489 SkBitmap& gainmapBitmap,
490 SkGainmapInfo& gainmapInfo) {
491 // Decode the base bitmap.
493 std::unique_ptr<SkCodec> baseCodec = SkJpegCodec::MakeFromStream(std::move(stream), &result);
494 REPORTER_ASSERT(r, baseCodec);
495 baseBitmap.allocPixels(baseCodec->getInfo());
497 SkCodec::kSuccess == baseCodec->getPixels(baseBitmap.info(),
498 baseBitmap.getPixels(),
499 baseBitmap.rowBytes()));
500 std::unique_ptr<SkAndroidCodec> androidCodec =
501 SkAndroidCodec::MakeFromCodec(std::move(baseCodec));
502 REPORTER_ASSERT(r, androidCodec);
503
504 // Extract the gainmap info and stream.
505 std::unique_ptr<SkStream> gainmapStream;
506 REPORTER_ASSERT(r, androidCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream));
507 REPORTER_ASSERT(r, gainmapStream);
508
509 // Decode the gainmap bitmap.
510 std::unique_ptr<SkCodec> gainmapCodec = SkCodec::MakeFromStream(std::move(gainmapStream));
511 REPORTER_ASSERT(r, gainmapCodec);
512 SkBitmap bm;
513 bm.allocPixels(gainmapCodec->getInfo());
514 gainmapBitmap.allocPixels(gainmapCodec->getInfo());
516 SkCodec::kSuccess == gainmapCodec->getPixels(gainmapBitmap.info(),
517 gainmapBitmap.getPixels(),
518 gainmapBitmap.rowBytes()));
519}
520
521DEF_TEST(AndroidCodec_jpegGainmapDecode, r) {
522 const struct Rec {
523 const char* path;
524 SkISize dimensions;
525 SkColor originColor;
526 SkColor farCornerColor;
528 } recs[] = {
529 {"images/iphone_13_pro.jpeg",
530 SkISize::Make(1512, 2016),
531 0xFF3B3B3B,
532 0xFF101010,
533 {{1.f, 1.f, 1.f, 1.f},
534 {3.482202f, 3.482202f, 3.482202f, 1.f},
535 {1.f, 1.f, 1.f, 1.f},
536 {0.f, 0.f, 0.f, 1.f},
537 {0.f, 0.f, 0.f, 1.f},
538 1.f,
539 3.482202f,
542 nullptr}},
543 {"images/iphone_15.jpeg",
544 SkISize::Make(2016, 1512),
545 0xFF5C5C5C,
546 0xFF656565,
547 {{1.f, 1.f, 1.f, 1.f},
548 {3.755272f, 3.755272f, 3.755272f, 1.f},
549 {1.f, 1.f, 1.f, 1.f},
550 {0.f, 0.f, 0.f, 1.f},
551 {0.f, 0.f, 0.f, 1.f},
552 1.f,
553 3.755272f,
556 nullptr}},
557 {"images/gainmap_gcontainer_only.jpg",
558 SkISize::Make(32, 32),
559 0xffffffff,
560 0xffffffff,
561 {{25.f, 0.5f, 1.f, 1.f},
562 {2.f, 4.f, 8.f, 1.f},
563 {0.5, 1.f, 2.f, 1.f},
564 {0.01f, 0.001f, 0.0001f, 1.f},
565 {0.0001f, 0.001f, 0.01f, 1.f},
566 2.f,
567 4.f,
570 nullptr}},
571 {"images/gainmap_iso21496_1_adobe_gcontainer.jpg",
572 SkISize::Make(32, 32),
573 0xffffffff,
574 0xff000000,
575 {{25.f, 0.5f, 1.f, 1.f},
576 {2.f, 4.f, 8.f, 1.f},
577 {0.5, 1.f, 2.f, 1.f},
578 {0.01f, 0.001f, 0.0001f, 1.f},
579 {0.0001f, 0.001f, 0.01f, 1.f},
580 2.f,
581 4.f,
584 nullptr}},
585 {"images/gainmap_iso21496_1.jpg",
586 SkISize::Make(32, 32),
587 0xffffffff,
588 0xff000000,
589 {{25.f, 0.5f, 1.f, 1.f},
590 {2.f, 4.f, 8.f, 1.f},
591 {0.5, 1.f, 2.f, 1.f},
592 {0.01f, 0.001f, 0.0001f, 1.f},
593 {0.0001f, 0.001f, 0.01f, 1.f},
594 2.f,
595 4.f,
599 };
600
601 TestStream::Type kStreamTypes[] = {
602 TestStream::Type::kUnseekable,
603 TestStream::Type::kSeekable,
604 TestStream::Type::kMemoryMapped,
605 };
606 for (const auto& streamType : kStreamTypes) {
607 bool useFileStream = streamType != TestStream::Type::kMemoryMapped;
608 for (const auto& rec : recs) {
609 auto stream = GetResourceAsStream(rec.path, useFileStream);
611 auto testStream = std::make_unique<TestStream>(streamType, stream.get());
612
613 SkBitmap baseBitmap;
614 SkBitmap gainmapBitmap;
615 SkGainmapInfo gainmapInfo;
616 decode_all(r, std::move(testStream), baseBitmap, gainmapBitmap, gainmapInfo);
617
618 // Spot-check the image size and pixels.
619 REPORTER_ASSERT(r, gainmapBitmap.dimensions() == rec.dimensions);
620 REPORTER_ASSERT(r, gainmapBitmap.getColor(0, 0) == rec.originColor);
622 r,
623 gainmapBitmap.getColor(rec.dimensions.fWidth - 1, rec.dimensions.fHeight - 1) ==
624 rec.farCornerColor);
625
626 // Verify the gainmap rendering parameters.
627 expect_approx_eq_info(r, rec.info, gainmapInfo);
628 }
629 }
630}
631
632DEF_TEST(AndroidCodec_jpegNoGainmap, r) {
633 // This test image has a large APP16 segment that will stress the various SkJpegSourceMgrs'
634 // data skipping paths.
635 const char* path = "images/icc-v2-gbr.jpg";
636
637 TestStream::Type kStreamTypes[] = {
638 TestStream::Type::kUnseekable,
639 TestStream::Type::kSeekable,
640 TestStream::Type::kMemoryMapped,
641 };
642 for (const auto& streamType : kStreamTypes) {
643 bool useFileStream = streamType != TestStream::Type::kMemoryMapped;
644 auto stream = GetResourceAsStream(path, useFileStream);
646 auto testStream = std::make_unique<TestStream>(streamType, stream.get());
647
648 // Decode the base bitmap.
650 std::unique_ptr<SkCodec> baseCodec =
651 SkJpegCodec::MakeFromStream(std::move(testStream), &result);
652 REPORTER_ASSERT(r, baseCodec);
653 SkBitmap baseBitmap;
654 baseBitmap.allocPixels(baseCodec->getInfo());
656 SkCodec::kSuccess == baseCodec->getPixels(baseBitmap.info(),
657 baseBitmap.getPixels(),
658 baseBitmap.rowBytes()));
659
660 std::unique_ptr<SkAndroidCodec> androidCodec =
661 SkAndroidCodec::MakeFromCodec(std::move(baseCodec));
662 REPORTER_ASSERT(r, androidCodec);
663
664 // Try to extract the gainmap info and stream. It should fail.
665 SkGainmapInfo gainmapInfo;
666 std::unique_ptr<SkStream> gainmapStream;
667 REPORTER_ASSERT(r, !androidCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream));
668 }
669}
670
671#if !defined(SK_ENABLE_NDK_IMAGES)
672
673DEF_TEST(AndroidCodec_gainmapInfoEncode, r) {
674 SkDynamicMemoryWStream encodeStream;
675
676 constexpr size_t kNumTests = 4;
677
678 SkBitmap baseBitmap;
679 baseBitmap.allocPixels(SkImageInfo::MakeN32Premul(16, 16));
680
681 SkBitmap gainmapBitmaps[kNumTests];
682 gainmapBitmaps[0].allocPixels(SkImageInfo::MakeN32Premul(16, 16));
683 gainmapBitmaps[1].allocPixels(SkImageInfo::MakeN32Premul(8, 8));
684 gainmapBitmaps[2].allocPixels(
686 gainmapBitmaps[3].allocPixels(
688
689 SkGainmapInfo infos[kNumTests] = {
690 // Multi-channel, UltraHDR-compatible.
691 {{1.f, 2.f, 4.f, 1.f},
692 {8.f, 16.f, 32.f, 1.f},
693 {64.f, 128.f, 256.f, 1.f},
694 {1 / 10.f, 1 / 11.f, 1 / 12.f, 1.f},
695 {1 / 13.f, 1 / 14.f, 1 / 15.f, 1.f},
696 4.f,
697 32.f,
700 nullptr},
701 // Multi-channel, not UltraHDR-compatible.
702 {{1.f, 2.f, 4.f, 1.f},
703 {8.f, 16.f, 32.f, 1.f},
704 {64.f, 128.f, 256.f, 1.f},
705 {1 / 10.f, 1 / 11.f, 1 / 12.f, 1.f},
706 {1 / 13.f, 1 / 14.f, 1 / 15.f, 1.f},
707 4.f,
708 32.f,
712 // Single-channel, UltraHDR-compatible.
713 {{1.f, 1.f, 1.f, 1.f},
714 {8.f, 8.f, 8.f, 1.f},
715 {64.f, 64.f, 64.f, 1.f},
716 {1 / 128.f, 1 / 128.f, 1 / 128.f, 1.f},
717 {1 / 256.f, 1 / 256.f, 1 / 256.f, 1.f},
718 4.f,
719 32.f,
722 nullptr},
723 // Single-channel, not UltraHDR-compatible.
724 {{1.f, 1.f, 1.f, 1.f},
725 {8.f, 8.f, 8.f, 1.f},
726 {64.f, 64.f, 64.f, 1.f},
727 {1 / 128.f, 1 / 128.f, 1 / 128.f, 1.f},
728 {1 / 256.f, 1 / 256.f, 1 / 256.f, 1.f},
729 4.f,
730 32.f,
734 };
735
736 for (size_t i = 0; i < kNumTests; ++i) {
737 // Encode |gainmapInfo|.
738 bool encodeResult = SkJpegGainmapEncoder::EncodeHDRGM(&encodeStream,
739 baseBitmap.pixmap(),
741 gainmapBitmaps[i].pixmap(),
743 infos[i]);
744 REPORTER_ASSERT(r, encodeResult);
745
746 // Decode into |decodedGainmapInfo|.
747 SkGainmapInfo decodedGainmapInfo;
748 SkBitmap decodedBaseBitmap;
749 SkBitmap decodedGainmapBitmap;
750 auto decodeStream = std::make_unique<SkMemoryStream>(encodeStream.detachAsData());
751 decode_all(r,
752 std::move(decodeStream),
753 decodedBaseBitmap,
754 decodedGainmapBitmap,
755 decodedGainmapInfo);
756
757 // Verify that the decode reproducd the input.
758 expect_approx_eq_info(r, infos[i], decodedGainmapInfo);
759 }
760}
761
762// Render an applied gainmap.
763static SkBitmap render_gainmap(const SkImageInfo& renderInfo,
764 float renderHdrRatio,
765 const SkBitmap& baseBitmap,
766 const SkBitmap& gainmapBitmap,
767 const SkGainmapInfo& gainmapInfo,
768 int x,
769 int y) {
770 SkRect baseRect = SkRect::MakeXYWH(x, y, renderInfo.width(), renderInfo.height());
771
772 float scaleX = gainmapBitmap.width() / static_cast<float>(baseBitmap.width());
773 float scaleY = gainmapBitmap.height() / static_cast<float>(baseBitmap.height());
774 SkRect gainmapRect = SkRect::MakeXYWH(baseRect.x() * scaleX,
775 baseRect.y() * scaleY,
776 baseRect.width() * scaleX,
777 baseRect.height() * scaleY);
778
779 SkRect dstRect = SkRect::Make(renderInfo.dimensions());
780
781 sk_sp<SkImage> baseImage = SkImages::RasterFromBitmap(baseBitmap);
782 sk_sp<SkImage> gainmapImage = SkImages::RasterFromBitmap(gainmapBitmap);
783 sk_sp<SkShader> shader = SkGainmapShader::Make(baseImage,
784 baseRect,
786 gainmapImage,
787 gainmapRect,
789 gainmapInfo,
790 dstRect,
791 renderHdrRatio,
792 renderInfo.refColorSpace());
793
795 result.allocPixels(renderInfo);
796 result.eraseColor(SK_ColorTRANSPARENT);
797 SkCanvas canvas(result);
798
800 paint.setShader(shader);
801 canvas.drawRect(dstRect, paint);
802
803 return result;
804}
805
806// Render a single pixel of an applied gainmap and return it.
807static SkColor4f render_gainmap_pixel(float renderHdrRatio,
808 const SkBitmap& baseBitmap,
809 const SkBitmap& gainmapBitmap,
810 const SkGainmapInfo& gainmapInfo,
811 int x,
812 int y) {
813 SkImageInfo testPixelInfo = SkImageInfo::Make(
814 /*width=*/1,
815 /*height=*/1,
819 SkBitmap testPixelBitmap = render_gainmap(
820 testPixelInfo, renderHdrRatio, baseBitmap, gainmapBitmap, gainmapInfo, x, y);
821 return testPixelBitmap.getColor4f(0, 0);
822}
823
824DEF_TEST(AndroidCodec_jpegGainmapTranscode, r) {
825 const char* path = "images/iphone_13_pro.jpeg";
826 SkBitmap baseBitmap[2];
827 SkBitmap gainmapBitmap[2];
828 SkGainmapInfo gainmapInfo[2];
829
830 // Decode an MPF-based gainmap image.
831 decode_all(r, GetResourceAsStream(path), baseBitmap[0], gainmapBitmap[0], gainmapInfo[0]);
832
833 // This test was written before SkGainmapShader added support for kApple type. Strip the
834 // type out.
835 gainmapInfo[0].fType = SkGainmapInfo::Type::kDefault;
836
837 constexpr float kEpsilon = 1e-2f;
838 {
839 SkDynamicMemoryWStream encodeStream;
840
841 // Transcode to UltraHDR.
842 bool encodeResult = SkJpegGainmapEncoder::EncodeHDRGM(&encodeStream,
843 baseBitmap[0].pixmap(),
845 gainmapBitmap[0].pixmap(),
847 gainmapInfo[0]);
848 REPORTER_ASSERT(r, encodeResult);
849 auto encodeData = encodeStream.detachAsData();
850
851 // Decode the just-encoded image.
852 auto decodeStream = std::make_unique<SkMemoryStream>(encodeData);
853 decode_all(r, std::move(decodeStream), baseBitmap[1], gainmapBitmap[1], gainmapInfo[1]);
854
855 // HDRGM will have the same rendering parameters.
856 expect_approx_eq_info(r, gainmapInfo[0], gainmapInfo[1]);
857
858 // Render a few pixels and verify that they come out the same. Rendering requires SkSL.
859 const struct Rec {
860 int x;
861 int y;
862 float hdrRatio;
863 SkColor4f expectedColor;
864 SkColorType forcedColorType;
865 } recs[] = {
866 {1446, 1603, 1.05f, {0.984375f, 1.004883f, 1.008789f, 1.f}, kUnknown_SkColorType},
867 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kUnknown_SkColorType},
868 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kGray_8_SkColorType},
869 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kAlpha_8_SkColorType},
870 {1446, 1603, 100.f, {1.147461f, 1.170898f, 1.174805f, 1.f}, kR8_unorm_SkColorType},
871 };
872
873 for (const auto& rec : recs) {
874 SkBitmap gainmapBitmap0;
875 SkASSERT(gainmapBitmap[0].colorType() == kGray_8_SkColorType);
876
877 // Force various different single-channel formats, to ensure that they all work. Note
878 // that when the color type is forced to kAlpha_8_SkColorType, the shader will always
879 // read (0,0,0,1) if the alpha type is kOpaque_SkAlphaType.
880 if (rec.forcedColorType == kUnknown_SkColorType) {
881 gainmapBitmap0 = gainmapBitmap[0];
882 } else {
883 gainmapBitmap0.installPixels(gainmapBitmap[0]
884 .info()
885 .makeColorType(rec.forcedColorType)
886 .makeAlphaType(kPremul_SkAlphaType),
887 gainmapBitmap[0].getPixels(),
888 gainmapBitmap[0].rowBytes());
889 }
891 rec.hdrRatio, baseBitmap[0], gainmapBitmap0, gainmapInfo[0], rec.x, rec.y);
893 rec.hdrRatio, baseBitmap[1], gainmapBitmap[1], gainmapInfo[1], rec.x, rec.y);
894
896 }
897 }
898}
899
900static sk_sp<SkData> get_mp_image(sk_sp<SkData> imageData, size_t imageNumber) {
901 SkMemoryStream stream(imageData);
902 auto sourceMgr = SkJpegSourceMgr::Make(&stream);
903
904 std::unique_ptr<SkJpegMultiPictureParameters> mpParams;
905 SkJpegSegment mpParamsSegment;
906 if (!find_mp_params_segment(&stream, &mpParams, &mpParamsSegment)) {
907 return nullptr;
908 }
909 return SkData::MakeSubset(
910 imageData.get(),
912 mpParams->images[imageNumber].dataOffset, mpParamsSegment.offset),
913 mpParams->images[imageNumber].size);
914}
915
916static std::unique_ptr<SkTiff::ImageFileDirectory> get_ifd(
917 sk_sp<SkData> imageData, uint8_t marker, const void* sig, size_t sigSize, size_t pad) {
918 SkMemoryStream stream(imageData);
919 auto sourceMgr = SkJpegSourceMgr::Make(&stream);
920 for (const auto& segment : sourceMgr->getAllSegments()) {
921 if (segment.marker != marker) {
922 continue;
923 }
924 auto parameterData = sourceMgr->getSegmentParameters(segment);
925 if (!parameterData) {
926 continue;
927 }
928 if (parameterData->size() < sigSize || memcmp(sig, parameterData->data(), sigSize) != 0) {
929 continue;
930 }
931 auto ifdData = SkData::MakeSubset(
932 parameterData.get(), sigSize + pad, parameterData->size() - (sigSize + pad));
933
934 bool littleEndian = false;
935 uint32_t ifdOffset = 0;
936 if (!SkTiff::ImageFileDirectory::ParseHeader(ifdData.get(), &littleEndian, &ifdOffset)) {
937 return nullptr;
938 }
939 return SkTiff::ImageFileDirectory::MakeFromOffset(ifdData, littleEndian, ifdOffset);
940 }
941 return nullptr;
942}
943
944static std::unique_ptr<SkTiff::ImageFileDirectory> get_mpf_ifd(sk_sp<SkData> imageData) {
945 return get_ifd(std::move(imageData), kMpfMarker, kMpfSig, sizeof(kMpfSig), 0);
946}
947
948static std::unique_ptr<SkTiff::ImageFileDirectory> get_exif_ifd(sk_sp<SkData> imageData) {
949 return get_ifd(std::move(imageData), kExifMarker, kExifSig, sizeof(kExifSig), 1);
950}
951
952DEF_TEST(AndroidCodec_mpfParse, r) {
953 sk_sp<SkData> inputData = GetResourceAsData("images/iphone_13_pro.jpeg");
954
955 {
956 // The MPF in iPhone images has 3 entries: version, image count, and the MP entries.
957 auto ifd = get_mpf_ifd(inputData);
958 REPORTER_ASSERT(r, ifd);
959 REPORTER_ASSERT(r, ifd->getNumEntries() == 3);
960 REPORTER_ASSERT(r, ifd->getEntryTag(0) == 0xB000);
961 REPORTER_ASSERT(r, ifd->getEntryTag(1) == 0xB001);
962 REPORTER_ASSERT(r, ifd->getEntryTag(2) == 0xB002);
963
964 // There is no attribute IFD.
965 REPORTER_ASSERT(r, !ifd->nextIfdOffset());
966 }
967
968 {
969 // The gainmap images have version and image count.
970 auto ifd = get_mpf_ifd(get_mp_image(inputData, 1));
971 REPORTER_ASSERT(r, ifd);
972
973 REPORTER_ASSERT(r, ifd->getNumEntries() == 2);
974 REPORTER_ASSERT(r, ifd->getEntryTag(0) == 0xB000);
975 REPORTER_ASSERT(r, ifd->getEntryTag(1) == 0xB001);
976 uint32_t value = 0;
977 REPORTER_ASSERT(r, ifd->getEntryUnsignedLong(1, 1, &value));
978 REPORTER_ASSERT(r, value == 3);
979
980 // There is no further IFD.
981 REPORTER_ASSERT(r, !ifd->nextIfdOffset());
982 }
983
984 // Replace |inputData| with its transcoded version.
985 {
986 SkBitmap baseBitmap;
987 SkBitmap gainmapBitmap;
988 SkGainmapInfo gainmapInfo;
989 decode_all(r,
990 std::make_unique<SkMemoryStream>(inputData),
991 baseBitmap,
992 gainmapBitmap,
993 gainmapInfo);
995 SkDynamicMemoryWStream encodeStream;
996 bool encodeResult = SkJpegGainmapEncoder::EncodeHDRGM(&encodeStream,
997 baseBitmap.pixmap(),
999 gainmapBitmap.pixmap(),
1001 gainmapInfo);
1002 REPORTER_ASSERT(r, encodeResult);
1003 inputData = encodeStream.detachAsData();
1004 }
1005
1006 {
1007 // Exif should be present and valid.
1008 auto ifd = get_exif_ifd(inputData);
1009 REPORTER_ASSERT(r, ifd);
1010 REPORTER_ASSERT(r, ifd->getNumEntries() == 1);
1011 constexpr uint16_t kSubIFDOffsetTag = 0x8769;
1012 REPORTER_ASSERT(r, ifd->getEntryTag(0) == kSubIFDOffsetTag);
1013 }
1014
1015 {
1016 // The MPF in encoded images has 3 entries: version, image count, and the MP entries.
1017 auto ifd = get_mpf_ifd(inputData);
1018 REPORTER_ASSERT(r, ifd);
1019 REPORTER_ASSERT(r, ifd->getNumEntries() == 3);
1020 REPORTER_ASSERT(r, ifd->getEntryTag(0) == 0xB000);
1021 REPORTER_ASSERT(r, ifd->getEntryTag(1) == 0xB001);
1022 REPORTER_ASSERT(r, ifd->getEntryTag(2) == 0xB002);
1023
1024 // There is no attribute IFD.
1025 REPORTER_ASSERT(r, !ifd->nextIfdOffset());
1026 }
1027
1028 {
1029 // The MPF in encoded gainmap images has 2 entries: Version and number of images.
1030 auto ifd = get_mpf_ifd(get_mp_image(inputData, 1));
1031 REPORTER_ASSERT(r, ifd);
1032
1033 REPORTER_ASSERT(r, ifd->getNumEntries() == 1);
1034 REPORTER_ASSERT(r, ifd->getEntryTag(0) == 0xB000);
1035
1036 // Verify the version data (don't verify the version in the primary image, because if that
1037 // were broken all MPF images would be broken).
1038 sk_sp<SkData> versionData = ifd->getEntryUndefinedData(0);
1039 REPORTER_ASSERT(r, versionData);
1040 REPORTER_ASSERT(r, versionData->bytes()[0] == '0');
1041 REPORTER_ASSERT(r, versionData->bytes()[1] == '1');
1042 REPORTER_ASSERT(r, versionData->bytes()[2] == '0');
1043 REPORTER_ASSERT(r, versionData->bytes()[3] == '0');
1044
1045 // There is no further IFD.
1046 REPORTER_ASSERT(r, !ifd->nextIfdOffset());
1047 }
1048}
1049
1050DEF_TEST(AndroidCodec_gainmapInfoParse, r) {
1051 const uint8_t versionData[] = {
1052 0x00, // Minimum version
1053 0x00,
1054 0x00, // Writer version
1055 0x00,
1056 };
1057 const uint8_t data[] = {
1058 0x00, 0x00, // Minimum version
1059 0x00, 0x00, // Writer version
1060 0xc0, // Flags
1061 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // Base HDR headroom
1062 0x00, 0x01, 0x45, 0x3e, 0x00, 0x00, 0x80, 0x00, // Altr HDR headroom
1063 0xfc, 0x23, 0x05, 0x14, 0x40, 0x00, 0x00, 0x00, // Red: Gainmap min
1064 0x00, 0x01, 0x1f, 0xe1, 0x00, 0x00, 0x80, 0x00, // Red: Gainmap max
1065 0x10, 0x4b, 0x9f, 0x0a, 0x40, 0x00, 0x00, 0x00, // Red: Gamma
1066 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Red: Base offset
1067 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Red: Altr offset
1068 0xfd, 0xdb, 0x68, 0x04, 0x40, 0x00, 0x00, 0x00, // Green: Gainmap min
1069 0x00, 0x01, 0x11, 0x68, 0x00, 0x00, 0x80, 0x00, // Green: Gainmap max
1070 0x10, 0x28, 0xf9, 0x53, 0x40, 0x00, 0x00, 0x00, // Green: Gamma
1071 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Green: Base offset
1072 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Green: Altr offset
1073 0xf7, 0x16, 0x7b, 0x90, 0x40, 0x00, 0x00, 0x00, // Blue: Gainmap min
1074 0x00, 0x01, 0x0f, 0x9a, 0x00, 0x00, 0x80, 0x00, // Blue: Gainmap max
1075 0x12, 0x95, 0xa8, 0x3f, 0x40, 0x00, 0x00, 0x00, // Blue: Gamma
1076 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Blue: Base offset
1077 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Blue: Altr offset
1078 };
1079 SkGainmapInfo kExpectedInfo = {{0.959023f, 0.977058f, 0.907989f, 1.f},
1080 {4.753710f, 4.395375f, 4.352630f, 1.f},
1081 {3.927490f, 3.960382f, 3.443712f, 1.f},
1082 {0.015625f, 0.015625f, 0.015625f, 1.f},
1083 {0.015625f, 0.015625f, 0.015625f, 1.f},
1084 1.000000f,
1085 5.819739f,
1088 nullptr};
1089 SkGainmapInfo kSingleChannelInfo = {{0.1234567e-4f, 0.1234567e-4f, 0.1234567e-4f, 1.f},
1090 {-0.1234567e-4f, -0.1234567e-4f, -0.1234567e-4f, 1.f},
1091 {0.1234567e+0f, 0.1234567e+0f, 0.1234567e+0f, 1.f},
1092 {0.1234567e+4f, 0.1234567e+4f, 0.1234567e+4f, 1.f},
1093 {0.1234567e+4f, 0.1234567e+4f, 0.1234567e+4f, 1.f},
1094 1.,
1095 4.f,
1099
1100 // Verify the version from data.
1103 SkData::MakeWithoutCopy(versionData, sizeof(versionData)).get()));
1104
1105 // Verify the SkGainmapInfo from data.
1109 expect_approx_eq_info(r, info, kExpectedInfo);
1110
1111 // Verify the parsed version.
1113
1114 // Verify the round-trip SkGainmapInfo.
1115 auto dataInfo = info.serialize();
1116 SkGainmapInfo infoRoundTrip;
1117 REPORTER_ASSERT(r, SkGainmapInfo::Parse(dataInfo.get(), infoRoundTrip));
1118 expect_approx_eq_info(r, info, infoRoundTrip);
1119
1120 // Serialize a single-channel SkGainmapInfo. The serialized data should be smaller.
1121 auto dataSingleChannelInfo = kSingleChannelInfo.serialize();
1122 REPORTER_ASSERT(r, dataSingleChannelInfo->size() < dataInfo->size());
1123 SkGainmapInfo singleChannelInfoRoundTrip;
1125 SkGainmapInfo::Parse(dataSingleChannelInfo.get(), singleChannelInfoRoundTrip));
1126 expect_approx_eq_info(r, singleChannelInfoRoundTrip, kSingleChannelInfo);
1127}
1128
1129#endif // !defined(SK_ENABLE_NDK_IMAGES)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
static bool approx_eq(float x, float y, float epsilon)
Definition: ExifTest.cpp:81
static sk_sp< SkData > get_mp_image(sk_sp< SkData > imageData, size_t imageNumber)
DEF_TEST(Codec_jpegSegmentScan, r)
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 std::unique_ptr< SkTiff::ImageFileDirectory > get_exif_ifd(sk_sp< SkData > imageData)
static SkColor4f render_gainmap_pixel(float renderHdrRatio, const SkBitmap &baseBitmap, const SkBitmap &gainmapBitmap, const SkGainmapInfo &gainmapInfo, int x, int y)
static std::unique_ptr< SkTiff::ImageFileDirectory > get_ifd(sk_sp< SkData > imageData, uint8_t marker, const void *sig, size_t sigSize, size_t pad)
static std::unique_ptr< SkTiff::ImageFileDirectory > get_mpf_ifd(sk_sp< SkData > imageData)
static const char marker[]
std::unique_ptr< SkStreamAsset > GetResourceAsStream(const char *resource, bool useFileStream)
Definition: Resources.cpp:31
sk_sp< SkData > GetResourceAsData(const char *resource)
Definition: Resources.cpp:42
@ 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 kExifMarker
static constexpr uint32_t kMpfMarker
static constexpr uint8_t kMpfSig[]
static constexpr uint8_t kJpegMarkerStartOfScan
constexpr uint8_t kExifSig[]
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
GLenum type
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)
Definition: SkCanvas.cpp:1673
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
Result
Definition: SkCodec.h:76
@ kSuccess
Definition: SkCodec.h:80
static sk_sp< SkColorSpace > MakeSRGB()
static sk_sp< SkColorSpace > MakeRGB(const skcms_TransferFunction &transferFn, const skcms_Matrix3x3 &toXYZ)
static sk_sp< SkData > MakeWithoutCopy(const void *data, size_t length)
Definition: SkData.h:116
const uint8_t * bytes() const
Definition: SkData.h:43
static sk_sp< SkData > MakeWithCopy(const void *data, size_t length)
Definition: SkData.cpp:111
static sk_sp< SkData > MakeSubset(const SkData *src, size_t offset, size_t length)
Definition: SkData.cpp:173
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
static bool ParseHeader(const SkData *data, bool *outLittleEndian, uint32_t *outIfdOffset)
static std::unique_ptr< ImageFileDirectory > MakeFromOffset(sk_sp< SkData > data, bool littleEndian, uint32_t ifdOffset, bool allowTruncated=false)
T * get() const
Definition: SkRefCnt.h:303
const Paint & paint
Definition: color_source.cc:38
static bool b
struct MyStruct a[10]
uint8_t value
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
double y
double x
constexpr uint16_t kSubIFDOffsetTag
Definition: SkExif.cpp:22
SK_API sk_sp< SkImage > RasterFromBitmap(const SkBitmap &bitmap)
static constexpr skcms_Matrix3x3 kRec2020
Definition: SkColorSpace.h:93
static constexpr skcms_Matrix3x3 kDisplayP3
Definition: SkColorSpace.h:87
static constexpr skcms_TransferFunction kSRGB
Definition: SkColorSpace.h:45
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
const myers::Point & get(const myers::Segment &)
SkSamplingOptions(SkFilterMode::kLinear))
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition: SkVx.h:707
SeparatedVector2 offset
sk_sp< SkData > serialize() const
static bool ParseVersion(const SkData *data)
static bool Parse(const SkData *data, SkGainmapInfo &info)
static sk_sp< SkData > SerializeVersion()
Definition: SkSize.h:16
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
Definition: SkImageInfo.h:421
int width() const
Definition: SkImageInfo.h:365
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
int height() const
Definition: SkImageInfo.h:371
static size_t GetImageAbsoluteOffset(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
float vals[3][3]
Definition: skcms_public.h:27
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63