| /* |
| * Copyright 2020 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkTypes.h" |
| #ifdef SK_ENABLE_NDK_IMAGES |
| #include "include/core/SkColorSpace.h" |
| #include "include/ports/SkImageGeneratorNDK.h" |
| #include "tests/Test.h" |
| #include "tools/Resources.h" |
| #include "tools/ToolUtils.h" |
| |
| #include <vector> |
| |
| static std::unique_ptr<SkImageGenerator> make_generator(const char* path, skiatest::Reporter* r) { |
| auto data = GetResourceAsData(path); |
| if (data) { |
| auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(std::move(data)); |
| if (gen) { |
| return gen; |
| } |
| ERRORF(r, "Failed to create NDK generator from %s\n", path); |
| } else { |
| // Silently fail so developers can skip using --resources |
| } |
| return nullptr; |
| } |
| |
| DEF_TEST(NdkDecode, r) { |
| static const struct { |
| const char* fPath; |
| SkISize fSize; |
| } recs[] = { |
| {"images/CMYK.jpg", {642, 516}}, |
| {"images/arrow.png", {187, 312}}, |
| {"images/baby_tux.webp", {386, 395}}, |
| {"images/color_wheel.gif", {128, 128}}, |
| {"images/rle.bmp", {320, 240}}, |
| {"images/color_wheel.ico", {128, 128}}, |
| {"images/google_chrome.ico", {256, 256}}, |
| {"images/mandrill.wbmp", {512, 512}}, |
| }; |
| for (auto& rec : recs) { |
| auto gen = make_generator(rec.fPath, r); |
| if (!gen) continue; |
| |
| const auto& info = gen->getInfo(); |
| REPORTER_ASSERT(r, info.dimensions() == rec.fSize); |
| |
| SkBitmap bm; |
| bm.allocPixels(info); |
| REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); |
| |
| REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType); |
| auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType); |
| bm.allocPixels(unpremulInfo); |
| REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); |
| } |
| } |
| |
| DEF_TEST(NdkDecode_nullData, r) { |
| auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(nullptr); |
| REPORTER_ASSERT(r, !gen); |
| } |
| |
| static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; |
| |
| static constexpr skcms_Matrix3x3 kDCIP3 = {{ |
| {0.486143, 0.323835, 0.154234}, |
| {0.226676, 0.710327, 0.0629966}, |
| {0.000800549, 0.0432385, 0.78275}, |
| }}; |
| |
| DEF_TEST(NdkDecode_reportedColorSpace, r) { |
| for (sk_sp<SkColorSpace> cs : { |
| sk_sp<SkColorSpace>(nullptr), |
| SkColorSpace::MakeSRGB(), |
| SkColorSpace::MakeSRGBLinear(), |
| SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB), |
| SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020), |
| SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3), |
| SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB), |
| SkColorSpace::MakeRGB(k2Dot6, kDCIP3), |
| }) { |
| SkBitmap bm; |
| bm.allocPixels(SkImageInfo::Make(10, 10, kRGBA_F16_SkColorType, kOpaque_SkAlphaType, cs)); |
| bm.eraseColor(SK_ColorBLUE); |
| |
| for (auto format : { SkEncodedImageFormat::kPNG, |
| SkEncodedImageFormat::kJPEG, |
| SkEncodedImageFormat::kWEBP }) { |
| auto data = SkEncodeBitmap(bm, format, 80); |
| auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(std::move(data)); |
| if (!gen) { |
| ERRORF(r, "Failed to encode!"); |
| return; |
| } |
| |
| if (!cs) cs = SkColorSpace::MakeSRGB(); |
| REPORTER_ASSERT(r, SkColorSpace::Equals(gen->getInfo().colorSpace(), cs.get())); |
| } |
| } |
| } |
| |
| DEF_TEST(NdkDecode_ColorSpace, r) { |
| for (const char* path: { |
| "images/CMYK.jpg", |
| "images/arrow.png", |
| "images/baby_tux.webp", |
| "images/color_wheel.gif", |
| "images/rle.bmp", |
| "images/color_wheel.ico", |
| "images/google_chrome.ico", |
| "images/mandrill.wbmp", |
| }) { |
| auto gen = make_generator(path, r); |
| if (!gen) continue; |
| |
| for (sk_sp<SkColorSpace> cs : { |
| sk_sp<SkColorSpace>(nullptr), |
| SkColorSpace::MakeSRGB(), |
| SkColorSpace::MakeSRGBLinear(), |
| SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB), |
| SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020), |
| SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3), |
| SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB), |
| SkColorSpace::MakeRGB(k2Dot6, kDCIP3), |
| }) { |
| auto info = gen->getInfo().makeColorSpace(cs); |
| |
| SkBitmap bm; |
| bm.allocPixels(info); |
| REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); |
| } |
| |
| std::vector<sk_sp<SkColorSpace>> unsupportedCs; |
| for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, |
| SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { |
| unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut)); |
| unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut)); |
| unsupportedCs.push_back(SkColorSpace::MakeRGB(k2Dot6, gamut)); |
| } |
| |
| for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kDisplayP3, |
| SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { |
| unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gamut)); |
| } |
| |
| for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, |
| SkNamedGamut::kXYZ }) { |
| unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut)); |
| } |
| |
| for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, |
| SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { |
| unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut)); |
| } |
| |
| for (auto gamut : { SkNamedGamut::kAdobeRGB, |
| SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { |
| unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut)); |
| } |
| |
| for (auto fn : { SkNamedTransferFn::kSRGB, SkNamedTransferFn::k2Dot2, |
| SkNamedTransferFn::kLinear, SkNamedTransferFn::kRec2020 }) { |
| unsupportedCs.push_back(SkColorSpace::MakeRGB(fn, kDCIP3)); |
| } |
| |
| for (auto unsupported : unsupportedCs) { |
| auto info = gen->getInfo().makeColorSpace(unsupported); |
| |
| SkBitmap bm; |
| bm.allocPixels(info); |
| REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap())); |
| } |
| } |
| } |
| |
| DEF_TEST(NdkDecode_reuseNoColorSpace, r) { |
| static const struct { |
| const char* fPath; |
| sk_sp<SkColorSpace> fCorrectedColorSpace; |
| bool fIsOpaque; |
| } recs[] = { |
| // AImageDecoder defaults to ADATASPACE_UNKNOWN for this image. |
| {"images/wide_gamut_yellow_224_224_64.jpeg", SkColorSpace::MakeSRGB(), true}, |
| // This image is SRGB, so convert to a different color space. |
| {"images/example_1.png", SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, |
| SkNamedGamut::kAdobeRGB), false}, |
| }; |
| for (auto& rec : recs) { |
| auto gen = make_generator(rec.fPath, r); |
| if (!gen) continue; |
| |
| REPORTER_ASSERT(r, gen->getInfo().colorSpace()->isSRGB()); |
| REPORTER_ASSERT(r, gen->getInfo().isOpaque() == rec.fIsOpaque); |
| |
| auto noColorCorrection = gen->getInfo().makeColorSpace(nullptr); |
| if (rec.fIsOpaque) { |
| // Use something other than the default color type to verify that the modified color |
| // type is used even when the color space is reset. |
| noColorCorrection = noColorCorrection.makeColorType(kRGB_565_SkColorType); |
| } |
| |
| SkBitmap orig; |
| orig.allocPixels(noColorCorrection); |
| REPORTER_ASSERT(r, gen->getPixels(orig.pixmap())); |
| |
| SkBitmap corrected; |
| corrected.allocPixels(noColorCorrection.makeColorSpace(rec.fCorrectedColorSpace)); |
| REPORTER_ASSERT(r, gen->getPixels(corrected.pixmap())); |
| |
| REPORTER_ASSERT(r, !ToolUtils::equal_pixels(orig, corrected)); |
| |
| SkBitmap reuse; |
| reuse.allocPixels(noColorCorrection); |
| REPORTER_ASSERT(r, gen->getPixels(reuse.pixmap())); |
| |
| REPORTER_ASSERT(r, ToolUtils::equal_pixels(orig, reuse)); |
| } |
| } |
| |
| // The NDK supports scaling up to arbitrary dimensions. Skia forces clients to do this in a |
| // separate step, so the client is in charge of how to do the upscale. |
| DEF_TEST(NdkDecode_noUpscale, r) { |
| for (const char* path: { |
| "images/CMYK.jpg", |
| "images/arrow.png", |
| "images/baby_tux.webp", |
| "images/color_wheel.gif", |
| "images/rle.bmp", |
| "images/color_wheel.ico", |
| "images/google_chrome.ico", |
| "images/mandrill.wbmp", |
| }) { |
| auto gen = make_generator(path, r); |
| if (!gen) continue; |
| |
| const auto actualDimensions = gen->getInfo().dimensions(); |
| const int width = actualDimensions.width(); |
| const int height = actualDimensions.height(); |
| for (SkISize dims : { |
| SkISize{width*2, height*2}, |
| SkISize{width + 1, height + 1}, |
| }) { |
| auto info = gen->getInfo().makeDimensions(dims); |
| SkBitmap bm; |
| bm.allocPixels(info); |
| REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap())); |
| } |
| } |
| } |
| |
| // libwebp supports downscaling to an arbitrary scale factor, and this is supported by the NDK. |
| DEF_TEST(NdkDecode_webpArbitraryDownscale, r) { |
| for (const char* path: { |
| "images/baby_tux.webp", |
| "images/yellow_rose.webp", |
| "images/webp-color-profile-lossless.webp", |
| }) { |
| auto gen = make_generator(path, r); |
| if (!gen) continue; |
| |
| const auto actualDimensions = gen->getInfo().dimensions(); |
| const int width = actualDimensions.width(); |
| const int height = actualDimensions.height(); |
| for (SkISize dims : { |
| SkISize{width/2, height/2}, |
| SkISize{width/4, height/4}, |
| SkISize{width/7, height/7}, |
| SkISize{width - 1, height - 1}, |
| SkISize{1, 1}, |
| SkISize{5, 20} |
| }) { |
| auto info = gen->getInfo().makeDimensions(dims); |
| SkBitmap bm; |
| bm.allocPixels(info); |
| REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); |
| |
| REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType); |
| auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType); |
| bm.allocPixels(unpremulInfo); |
| REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); |
| } |
| } |
| } |
| |
| // libjpeg-turbo supports downscaling to some scale factors. |
| DEF_TEST(NdkDecode_jpegDownscale, r) { |
| static const struct { |
| const char* fPath; |
| SkISize fSupportedSizes[4]; |
| } recs[] = { |
| {"images/CMYK.jpg", {{642,516},{321,258},{161,129},{81,65}}}, |
| {"images/dog.jpg", {{180,180},{90,90},{45,45},{23,23}}}, |
| {"images/grayscale.jpg", {{128,128},{64,64},{32,32},{16,16}}}, |
| {"images/brickwork-texture.jpg", {{512,512},{256,256},{128,128},{64,64}}}, |
| {"images/mandrill_h2v1.jpg", {{512,512},{256,256},{128,128},{64,64}}}, |
| {"images/ducky.jpg", {{489,537},{245,269},{123,135},{62,68}}}, |
| }; |
| for (auto& rec : recs) { |
| auto gen = make_generator(rec.fPath, r); |
| if (!gen) continue; |
| |
| for (SkISize dims : rec.fSupportedSizes) { |
| auto info = gen->getInfo().makeDimensions(dims); |
| SkBitmap bm; |
| bm.allocPixels(info); |
| if (!gen->getPixels(bm.pixmap())) { |
| ERRORF(r, "failed to decode %s to {%i,%i}\n", rec.fPath, dims.width(), |
| dims.height()); |
| } |
| |
| REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType); |
| auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType); |
| bm.allocPixels(unpremulInfo); |
| REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); |
| } |
| } |
| } |
| |
| DEF_TEST(NdkDecode_reuseJpeg, r) { |
| auto gen = make_generator("images/CMYK.jpg", r); |
| if (!gen) return; |
| |
| SkImageInfo info = gen->getInfo(); |
| SkBitmap orig; |
| orig.allocPixels(info); |
| REPORTER_ASSERT(r, gen->getPixels(orig.pixmap())); |
| |
| info = info.makeWH(321, 258); |
| SkBitmap downscaled; |
| downscaled.allocPixels(info); |
| REPORTER_ASSERT(r, gen->getPixels(downscaled.pixmap())); |
| |
| SkBitmap reuse; |
| reuse.allocPixels(gen->getInfo()); |
| REPORTER_ASSERT(r, gen->getPixels(reuse.pixmap())); |
| |
| REPORTER_ASSERT(r, ToolUtils::equal_pixels(orig, reuse)); |
| } |
| |
| // The NDK supports scaling down to arbitrary dimensions. Skia forces clients to do this in a |
| // separate step, so the client is in charge of how to do the downscale. |
| DEF_TEST(NdkDecode_noDownscale, r) { |
| for (const char* path: { |
| "images/arrow.png", |
| "images/color_wheel.gif", |
| "images/rle.bmp", |
| "images/color_wheel.ico", |
| "images/google_chrome.ico", |
| "images/mandrill.wbmp", |
| }) { |
| auto gen = make_generator(path, r); |
| if (!gen) continue; |
| |
| const auto actualDimensions = gen->getInfo().dimensions(); |
| const int width = actualDimensions.width(); |
| const int height = actualDimensions.height(); |
| for (SkISize dims : { |
| SkISize{width/2, height/2}, |
| SkISize{width/3, height/3}, |
| SkISize{width/4, height/4}, |
| SkISize{width/8, height/8}, |
| SkISize{width - 1, height - 1}, |
| }) { |
| auto info = gen->getInfo().makeDimensions(dims); |
| SkBitmap bm; |
| bm.allocPixels(info); |
| REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap())); |
| } |
| } |
| } |
| |
| DEF_TEST(NdkDecode_Gray8, r) { |
| static const struct { |
| const char* fPath; |
| bool fGrayscale; |
| } recs[] = { |
| {"images/CMYK.jpg", false}, |
| {"images/arrow.png", false}, |
| {"images/baby_tux.webp", false}, |
| {"images/color_wheel.gif", false}, |
| {"images/rle.bmp", false}, |
| {"images/color_wheel.ico", false}, |
| {"images/google_chrome.ico", false}, |
| {"images/mandrill.wbmp", true}, |
| {"images/grayscale.jpg", true}, |
| {"images/grayscale.png", true}, |
| }; |
| for (auto& rec : recs) { |
| auto gen = make_generator(rec.fPath, r); |
| if (!gen) continue; |
| |
| SkImageInfo info = gen->getInfo(); |
| if (rec.fGrayscale) { |
| REPORTER_ASSERT(r, info.colorType() == kGray_8_SkColorType); |
| REPORTER_ASSERT(r, info.alphaType() == kOpaque_SkAlphaType); |
| } else { |
| info = info.makeColorType(kGray_8_SkColorType); |
| } |
| SkBitmap bm; |
| bm.allocPixels(info); |
| bool success = gen->getPixels(bm.pixmap()); |
| if (success != rec.fGrayscale) { |
| ERRORF(r, "Expected decoding %s to Gray8 to %s. Actual: %s\n", rec.fPath, |
| (rec.fGrayscale ? "succeed" : "fail"), (success ? "succeed" : "fail")); |
| } |
| } |
| } |
| |
| DEF_TEST(NdkDecode_Opaque_and_565, r) { |
| for (const char* path: { |
| "images/CMYK.jpg", |
| "images/dog.jpg", |
| "images/ducky.jpg", |
| "images/arrow.png", |
| "images/example_1.png", |
| "images/explosion_sprites.png", |
| "images/lut_identity.png", |
| "images/grayscale.png", |
| "images/baby_tux.webp", |
| "images/yellow_rose.webp", |
| "images/webp-color-profile-lossless.webp", |
| "images/colorTables.gif", |
| "images/color_wheel.gif", |
| "images/flightAnim.gif", |
| "images/randPixels.gif", |
| "images/rle.bmp", |
| "images/color_wheel.ico", |
| "images/google_chrome.ico", |
| "images/mandrill.wbmp", |
| }) { |
| auto gen = make_generator(path, r); |
| if (!gen) continue; |
| |
| auto info = gen->getInfo().makeAlphaType(kOpaque_SkAlphaType); |
| SkBitmap bm; |
| bm.allocPixels(info); |
| bool success = gen->getPixels(bm.pixmap()); |
| REPORTER_ASSERT(r, success == gen->getInfo().isOpaque()); |
| |
| info = info.makeColorType(kRGB_565_SkColorType); |
| bm.allocPixels(info); |
| success = gen->getPixels(bm.pixmap()); |
| REPORTER_ASSERT(r, success == gen->getInfo().isOpaque()); |
| } |
| } |
| |
| DEF_TEST(NdkDecode_AlwaysSupportedColorTypes, r) { |
| for (const char* path: { |
| "images/CMYK.jpg", |
| "images/dog.jpg", |
| "images/ducky.jpg", |
| "images/arrow.png", |
| "images/example_1.png", |
| "images/explosion_sprites.png", |
| "images/lut_identity.png", |
| "images/grayscale.png", |
| "images/baby_tux.webp", |
| "images/yellow_rose.webp", |
| "images/webp-color-profile-lossless.webp", |
| "images/colorTables.gif", |
| "images/color_wheel.gif", |
| "images/flightAnim.gif", |
| "images/randPixels.gif", |
| "images/rle.bmp", |
| "images/color_wheel.ico", |
| "images/google_chrome.ico", |
| "images/mandrill.wbmp", |
| }) { |
| auto gen = make_generator(path, r); |
| if (!gen) continue; |
| |
| auto info = gen->getInfo().makeColorType(kRGBA_F16_SkColorType); |
| SkBitmap bm; |
| bm.allocPixels(info); |
| REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); |
| |
| // This also tests that we can reuse the same generator for a different |
| // color type. |
| info = info.makeColorType(kRGBA_8888_SkColorType); |
| bm.allocPixels(info); |
| REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); |
| } |
| } |
| |
| DEF_TEST(NdkDecode_UnsupportedColorTypes, r) { |
| for (const char* path: { |
| "images/CMYK.jpg", |
| "images/dog.jpg", |
| "images/ducky.jpg", |
| "images/arrow.png", |
| "images/example_1.png", |
| "images/explosion_sprites.png", |
| "images/lut_identity.png", |
| "images/grayscale.png", |
| "images/baby_tux.webp", |
| "images/yellow_rose.webp", |
| "images/webp-color-profile-lossless.webp", |
| "images/colorTables.gif", |
| "images/color_wheel.gif", |
| "images/flightAnim.gif", |
| "images/randPixels.gif", |
| "images/rle.bmp", |
| "images/color_wheel.ico", |
| "images/google_chrome.ico", |
| "images/mandrill.wbmp", |
| }) { |
| auto gen = make_generator(path, r); |
| if (!gen) continue; |
| |
| for (SkColorType ct : { |
| kUnknown_SkColorType, |
| kAlpha_8_SkColorType, |
| kARGB_4444_SkColorType, |
| kRGB_888x_SkColorType, |
| kBGRA_8888_SkColorType, |
| kRGBA_1010102_SkColorType, |
| kBGRA_1010102_SkColorType, |
| kRGB_101010x_SkColorType, |
| kBGR_101010x_SkColorType, |
| kRGBA_F16Norm_SkColorType, |
| kRGBA_F32_SkColorType, |
| kR8G8_unorm_SkColorType, |
| kA16_float_SkColorType, |
| kR16G16_float_SkColorType, |
| kA16_unorm_SkColorType, |
| kR16G16_unorm_SkColorType, |
| kR16G16B16A16_unorm_SkColorType, |
| }) { |
| auto info = gen->getInfo().makeColorType(ct); |
| SkBitmap bm; |
| bm.allocPixels(info); |
| if (gen->getPixels(bm.pixmap())) { |
| ERRORF(r, "Expected decoding %s to %i to fail!", path, ct); |
| } |
| } |
| } |
| } |
| #endif // SK_ENABLE_NDK_IMAGES |