/* * 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/SkColor.h" #include "include/core/SkColorSpace.h" #include "include/core/SkImageEncoder.h" #include "include/core/SkImageGenerator.h" #include "include/private/SkMalloc.h" #include "src/images/SkImageEncoderPriv.h" #include "tests/Test.h" #include "tools/Resources.h" #include "tools/ToolUtils.h" #include #include static const char* kPng = "png"; static const char* kJpeg = "jpeg"; static const char* kWebpLossless = "webp_lossless"; static const char* kWebpLossy = "webp_lossy"; namespace { static const struct { SkEncodedImageFormat format; int quality; const char* name; } gRecs[] = { { SkEncodedImageFormat::kPNG, 100, kPng}, { SkEncodedImageFormat::kJPEG, 100, kJpeg}, { SkEncodedImageFormat::kWEBP, 100, kWebpLossless}, { SkEncodedImageFormat::kWEBP, 80, kWebpLossy}, }; } static sk_sp encode_ndk(const SkPixmap& pmap, SkEncodedImageFormat format, int quality) { SkDynamicMemoryWStream stream; return SkEncodeImageWithNDK(&stream, pmap, format, quality) ? stream.detachAsData() : nullptr; } DEF_TEST(NdkEncode, r) { for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) { SkBitmap bm; bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType)); bm.eraseColor(SK_ColorBLUE); for (const auto& rec : gRecs) { auto encoded = encode_ndk(bm.pixmap(), rec.format, rec.quality); if (!encoded) { ERRORF(r, "Failed to encode %s to %s\n", ToolUtils::colortype_name(ct), rec.name); continue; } auto gen = SkImageGenerator::MakeFromEncoded(std::move(encoded)); if (!gen) { ERRORF(r, "Failed to decode from %s as %s\n", ToolUtils::colortype_name(ct), rec.name); continue; } if (rec.name == kPng && bm.colorType() == kRGB_565_SkColorType) { REPORTER_ASSERT(r, gen->getInfo().colorType() == kRGB_565_SkColorType); } else { REPORTER_ASSERT(r, gen->getInfo().colorType() == kN32_SkColorType); } SkBitmap bm2; bm2.allocPixels(bm.info()); REPORTER_ASSERT(r, gen->getPixels(bm2.pixmap())); for (int x = 0; x < bm.width(); x++) for (int y = 0; y < bm.height(); y++) { SkColor orig = bm .getColor(x, y); SkColor actual = bm2.getColor(x, y); REPORTER_ASSERT(r, SkColorGetA(orig) == SkColorGetA(actual)); REPORTER_ASSERT(r, SkColorGetA(orig) == 0xFF); if (rec.name == kPng || rec.name == kWebpLossless) { REPORTER_ASSERT(r, orig == actual); } else { int diffR = std::abs((int) SkColorGetR(orig) - (int) SkColorGetR(actual)); int diffG = std::abs((int) SkColorGetG(orig) - (int) SkColorGetG(actual)); int diffB = std::abs((int) SkColorGetB(orig) - (int) SkColorGetB(actual)); REPORTER_ASSERT(r, diffR <= 2 && diffG <= 1 && diffB <= 1); } } } } } DEF_TEST(NdkEncode_unsupportedFormats, r) { for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) { SkBitmap bm; bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType)); bm.eraseColor(SK_ColorBLUE); for (auto format : { SkEncodedImageFormat::kBMP, SkEncodedImageFormat::kGIF, SkEncodedImageFormat::kICO, SkEncodedImageFormat::kWBMP, SkEncodedImageFormat::kPKM, SkEncodedImageFormat::kKTX, SkEncodedImageFormat::kASTC, SkEncodedImageFormat::kDNG, SkEncodedImageFormat::kHEIF }) { REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), format, 100)); } } } DEF_TEST(NdkEncode_badQuality, r) { for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) { SkBitmap bm; bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType)); bm.eraseColor(SK_ColorBLUE); for (auto format : { SkEncodedImageFormat::kJPEG, SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kWEBP }) { for (int quality : {-1, -100, 101, 200}) { REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), format, quality)); } } } } DEF_TEST(NdkEncode_nullPixels, r) { for (auto info : { SkImageInfo::MakeUnknown(), SkImageInfo::MakeN32Premul(10, 10), SkImageInfo::MakeN32Premul(0, 0)}) { SkPixmap pm(info, nullptr, info.minRowBytes()); for (const auto& rec : gRecs) { REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality)); } } } DEF_TEST(NdkEncode_badInfo, r) { // Allocate an arbitrary amount of memory. These infos don't have a natural // amount to allocate, and the encoder shouldn't touch the memory anyway. // But this allows us to verify that the bad info fails, even when the pixel // pointer is not null. void* pixels = sk_malloc_throw(1024); std::vector pixmaps{ SkPixmap(SkImageInfo::MakeN32Premul(-10, 10), pixels, 1000), SkPixmap(SkImageInfo::MakeN32Premul(10, -10), pixels, 200), SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 20), SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 41), SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 0), SkPixmap(SkImageInfo::MakeN32Premul( 0, 0), pixels, 40)}; if (sizeof(size_t) > sizeof(uint32_t)) { pixmaps.emplace_back(SkImageInfo::MakeN32Premul(10, 10), pixels, static_cast(UINT32_MAX) + 1); } for (const auto& pm : pixmaps) { for (const auto& rec : gRecs) { REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality)); } } free(pixels); } DEF_TEST(NdkEncode_unsupportedColorTypes, r) { 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, kGray_8_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 = SkImageInfo::Make(7, 13, ct, kOpaque_SkAlphaType, SkColorSpace::MakeSRGB()); SkBitmap bm; bm.allocPixels(info); bm.eraseColor(SK_ColorGREEN); for (const auto& rec : gRecs) { REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality)); } if (!SkColorTypeIsAlwaysOpaque(ct)) { for (auto at : { kPremul_SkAlphaType, kUnpremul_SkAlphaType}) { info = info.makeAlphaType(at); bm.allocPixels(info); bm.eraseARGB(0x7F, 0xFF, 0xFF, 0xFF); } for (const auto& rec : gRecs) { REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality)); } } } } DEF_TEST(NdkEncode_unsupportedAlphaTypes, r) { for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) { for (auto at : { kUnknown_SkAlphaType, (SkAlphaType) -1}) { auto info = SkImageInfo::Make(10, 10, ct, at); size_t rowBytes = info.minRowBytes(); void* pixels = sk_malloc_throw(info.computeByteSize(rowBytes)); SkPixmap pm(info, pixels, rowBytes); for (const auto& rec : gRecs) { REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality)); } free(pixels); } } } 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}, }}; static bool nearly_equal(float a, float b) { return fabs(a - b) < .002f; } static bool nearly_equal(const skcms_TransferFunction& x, const skcms_TransferFunction& y) { return nearly_equal(x.g, y.g) && nearly_equal(x.a, y.a) && nearly_equal(x.b, y.b) && nearly_equal(x.c, y.c) && nearly_equal(x.d, y.d) && nearly_equal(x.e, y.e) && nearly_equal(x.f, y.f); } static bool nearly_equal(const skcms_Matrix3x3& a, const skcms_Matrix3x3& b) { for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) { if (!nearly_equal(a.vals[i][j], b.vals[i][j])) return false; } return true; } static bool nearly_equal(SkColorSpace* a, SkColorSpace* b) { skcms_TransferFunction fnA, fnB; skcms_Matrix3x3 gamutA, gamutB; return a && b && a->isNumericalTransferFn(&fnA) && a->toXYZD50(&gamutA) && b->isNumericalTransferFn(&fnB) && b->toXYZD50(&gamutB) && nearly_equal(fnA, fnB) && nearly_equal(gamutA, gamutB); } DEF_TEST(NdkEncode_ColorSpace, r) { const struct { sk_sp cs; const char* name; } colorSpaces[] = { { sk_sp(nullptr), "null" }, { SkColorSpace::MakeSRGB(), "srgb" }, { SkColorSpace::MakeSRGBLinear(), "srgb-linear"}, { SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB), "bt709" }, { SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020), "rec2020" }, { SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3), "p3" }, { SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB), "adobeRGB"}, { SkColorSpace::MakeRGB(k2Dot6, kDCIP3), "dci-p3" }, }; for (const auto& colorSpace : colorSpaces) { for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) { SkBitmap bm; bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType, colorSpace.cs)); bm.eraseColor(SK_ColorRED); for (const auto& rec : gRecs) { auto encoded = encode_ndk(bm.pixmap(), rec.format, rec.quality); REPORTER_ASSERT(r, encoded); auto gen = SkImageGenerator::MakeFromEncoded(std::move(encoded)); REPORTER_ASSERT(r, gen); auto expected = colorSpace.cs ? colorSpace.cs : SkColorSpace::MakeSRGB(); auto* actual = gen->getInfo().colorSpace(); if (!nearly_equal(actual, expected.get())) { const char* name = "unknown"; for (auto named : colorSpaces) { if (nearly_equal(actual, named.cs.get())) { name = named.name; break; } } ERRORF(r, "Mismatch: expected: %s\tactual:%s", colorSpace.name, name); } } } } } DEF_TEST(NdkEncode_unsupportedColorSpace, r) { std::vector> 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) { for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) { SkBitmap bm; bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType, unsupported)); bm.eraseColor(SK_ColorBLUE); for (const auto& rec : gRecs) { REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality)); } } } } #endif // SK_ENABLE_NDK_IMAGES