1 /*
2 * Copyright 2020 Google LLC
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/core/SkTypes.h"
9 #ifdef SK_ENABLE_NDK_IMAGES
10 #include "include/ports/SkImageGeneratorNDK.h"
11 #include "tests/Test.h"
12 #include "tools/Resources.h"
13 #include "tools/ToolUtils.h"
14
15 #include <vector>
16
make_generator(const char * path,skiatest::Reporter * r)17 static std::unique_ptr<SkImageGenerator> make_generator(const char* path, skiatest::Reporter* r) {
18 auto data = GetResourceAsData(path);
19 if (data) {
20 auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(std::move(data));
21 if (gen) {
22 return gen;
23 }
24 ERRORF(r, "Failed to create NDK generator from %s\n", path);
25 } else {
26 // Silently fail so developers can skip using --resources
27 }
28 return nullptr;
29 }
30
DEF_TEST(NdkDecode,r)31 DEF_TEST(NdkDecode, r) {
32 static const struct {
33 const char* fPath;
34 SkISize fSize;
35 } recs[] = {
36 {"images/CMYK.jpg", {642, 516}},
37 {"images/arrow.png", {187, 312}},
38 {"images/baby_tux.webp", {386, 395}},
39 {"images/color_wheel.gif", {128, 128}},
40 {"images/rle.bmp", {320, 240}},
41 {"images/color_wheel.ico", {128, 128}},
42 {"images/google_chrome.ico", {256, 256}},
43 {"images/mandrill.wbmp", {512, 512}},
44 };
45 for (auto& rec : recs) {
46 auto gen = make_generator(rec.fPath, r);
47 if (!gen) continue;
48
49 const auto& info = gen->getInfo();
50 REPORTER_ASSERT(r, info.dimensions() == rec.fSize);
51
52 SkBitmap bm;
53 bm.allocPixels(info);
54 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap()));
55
56 REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType);
57 auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType);
58 bm.allocPixels(unpremulInfo);
59 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap()));
60 }
61 }
62
DEF_TEST(NdkDecode_nullData,r)63 DEF_TEST(NdkDecode_nullData, r) {
64 auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(nullptr);
65 REPORTER_ASSERT(r, !gen);
66 }
67
68 static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
69
70 static constexpr skcms_Matrix3x3 kDCIP3 = {{
71 {0.486143, 0.323835, 0.154234},
72 {0.226676, 0.710327, 0.0629966},
73 {0.000800549, 0.0432385, 0.78275},
74 }};
75
DEF_TEST(NdkDecode_reportedColorSpace,r)76 DEF_TEST(NdkDecode_reportedColorSpace, r) {
77 for (sk_sp<SkColorSpace> cs : {
78 sk_sp<SkColorSpace>(nullptr),
79 SkColorSpace::MakeSRGB(),
80 SkColorSpace::MakeSRGBLinear(),
81 SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB),
82 SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020),
83 SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3),
84 SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB),
85 SkColorSpace::MakeRGB(k2Dot6, kDCIP3),
86 }) {
87 SkBitmap bm;
88 bm.allocPixels(SkImageInfo::Make(10, 10, kRGBA_F16_SkColorType, kOpaque_SkAlphaType, cs));
89 bm.eraseColor(SK_ColorBLUE);
90
91 for (auto format : { SkEncodedImageFormat::kPNG,
92 SkEncodedImageFormat::kJPEG,
93 SkEncodedImageFormat::kWEBP }) {
94 auto data = SkEncodeBitmap(bm, format, 80);
95 auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(std::move(data));
96 if (!gen) {
97 ERRORF(r, "Failed to encode!");
98 return;
99 }
100
101 if (!cs) cs = SkColorSpace::MakeSRGB();
102 REPORTER_ASSERT(r, SkColorSpace::Equals(gen->getInfo().colorSpace(), cs.get()));
103 }
104 }
105 }
106
DEF_TEST(NdkDecode_ColorSpace,r)107 DEF_TEST(NdkDecode_ColorSpace, r) {
108 for (const char* path: {
109 "images/CMYK.jpg",
110 "images/arrow.png",
111 "images/baby_tux.webp",
112 "images/color_wheel.gif",
113 "images/rle.bmp",
114 "images/color_wheel.ico",
115 "images/google_chrome.ico",
116 "images/mandrill.wbmp",
117 }) {
118 auto gen = make_generator(path, r);
119 if (!gen) continue;
120
121 for (sk_sp<SkColorSpace> cs : {
122 sk_sp<SkColorSpace>(nullptr),
123 SkColorSpace::MakeSRGB(),
124 SkColorSpace::MakeSRGBLinear(),
125 SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB),
126 SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020),
127 SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3),
128 SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB),
129 SkColorSpace::MakeRGB(k2Dot6, kDCIP3),
130 }) {
131 auto info = gen->getInfo().makeColorSpace(cs);
132
133 SkBitmap bm;
134 bm.allocPixels(info);
135 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap()));
136 }
137
138 std::vector<sk_sp<SkColorSpace>> unsupportedCs;
139 for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3,
140 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) {
141 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut));
142 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut));
143 unsupportedCs.push_back(SkColorSpace::MakeRGB(k2Dot6, gamut));
144 }
145
146 for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kDisplayP3,
147 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) {
148 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gamut));
149 }
150
151 for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3,
152 SkNamedGamut::kXYZ }) {
153 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut));
154 }
155
156 for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3,
157 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) {
158 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut));
159 }
160
161 for (auto gamut : { SkNamedGamut::kAdobeRGB,
162 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) {
163 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut));
164 }
165
166 for (auto fn : { SkNamedTransferFn::kSRGB, SkNamedTransferFn::k2Dot2,
167 SkNamedTransferFn::kLinear, SkNamedTransferFn::kRec2020 }) {
168 unsupportedCs.push_back(SkColorSpace::MakeRGB(fn, kDCIP3));
169 }
170
171 for (auto unsupported : unsupportedCs) {
172 auto info = gen->getInfo().makeColorSpace(unsupported);
173
174 SkBitmap bm;
175 bm.allocPixels(info);
176 REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap()));
177 }
178 }
179 }
180
DEF_TEST(NdkDecode_reuseNoColorSpace,r)181 DEF_TEST(NdkDecode_reuseNoColorSpace, r) {
182 static const struct {
183 const char* fPath;
184 sk_sp<SkColorSpace> fCorrectedColorSpace;
185 bool fIsOpaque;
186 } recs[] = {
187 // AImageDecoder defaults to ADATASPACE_UNKNOWN for this image.
188 {"images/wide_gamut_yellow_224_224_64.jpeg", SkColorSpace::MakeSRGB(), true},
189 // This image is SRGB, so convert to a different color space.
190 {"images/example_1.png", SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2,
191 SkNamedGamut::kAdobeRGB), false},
192 };
193 for (auto& rec : recs) {
194 auto gen = make_generator(rec.fPath, r);
195 if (!gen) continue;
196
197 REPORTER_ASSERT(r, gen->getInfo().colorSpace()->isSRGB());
198 REPORTER_ASSERT(r, gen->getInfo().isOpaque() == rec.fIsOpaque);
199
200 auto noColorCorrection = gen->getInfo().makeColorSpace(nullptr);
201 if (rec.fIsOpaque) {
202 // Use something other than the default color type to verify that the modified color
203 // type is used even when the color space is reset.
204 noColorCorrection = noColorCorrection.makeColorType(kRGB_565_SkColorType);
205 }
206
207 SkBitmap orig;
208 orig.allocPixels(noColorCorrection);
209 REPORTER_ASSERT(r, gen->getPixels(orig.pixmap()));
210
211 SkBitmap corrected;
212 corrected.allocPixels(noColorCorrection.makeColorSpace(rec.fCorrectedColorSpace));
213 REPORTER_ASSERT(r, gen->getPixels(corrected.pixmap()));
214
215 REPORTER_ASSERT(r, !ToolUtils::equal_pixels(orig, corrected));
216
217 SkBitmap reuse;
218 reuse.allocPixels(noColorCorrection);
219 REPORTER_ASSERT(r, gen->getPixels(reuse.pixmap()));
220
221 REPORTER_ASSERT(r, ToolUtils::equal_pixels(orig, reuse));
222 }
223 }
224
225 // The NDK supports scaling up to arbitrary dimensions. Skia forces clients to do this in a
226 // separate step, so the client is in charge of how to do the upscale.
DEF_TEST(NdkDecode_noUpscale,r)227 DEF_TEST(NdkDecode_noUpscale, r) {
228 for (const char* path: {
229 "images/CMYK.jpg",
230 "images/arrow.png",
231 "images/baby_tux.webp",
232 "images/color_wheel.gif",
233 "images/rle.bmp",
234 "images/color_wheel.ico",
235 "images/google_chrome.ico",
236 "images/mandrill.wbmp",
237 }) {
238 auto gen = make_generator(path, r);
239 if (!gen) continue;
240
241 const auto actualDimensions = gen->getInfo().dimensions();
242 const int width = actualDimensions.width();
243 const int height = actualDimensions.height();
244 for (SkISize dims : {
245 SkISize{width*2, height*2},
246 SkISize{width + 1, height + 1},
247 }) {
248 auto info = gen->getInfo().makeDimensions(dims);
249 SkBitmap bm;
250 bm.allocPixels(info);
251 REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap()));
252 }
253 }
254 }
255
256 // libwebp supports downscaling to an arbitrary scale factor, and this is supported by the NDK.
DEF_TEST(NdkDecode_webpArbitraryDownscale,r)257 DEF_TEST(NdkDecode_webpArbitraryDownscale, r) {
258 for (const char* path: {
259 "images/baby_tux.webp",
260 "images/yellow_rose.webp",
261 "images/webp-color-profile-lossless.webp",
262 }) {
263 auto gen = make_generator(path, r);
264 if (!gen) continue;
265
266 const auto actualDimensions = gen->getInfo().dimensions();
267 const int width = actualDimensions.width();
268 const int height = actualDimensions.height();
269 for (SkISize dims : {
270 SkISize{width/2, height/2},
271 SkISize{width/4, height/4},
272 SkISize{width/7, height/7},
273 SkISize{width - 1, height - 1},
274 SkISize{1, 1},
275 SkISize{5, 20}
276 }) {
277 auto info = gen->getInfo().makeDimensions(dims);
278 SkBitmap bm;
279 bm.allocPixels(info);
280 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap()));
281
282 REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType);
283 auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType);
284 bm.allocPixels(unpremulInfo);
285 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap()));
286 }
287 }
288 }
289
290 // libjpeg-turbo supports downscaling to some scale factors.
DEF_TEST(NdkDecode_jpegDownscale,r)291 DEF_TEST(NdkDecode_jpegDownscale, r) {
292 static const struct {
293 const char* fPath;
294 SkISize fSupportedSizes[4];
295 } recs[] = {
296 {"images/CMYK.jpg", {{642,516},{321,258},{161,129},{81,65}}},
297 {"images/dog.jpg", {{180,180},{90,90},{45,45},{23,23}}},
298 {"images/grayscale.jpg", {{128,128},{64,64},{32,32},{16,16}}},
299 {"images/brickwork-texture.jpg", {{512,512},{256,256},{128,128},{64,64}}},
300 {"images/mandrill_h2v1.jpg", {{512,512},{256,256},{128,128},{64,64}}},
301 {"images/ducky.jpg", {{489,537},{245,269},{123,135},{62,68}}},
302 };
303 for (auto& rec : recs) {
304 auto gen = make_generator(rec.fPath, r);
305 if (!gen) continue;
306
307 for (SkISize dims : rec.fSupportedSizes) {
308 auto info = gen->getInfo().makeDimensions(dims);
309 SkBitmap bm;
310 bm.allocPixels(info);
311 if (!gen->getPixels(bm.pixmap())) {
312 ERRORF(r, "failed to decode %s to {%i,%i}\n", rec.fPath, dims.width(),
313 dims.height());
314 }
315
316 REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType);
317 auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType);
318 bm.allocPixels(unpremulInfo);
319 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap()));
320 }
321 }
322 }
323
DEF_TEST(NdkDecode_reuseJpeg,r)324 DEF_TEST(NdkDecode_reuseJpeg, r) {
325 auto gen = make_generator("images/CMYK.jpg", r);
326 if (!gen) return;
327
328 SkImageInfo info = gen->getInfo();
329 SkBitmap orig;
330 orig.allocPixels(info);
331 REPORTER_ASSERT(r, gen->getPixels(orig.pixmap()));
332
333 info = info.makeWH(321, 258);
334 SkBitmap downscaled;
335 downscaled.allocPixels(info);
336 REPORTER_ASSERT(r, gen->getPixels(downscaled.pixmap()));
337
338 SkBitmap reuse;
339 reuse.allocPixels(gen->getInfo());
340 REPORTER_ASSERT(r, gen->getPixels(reuse.pixmap()));
341
342 REPORTER_ASSERT(r, ToolUtils::equal_pixels(orig, reuse));
343 }
344
345 // The NDK supports scaling down to arbitrary dimensions. Skia forces clients to do this in a
346 // separate step, so the client is in charge of how to do the downscale.
DEF_TEST(NdkDecode_noDownscale,r)347 DEF_TEST(NdkDecode_noDownscale, r) {
348 for (const char* path: {
349 "images/arrow.png",
350 "images/color_wheel.gif",
351 "images/rle.bmp",
352 "images/color_wheel.ico",
353 "images/google_chrome.ico",
354 "images/mandrill.wbmp",
355 }) {
356 auto gen = make_generator(path, r);
357 if (!gen) continue;
358
359 const auto actualDimensions = gen->getInfo().dimensions();
360 const int width = actualDimensions.width();
361 const int height = actualDimensions.height();
362 for (SkISize dims : {
363 SkISize{width/2, height/2},
364 SkISize{width/3, height/3},
365 SkISize{width/4, height/4},
366 SkISize{width/8, height/8},
367 SkISize{width - 1, height - 1},
368 }) {
369 auto info = gen->getInfo().makeDimensions(dims);
370 SkBitmap bm;
371 bm.allocPixels(info);
372 REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap()));
373 }
374 }
375 }
376
DEF_TEST(NdkDecode_Gray8,r)377 DEF_TEST(NdkDecode_Gray8, r) {
378 static const struct {
379 const char* fPath;
380 bool fGrayscale;
381 } recs[] = {
382 {"images/CMYK.jpg", false},
383 {"images/arrow.png", false},
384 {"images/baby_tux.webp", false},
385 {"images/color_wheel.gif", false},
386 {"images/rle.bmp", false},
387 {"images/color_wheel.ico", false},
388 {"images/google_chrome.ico", false},
389 {"images/mandrill.wbmp", true},
390 {"images/grayscale.jpg", true},
391 {"images/grayscale.png", true},
392 };
393 for (auto& rec : recs) {
394 auto gen = make_generator(rec.fPath, r);
395 if (!gen) continue;
396
397 SkImageInfo info = gen->getInfo();
398 if (rec.fGrayscale) {
399 REPORTER_ASSERT(r, info.colorType() == kGray_8_SkColorType);
400 REPORTER_ASSERT(r, info.alphaType() == kOpaque_SkAlphaType);
401 } else {
402 info = info.makeColorType(kGray_8_SkColorType);
403 }
404 SkBitmap bm;
405 bm.allocPixels(info);
406 bool success = gen->getPixels(bm.pixmap());
407 if (success != rec.fGrayscale) {
408 ERRORF(r, "Expected decoding %s to Gray8 to %s. Actual: %s\n", rec.fPath,
409 (rec.fGrayscale ? "succeed" : "fail"), (success ? "succeed" : "fail"));
410 }
411 }
412 }
413
DEF_TEST(NdkDecode_Opaque_and_565,r)414 DEF_TEST(NdkDecode_Opaque_and_565, r) {
415 for (const char* path: {
416 "images/CMYK.jpg",
417 "images/dog.jpg",
418 "images/ducky.jpg",
419 "images/arrow.png",
420 "images/example_1.png",
421 "images/explosion_sprites.png",
422 "images/lut_identity.png",
423 "images/grayscale.png",
424 "images/baby_tux.webp",
425 "images/yellow_rose.webp",
426 "images/webp-color-profile-lossless.webp",
427 "images/colorTables.gif",
428 "images/color_wheel.gif",
429 "images/flightAnim.gif",
430 "images/randPixels.gif",
431 "images/rle.bmp",
432 "images/color_wheel.ico",
433 "images/google_chrome.ico",
434 "images/mandrill.wbmp",
435 }) {
436 auto gen = make_generator(path, r);
437 if (!gen) continue;
438
439 auto info = gen->getInfo().makeAlphaType(kOpaque_SkAlphaType);
440 SkBitmap bm;
441 bm.allocPixels(info);
442 bool success = gen->getPixels(bm.pixmap());
443 REPORTER_ASSERT(r, success == gen->getInfo().isOpaque());
444
445 info = info.makeColorType(kRGB_565_SkColorType);
446 bm.allocPixels(info);
447 success = gen->getPixels(bm.pixmap());
448 REPORTER_ASSERT(r, success == gen->getInfo().isOpaque());
449 }
450 }
451
DEF_TEST(NdkDecode_AlwaysSupportedColorTypes,r)452 DEF_TEST(NdkDecode_AlwaysSupportedColorTypes, r) {
453 for (const char* path: {
454 "images/CMYK.jpg",
455 "images/dog.jpg",
456 "images/ducky.jpg",
457 "images/arrow.png",
458 "images/example_1.png",
459 "images/explosion_sprites.png",
460 "images/lut_identity.png",
461 "images/grayscale.png",
462 "images/baby_tux.webp",
463 "images/yellow_rose.webp",
464 "images/webp-color-profile-lossless.webp",
465 "images/colorTables.gif",
466 "images/color_wheel.gif",
467 "images/flightAnim.gif",
468 "images/randPixels.gif",
469 "images/rle.bmp",
470 "images/color_wheel.ico",
471 "images/google_chrome.ico",
472 "images/mandrill.wbmp",
473 }) {
474 auto gen = make_generator(path, r);
475 if (!gen) continue;
476
477 auto info = gen->getInfo().makeColorType(kRGBA_F16_SkColorType);
478 SkBitmap bm;
479 bm.allocPixels(info);
480 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap()));
481
482 // This also tests that we can reuse the same generator for a different
483 // color type.
484 info = info.makeColorType(kRGBA_8888_SkColorType);
485 bm.allocPixels(info);
486 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap()));
487 }
488 }
489
DEF_TEST(NdkDecode_UnsupportedColorTypes,r)490 DEF_TEST(NdkDecode_UnsupportedColorTypes, r) {
491 for (const char* path: {
492 "images/CMYK.jpg",
493 "images/dog.jpg",
494 "images/ducky.jpg",
495 "images/arrow.png",
496 "images/example_1.png",
497 "images/explosion_sprites.png",
498 "images/lut_identity.png",
499 "images/grayscale.png",
500 "images/baby_tux.webp",
501 "images/yellow_rose.webp",
502 "images/webp-color-profile-lossless.webp",
503 "images/colorTables.gif",
504 "images/color_wheel.gif",
505 "images/flightAnim.gif",
506 "images/randPixels.gif",
507 "images/rle.bmp",
508 "images/color_wheel.ico",
509 "images/google_chrome.ico",
510 "images/mandrill.wbmp",
511 }) {
512 auto gen = make_generator(path, r);
513 if (!gen) continue;
514
515 for (SkColorType ct : {
516 kUnknown_SkColorType,
517 kAlpha_8_SkColorType,
518 kARGB_4444_SkColorType,
519 kRGB_888x_SkColorType,
520 kBGRA_8888_SkColorType,
521 kRGBA_1010102_SkColorType,
522 kBGRA_1010102_SkColorType,
523 kRGB_101010x_SkColorType,
524 kBGR_101010x_SkColorType,
525 kRGBA_F16Norm_SkColorType,
526 kRGBA_F32_SkColorType,
527 kR8G8_unorm_SkColorType,
528 kA16_float_SkColorType,
529 kR16G16_float_SkColorType,
530 kA16_unorm_SkColorType,
531 kR16G16_unorm_SkColorType,
532 kR16G16B16A16_unorm_SkColorType,
533 }) {
534 auto info = gen->getInfo().makeColorType(ct);
535 SkBitmap bm;
536 bm.allocPixels(info);
537 if (gen->getPixels(bm.pixmap())) {
538 ERRORF(r, "Expected decoding %s to %i to fail!", path, ct);
539 }
540 }
541 }
542 }
543 #endif // SK_ENABLE_NDK_IMAGES
544