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