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