blob: a7b95d57ab0e4e35704907c3a7c4da512ee0a744 [file] [log] [blame]
/*
* 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