xref: /third_party/skia/tests/NdkDecodeTest.cpp (revision cb93a386)
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
17static 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
31DEF_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
63DEF_TEST(NdkDecode_nullData, r) {
64    auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(nullptr);
65    REPORTER_ASSERT(r, !gen);
66}
67
68static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
69
70static 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
76DEF_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
107DEF_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
181DEF_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.
227DEF_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.
257DEF_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.
291DEF_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
324DEF_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.
347DEF_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
377DEF_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
414DEF_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
452DEF_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
490DEF_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