358 lines
14 KiB
C++
358 lines
14 KiB
C++
|
/*
|
||
|
* 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/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 <stdint.h>
|
||
|
#include <vector>
|
||
|
|
||
|
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<SkData> 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<SkPixmap> 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<size_t>(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<SkColorSpace> cs;
|
||
|
const char* name;
|
||
|
} colorSpaces[] = {
|
||
|
{ sk_sp<SkColorSpace>(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<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) {
|
||
|
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
|