523116d9fe
(1) Parse ICC gray profiles into RGB SkColorSpace objects. This is easy - "gray transfer fn + white point" is subset of "RGB transfer fns + matrix". This makes it easier/possible for the drawing code to reason about color spaces attached to kGray buffers. (2) Allow gray images to be tagged with gray ICCs OR rgb ICCs. ICC gray forces to designer to use "D50 gray". It is not uncommon to see gray images with RGB profiles - and this actually allows the designer to choose more kinds of gray (ex: sRGB gray). (3) Make SkJpegCodec support gray images with RGB ICCs. (4) Make SkPngCodec support gray images with Gray ICCs. (5) Delete gray from SkColorSpace_A2B - we no longer create these objects for gray profiles. BUG=skia: Change-Id: Id5eca803798330c54a19c4657def2e5976d1941e Reviewed-on: https://skia-review.googlesource.com/6922 Reviewed-by: Brian Osman <brianosman@google.com> Commit-Queue: Matt Sarett <msarett@google.com>
325 lines
13 KiB
C++
325 lines
13 KiB
C++
/*
|
|
* Copyright 2016 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "Resources.h"
|
|
#include "SkCodec.h"
|
|
#include "SkCodecPriv.h"
|
|
#include "SkColorPriv.h"
|
|
#include "SkColorSpace.h"
|
|
#include "SkColorSpace_A2B.h"
|
|
#include "SkColorSpace_Base.h"
|
|
#include "SkColorSpace_XYZ.h"
|
|
#include "SkColorSpaceXform_Base.h"
|
|
#include "Test.h"
|
|
|
|
static constexpr int kChannels = 3;
|
|
|
|
class ColorSpaceXformTest {
|
|
public:
|
|
static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform(const sk_sp<SkGammas>& gammas) {
|
|
// Logically we can pass any matrix here. For simplicty, pass I(), i.e. D50 XYZ gamut.
|
|
sk_sp<SkColorSpace> space(new SkColorSpace_XYZ(
|
|
kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr));
|
|
|
|
// Use special testing entry point, so we don't skip the xform, even though src == dst.
|
|
return SlowIdentityXform(static_cast<SkColorSpace_XYZ*>(space.get()));
|
|
}
|
|
|
|
static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform_A2B(
|
|
SkGammaNamed gammaNamed, const sk_sp<SkGammas>& gammas) {
|
|
std::vector<SkColorSpace_A2B::Element> srcElements;
|
|
// sRGB
|
|
const float values[16] = {
|
|
0.4358f, 0.3853f, 0.1430f, 0.0f,
|
|
0.2224f, 0.7170f, 0.0606f, 0.0f,
|
|
0.0139f, 0.0971f, 0.7139f, 0.0f,
|
|
0.0000f, 0.0000f, 0.0000f, 1.0f
|
|
};
|
|
SkMatrix44 arbitraryMatrix{SkMatrix44::kUninitialized_Constructor};
|
|
arbitraryMatrix.setRowMajorf(values);
|
|
if (kNonStandard_SkGammaNamed == gammaNamed) {
|
|
SkASSERT(gammas);
|
|
srcElements.push_back(SkColorSpace_A2B::Element(gammas));
|
|
} else {
|
|
srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed, kChannels));
|
|
}
|
|
srcElements.push_back(SkColorSpace_A2B::Element(arbitraryMatrix));
|
|
auto srcSpace =
|
|
ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
|
|
SkColorSpace_Base::kRGB_ICCTypeFlag,
|
|
std::move(srcElements));
|
|
sk_sp<SkColorSpace> dstSpace(new SkColorSpace_XYZ(gammaNamed, gammas, arbitraryMatrix,
|
|
nullptr));
|
|
|
|
return SkColorSpaceXform::New(static_cast<SkColorSpace_A2B*>(srcSpace.get()),
|
|
static_cast<SkColorSpace_XYZ*>(dstSpace.get()));
|
|
}
|
|
|
|
static sk_sp<SkColorSpace> CreateA2BSpace(SkColorSpace_A2B::PCS pcs,
|
|
SkColorSpace_Base::ICCTypeFlag iccType,
|
|
std::vector<SkColorSpace_A2B::Element> elements) {
|
|
return sk_sp<SkColorSpace>(new SkColorSpace_A2B(iccType, std::move(elements),
|
|
pcs, nullptr));
|
|
}
|
|
};
|
|
|
|
static bool almost_equal(int x, int y) {
|
|
return SkTAbs(x - y) <= 1;
|
|
}
|
|
|
|
static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& gammas,
|
|
bool repeat) {
|
|
// Arbitrary set of 10 pixels
|
|
constexpr int width = 10;
|
|
constexpr uint32_t srcPixels[width] = {
|
|
0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271,
|
|
0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, };
|
|
uint32_t dstPixels[width];
|
|
|
|
// Create and perform an identity xform.
|
|
std::unique_ptr<SkColorSpaceXform> xform = ColorSpaceXformTest::CreateIdentityXform(gammas);
|
|
bool result = xform->apply(select_xform_format(kN32_SkColorType), dstPixels,
|
|
SkColorSpaceXform::kBGRA_8888_ColorFormat, srcPixels, width,
|
|
kOpaque_SkAlphaType);
|
|
REPORTER_ASSERT(r, result);
|
|
|
|
// Since the src->dst matrix is the identity, and the gamma curves match,
|
|
// the pixels should be unchanged.
|
|
for (int i = 0; i < width; i++) {
|
|
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 0) & 0xFF),
|
|
SkGetPackedB32(dstPixels[i])));
|
|
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 8) & 0xFF),
|
|
SkGetPackedG32(dstPixels[i])));
|
|
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF),
|
|
SkGetPackedR32(dstPixels[i])));
|
|
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
|
|
SkGetPackedA32(dstPixels[i])));
|
|
}
|
|
|
|
if (repeat) {
|
|
// We should cache part of the transform after the run. So it is interesting
|
|
// to make sure it still runs correctly the second time.
|
|
test_identity_xform(r, gammas, false);
|
|
}
|
|
}
|
|
|
|
static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNamed,
|
|
const sk_sp<SkGammas>& gammas) {
|
|
// Arbitrary set of 10 pixels
|
|
constexpr int width = 10;
|
|
constexpr uint32_t srcPixels[width] = {
|
|
0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271,
|
|
0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, };
|
|
uint32_t dstPixels[width];
|
|
|
|
// Create and perform an identity xform.
|
|
auto xform = ColorSpaceXformTest::CreateIdentityXform_A2B(gammaNamed, gammas);
|
|
bool result = xform->apply(select_xform_format(kN32_SkColorType), dstPixels,
|
|
SkColorSpaceXform::kBGRA_8888_ColorFormat, srcPixels, width,
|
|
kOpaque_SkAlphaType);
|
|
REPORTER_ASSERT(r, result);
|
|
|
|
// Since the src->dst matrix is the identity, and the gamma curves match,
|
|
// the pixels should be unchanged.
|
|
for (int i = 0; i < width; i++) {
|
|
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 0) & 0xFF),
|
|
SkGetPackedB32(dstPixels[i])));
|
|
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 8) & 0xFF),
|
|
SkGetPackedG32(dstPixels[i])));
|
|
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF),
|
|
SkGetPackedR32(dstPixels[i])));
|
|
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
|
|
SkGetPackedA32(dstPixels[i])));
|
|
}
|
|
}
|
|
|
|
DEF_TEST(ColorSpaceXform_TableGamma, r) {
|
|
// Lookup-table based gamma curves
|
|
constexpr size_t tableSize = 10;
|
|
void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize);
|
|
sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
|
|
for (int i = 0; i < kChannels; ++i) {
|
|
gammas->fType[i] = SkGammas::Type::kTable_Type;
|
|
gammas->fData[i].fTable.fSize = tableSize;
|
|
gammas->fData[i].fTable.fOffset = 0;
|
|
}
|
|
|
|
float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
|
|
|
|
table[0] = 0.00f;
|
|
table[1] = 0.05f;
|
|
table[2] = 0.10f;
|
|
table[3] = 0.15f;
|
|
table[4] = 0.25f;
|
|
table[5] = 0.35f;
|
|
table[6] = 0.45f;
|
|
table[7] = 0.60f;
|
|
table[8] = 0.75f;
|
|
table[9] = 1.00f;
|
|
test_identity_xform(r, gammas, true);
|
|
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
|
|
}
|
|
|
|
DEF_TEST(ColorSpaceXform_ParametricGamma, r) {
|
|
// Parametric gamma curves
|
|
void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
|
|
sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
|
|
for (int i = 0; i < kChannels; ++i) {
|
|
gammas->fType[i] = SkGammas::Type::kParam_Type;
|
|
gammas->fData[i].fParamOffset = 0;
|
|
}
|
|
|
|
SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>
|
|
(memory, sizeof(SkGammas));
|
|
|
|
// Interval.
|
|
params->fD = 0.04045f;
|
|
|
|
// First equation:
|
|
params->fC = 1.0f / 12.92f;
|
|
params->fF = 0.0f;
|
|
|
|
// Second equation:
|
|
// Note that the function is continuous (it's actually sRGB).
|
|
params->fA = 1.0f / 1.055f;
|
|
params->fB = 0.055f / 1.055f;
|
|
params->fE = 0.0f;
|
|
params->fG = 2.4f;
|
|
test_identity_xform(r, gammas, true);
|
|
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
|
|
}
|
|
|
|
DEF_TEST(ColorSpaceXform_ExponentialGamma, r) {
|
|
// Exponential gamma curves
|
|
sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels));
|
|
for (int i = 0; i < kChannels; ++i) {
|
|
gammas->fType[i] = SkGammas::Type::kValue_Type;
|
|
gammas->fData[i].fValue = 1.4f;
|
|
}
|
|
test_identity_xform(r, gammas, true);
|
|
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
|
|
}
|
|
|
|
DEF_TEST(ColorSpaceXform_NamedGamma, r) {
|
|
sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels));
|
|
gammas->fType[0] = gammas->fType[1] = gammas->fType[2] = SkGammas::Type::kNamed_Type;
|
|
gammas->fData[0].fNamed = kSRGB_SkGammaNamed;
|
|
gammas->fData[1].fNamed = k2Dot2Curve_SkGammaNamed;
|
|
gammas->fData[2].fNamed = kLinear_SkGammaNamed;
|
|
test_identity_xform(r, gammas, true);
|
|
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
|
|
test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr);
|
|
test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr);
|
|
test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr);
|
|
}
|
|
|
|
DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) {
|
|
constexpr size_t tableSize = 10;
|
|
void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize +
|
|
sizeof(SkColorSpaceTransferFn));
|
|
sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
|
|
|
|
float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
|
|
table[0] = 0.00f;
|
|
table[1] = 0.15f;
|
|
table[2] = 0.20f;
|
|
table[3] = 0.25f;
|
|
table[4] = 0.35f;
|
|
table[5] = 0.45f;
|
|
table[6] = 0.55f;
|
|
table[7] = 0.70f;
|
|
table[8] = 0.85f;
|
|
table[9] = 1.00f;
|
|
|
|
SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>(memory,
|
|
sizeof(SkGammas) + sizeof(float) * tableSize);
|
|
params->fA = 1.0f / 1.055f;
|
|
params->fB = 0.055f / 1.055f;
|
|
params->fC = 1.0f / 12.92f;
|
|
params->fD = 0.04045f;
|
|
params->fE = 0.0f;
|
|
params->fF = 0.0f;
|
|
params->fG = 2.4f;
|
|
|
|
gammas->fType[0] = SkGammas::Type::kValue_Type;
|
|
gammas->fData[0].fValue = 1.2f;
|
|
|
|
gammas->fType[1] = SkGammas::Type::kTable_Type;
|
|
gammas->fData[1].fTable.fSize = tableSize;
|
|
gammas->fData[1].fTable.fOffset = 0;
|
|
|
|
gammas->fType[2] = SkGammas::Type::kParam_Type;
|
|
gammas->fData[2].fParamOffset = sizeof(float) * tableSize;
|
|
|
|
test_identity_xform(r, gammas, true);
|
|
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
|
|
}
|
|
|
|
DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
|
|
constexpr int inputChannels = 3;
|
|
constexpr int gp = 4; // # grid points
|
|
|
|
constexpr int numEntries = gp*gp*gp*3;
|
|
const uint8_t gridPoints[3] = {gp, gp, gp};
|
|
void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries);
|
|
sk_sp<SkColorLookUpTable> colorLUT(new (memory) SkColorLookUpTable(inputChannels, gridPoints));
|
|
// make a CLUT that rotates R, G, and B ie R->G, G->B, B->R
|
|
float* table = SkTAddOffset<float>(memory, sizeof(SkColorLookUpTable));
|
|
for (int r = 0; r < gp; ++r) {
|
|
for (int g = 0; g < gp; ++g) {
|
|
for (int b = 0; b < gp; ++b) {
|
|
table[3*(gp*gp*r + gp*g + b) + 0] = g * (1.f / (gp - 1.f));
|
|
table[3*(gp*gp*r + gp*g + b) + 1] = b * (1.f / (gp - 1.f));
|
|
table[3*(gp*gp*r + gp*g + b) + 2] = r * (1.f / (gp - 1.f));
|
|
}
|
|
}
|
|
}
|
|
|
|
// build an even distribution of pixels every (7 / 255) steps
|
|
// to test the xform on
|
|
constexpr int pixelgp = 7;
|
|
constexpr int numPixels = pixelgp*pixelgp*pixelgp;
|
|
SkAutoTMalloc<uint32_t> srcPixels(numPixels);
|
|
int srcIndex = 0;
|
|
for (int r = 0; r < pixelgp; ++r) {
|
|
for (int g = 0; g < pixelgp; ++g) {
|
|
for (int b = 0; b < pixelgp; ++b) {
|
|
const int red = (int) (r * (255.f / (pixelgp - 1.f)));
|
|
const int green = (int) (g * (255.f / (pixelgp - 1.f)));
|
|
const int blue = (int) (b * (255.f / (pixelgp - 1.f)));
|
|
srcPixels[srcIndex] = SkColorSetRGB(red, green, blue);
|
|
++srcIndex;
|
|
}
|
|
}
|
|
}
|
|
SkAutoTMalloc<uint32_t> dstPixels(numPixels);
|
|
|
|
// src space is identity besides CLUT
|
|
std::vector<SkColorSpace_A2B::Element> srcElements;
|
|
srcElements.push_back(SkColorSpace_A2B::Element(std::move(colorLUT)));
|
|
auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
|
|
SkColorSpace_Base::kRGB_ICCTypeFlag,
|
|
std::move(srcElements));
|
|
// dst space is entirely identity
|
|
auto dstSpace = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, SkMatrix44::I());
|
|
auto xform = SkColorSpaceXform::New(srcSpace.get(), dstSpace.get());
|
|
bool result = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, dstPixels.get(),
|
|
SkColorSpaceXform::kRGBA_8888_ColorFormat, srcPixels.get(),
|
|
numPixels, kOpaque_SkAlphaType);
|
|
REPORTER_ASSERT(r, result);
|
|
|
|
for (int i = 0; i < numPixels; ++i) {
|
|
REPORTER_ASSERT(r, almost_equal(SkColorGetR(srcPixels[i]),
|
|
SkColorGetG(dstPixels[i])));
|
|
REPORTER_ASSERT(r, almost_equal(SkColorGetG(srcPixels[i]),
|
|
SkColorGetB(dstPixels[i])));
|
|
REPORTER_ASSERT(r, almost_equal(SkColorGetB(srcPixels[i]),
|
|
SkColorGetR(dstPixels[i])));
|
|
}
|
|
}
|
|
|