1/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "Resources.h"
9#include "SkCodec.h"
10#include "SkCodecPriv.h"
11#include "SkColorPriv.h"
12#include "SkColorSpace.h"
13#include "SkColorSpace_A2B.h"
14#include "SkColorSpace_Base.h"
15#include "SkColorSpace_XYZ.h"
16#include "SkColorSpaceXform_Base.h"
17#include "Test.h"
18
19static constexpr int kChannels = 3;
20
21class ColorSpaceXformTest {
22public:
23    static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform(const sk_sp<SkGammas>& gammas) {
24        // Logically we can pass any matrix here.  For simplicty, pass I(), i.e. D50 XYZ gamut.
25        sk_sp<SkColorSpace> space(new SkColorSpace_XYZ(
26                kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr));
27
28        // Use special testing entry point, so we don't skip the xform, even though src == dst.
29        return SlowIdentityXform(static_cast<SkColorSpace_XYZ*>(space.get()));
30    }
31
32    static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform_A2B(
33            SkGammaNamed gammaNamed, const sk_sp<SkGammas>& gammas) {
34        std::vector<SkColorSpace_A2B::Element> srcElements;
35        // sRGB
36        const float values[16] = {
37            0.4358f, 0.3853f, 0.1430f, 0.0f,
38            0.2224f, 0.7170f, 0.0606f, 0.0f,
39            0.0139f, 0.0971f, 0.7139f, 0.0f,
40            0.0000f, 0.0000f, 0.0000f, 1.0f
41        };
42        SkMatrix44 arbitraryMatrix{SkMatrix44::kUninitialized_Constructor};
43        arbitraryMatrix.setRowMajorf(values);
44        if (kNonStandard_SkGammaNamed == gammaNamed) {
45            SkASSERT(gammas);
46            srcElements.push_back(SkColorSpace_A2B::Element(gammas));
47        } else {
48            srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed, kChannels));
49        }
50        srcElements.push_back(SkColorSpace_A2B::Element(arbitraryMatrix));
51        auto srcSpace =
52                ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
53                                                    SkColorSpace_Base::kRGB_ICCTypeFlag,
54                                                    std::move(srcElements));
55        sk_sp<SkColorSpace> dstSpace(new SkColorSpace_XYZ(gammaNamed, gammas, arbitraryMatrix,
56                                                          nullptr));
57
58        return SkColorSpaceXform::New(static_cast<SkColorSpace_A2B*>(srcSpace.get()),
59                                      static_cast<SkColorSpace_XYZ*>(dstSpace.get()));
60    }
61
62    static sk_sp<SkColorSpace> CreateA2BSpace(SkColorSpace_A2B::PCS pcs,
63                                              SkColorSpace_Base::ICCTypeFlag iccType,
64                                              std::vector<SkColorSpace_A2B::Element> elements) {
65        return sk_sp<SkColorSpace>(new SkColorSpace_A2B(iccType, std::move(elements),
66                                                        pcs, nullptr));
67    }
68};
69
70static bool almost_equal(int x, int y, int tol=1) {
71    return SkTAbs(x-y) <= tol;
72}
73
74static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& gammas,
75                                bool repeat) {
76    // Arbitrary set of 10 pixels
77    constexpr int width = 10;
78    constexpr uint32_t srcPixels[width] = {
79            0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271,
80            0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, };
81    uint32_t dstPixels[width];
82
83    // Create and perform an identity xform.
84    std::unique_ptr<SkColorSpaceXform> xform = ColorSpaceXformTest::CreateIdentityXform(gammas);
85    bool result = xform->apply(select_xform_format(kN32_SkColorType), dstPixels,
86                               SkColorSpaceXform::kBGRA_8888_ColorFormat, srcPixels, width,
87                               kOpaque_SkAlphaType);
88    REPORTER_ASSERT(r, result);
89
90    // Since the src->dst matrix is the identity, and the gamma curves match,
91    // the pixels should be unchanged.
92    for (int i = 0; i < width; i++) {
93        REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >>  0) & 0xFF),
94                                        SkGetPackedB32(dstPixels[i])));
95        REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >>  8) & 0xFF),
96                                        SkGetPackedG32(dstPixels[i])));
97        REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF),
98                                        SkGetPackedR32(dstPixels[i])));
99        REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
100                                        SkGetPackedA32(dstPixels[i])));
101    }
102
103    if (repeat) {
104        // We should cache part of the transform after the run.  So it is interesting
105        // to make sure it still runs correctly the second time.
106        test_identity_xform(r, gammas, false);
107    }
108}
109
110static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNamed,
111                                    const sk_sp<SkGammas>& gammas, int tol=1) {
112    // Arbitrary set of 10 pixels
113    constexpr int width = 10;
114    constexpr uint32_t srcPixels[width] = {
115            0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271,
116            0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, };
117    uint32_t dstPixels[width];
118
119    // Create and perform an identity xform.
120    auto xform = ColorSpaceXformTest::CreateIdentityXform_A2B(gammaNamed, gammas);
121    bool result = xform->apply(select_xform_format(kN32_SkColorType), dstPixels,
122                               SkColorSpaceXform::kBGRA_8888_ColorFormat, srcPixels, width,
123                               kOpaque_SkAlphaType);
124    REPORTER_ASSERT(r, result);
125
126    // Since the src->dst matrix is the identity, and the gamma curves match,
127    // the pixels should be ~unchanged.
128    for (int i = 0; i < width; i++) {
129        REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >>  0) & 0xFF),
130                                        SkGetPackedB32(dstPixels[i]), tol));
131        REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >>  8) & 0xFF),
132                                        SkGetPackedG32(dstPixels[i]), tol));
133        REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF),
134                                        SkGetPackedR32(dstPixels[i]), tol));
135        REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
136                                        SkGetPackedA32(dstPixels[i]), tol));
137    }
138}
139
140DEF_TEST(ColorSpaceXform_TableGamma, r) {
141    // Lookup-table based gamma curves
142    constexpr size_t tableSize = 10;
143    void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize);
144    sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
145    for (int i = 0; i < kChannels; ++i) {
146        gammas->fType[i] = SkGammas::Type::kTable_Type;
147        gammas->fData[i].fTable.fSize = tableSize;
148        gammas->fData[i].fTable.fOffset = 0;
149    }
150
151    float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
152    table[0] = 0.00f;
153    table[1] = 0.05f;
154    table[2] = 0.10f;
155    table[3] = 0.15f;
156    table[4] = 0.25f;
157    table[5] = 0.35f;
158    table[6] = 0.45f;
159    table[7] = 0.60f;
160    table[8] = 0.75f;
161    table[9] = 1.00f;
162    // This table's pretty small compared to real ones in the wild (think 256),
163    // so we give test_identity_xform_A2B a wide tolerance.
164    // This lets us implement table transfer functions with a single lookup.
165    const int tolerance = 13;
166
167    test_identity_xform(r, gammas, true);
168    test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, tolerance);
169}
170
171DEF_TEST(ColorSpaceXform_ParametricGamma, r) {
172    // Parametric gamma curves
173    void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
174    sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
175    for (int i = 0; i < kChannels; ++i) {
176        gammas->fType[i] = SkGammas::Type::kParam_Type;
177        gammas->fData[i].fParamOffset = 0;
178    }
179
180    SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>
181            (memory, sizeof(SkGammas));
182
183    // Interval.
184    params->fD = 0.04045f;
185
186    // First equation:
187    params->fC = 1.0f / 12.92f;
188    params->fF = 0.0f;
189
190    // Second equation:
191    // Note that the function is continuous (it's actually sRGB).
192    params->fA = 1.0f / 1.055f;
193    params->fB = 0.055f / 1.055f;
194    params->fE = 0.0f;
195    params->fG = 2.4f;
196    test_identity_xform(r, gammas, true);
197    test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
198}
199
200DEF_TEST(ColorSpaceXform_ExponentialGamma, r) {
201    // Exponential gamma curves
202    sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels));
203    for (int i = 0; i < kChannels; ++i) {
204        gammas->fType[i] = SkGammas::Type::kValue_Type;
205        gammas->fData[i].fValue = 1.4f;
206    }
207    test_identity_xform(r, gammas, true);
208    test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
209}
210
211DEF_TEST(ColorSpaceXform_NamedGamma, r) {
212    sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels));
213    gammas->fType[0] = gammas->fType[1] = gammas->fType[2] = SkGammas::Type::kNamed_Type;
214    gammas->fData[0].fNamed = kSRGB_SkGammaNamed;
215    gammas->fData[1].fNamed = k2Dot2Curve_SkGammaNamed;
216    gammas->fData[2].fNamed = kLinear_SkGammaNamed;
217    test_identity_xform(r, gammas, true);
218    test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
219    test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr);
220    test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr);
221    test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr);
222}
223
224DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) {
225    constexpr size_t tableSize = 10;
226    void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize +
227                                   sizeof(SkColorSpaceTransferFn));
228    sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
229
230    float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
231    table[0] = 0.00f;
232    table[1] = 0.15f;
233    table[2] = 0.20f;
234    table[3] = 0.25f;
235    table[4] = 0.35f;
236    table[5] = 0.45f;
237    table[6] = 0.55f;
238    table[7] = 0.70f;
239    table[8] = 0.85f;
240    table[9] = 1.00f;
241
242    SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>(memory,
243            sizeof(SkGammas) + sizeof(float) * tableSize);
244    params->fA = 1.0f / 1.055f;
245    params->fB = 0.055f / 1.055f;
246    params->fC = 1.0f / 12.92f;
247    params->fD = 0.04045f;
248    params->fE = 0.0f;
249    params->fF = 0.0f;
250    params->fG = 2.4f;
251
252    gammas->fType[0] = SkGammas::Type::kValue_Type;
253    gammas->fData[0].fValue = 1.2f;
254
255    // See ColorSpaceXform_TableGamma... we've decided to allow some tolerance
256    // for SkJumper's implementation of tables.
257    const int tolerance = 12;
258    gammas->fType[1] = SkGammas::Type::kTable_Type;
259    gammas->fData[1].fTable.fSize = tableSize;
260    gammas->fData[1].fTable.fOffset = 0;
261
262    gammas->fType[2] = SkGammas::Type::kParam_Type;
263    gammas->fData[2].fParamOffset = sizeof(float) * tableSize;
264
265    test_identity_xform(r, gammas, true);
266    test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, tolerance);
267}
268
269DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
270    constexpr int inputChannels = 3;
271    constexpr int gp            = 4; // # grid points
272
273    constexpr int numEntries    = gp*gp*gp*3;
274    const uint8_t gridPoints[3] = {gp, gp, gp};
275    void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries);
276    sk_sp<SkColorLookUpTable> colorLUT(new (memory) SkColorLookUpTable(inputChannels, gridPoints));
277    // make a CLUT that rotates R, G, and B ie R->G, G->B, B->R
278    float* table = SkTAddOffset<float>(memory, sizeof(SkColorLookUpTable));
279    for (int r = 0; r < gp; ++r) {
280        for (int g = 0; g < gp; ++g) {
281            for (int b = 0; b < gp; ++b) {
282                table[3*(gp*gp*r + gp*g + b) + 0] = g * (1.f / (gp - 1.f));
283                table[3*(gp*gp*r + gp*g + b) + 1] = b * (1.f / (gp - 1.f));
284                table[3*(gp*gp*r + gp*g + b) + 2] = r * (1.f / (gp - 1.f));
285            }
286        }
287    }
288
289    // build an even distribution of pixels every (7 / 255) steps
290    // to test the xform on
291    constexpr int pixelgp   = 7;
292    constexpr int numPixels = pixelgp*pixelgp*pixelgp;
293    SkAutoTMalloc<uint32_t> srcPixels(numPixels);
294    int srcIndex = 0;
295    for (int r = 0; r < pixelgp; ++r) {
296        for (int g = 0; g < pixelgp; ++g) {
297            for (int b = 0; b < pixelgp; ++b) {
298                const int red   = (int) (r * (255.f / (pixelgp - 1.f)));
299                const int green = (int) (g * (255.f / (pixelgp - 1.f)));
300                const int blue  = (int) (b * (255.f / (pixelgp - 1.f)));
301                srcPixels[srcIndex] = SkColorSetRGB(red, green, blue);
302                ++srcIndex;
303            }
304        }
305    }
306    SkAutoTMalloc<uint32_t> dstPixels(numPixels);
307
308    // src space is identity besides CLUT
309    std::vector<SkColorSpace_A2B::Element> srcElements;
310    srcElements.push_back(SkColorSpace_A2B::Element(std::move(colorLUT)));
311    auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
312                                                        SkColorSpace_Base::kRGB_ICCTypeFlag,
313                                                        std::move(srcElements));
314    // dst space is entirely identity
315    auto dstSpace = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, SkMatrix44::I());
316    auto xform = SkColorSpaceXform::New(srcSpace.get(), dstSpace.get());
317    bool result = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, dstPixels.get(),
318                               SkColorSpaceXform::kRGBA_8888_ColorFormat, srcPixels.get(),
319                               numPixels, kOpaque_SkAlphaType);
320    REPORTER_ASSERT(r, result);
321
322    for (int i = 0; i < numPixels; ++i) {
323        REPORTER_ASSERT(r, almost_equal(SkColorGetR(srcPixels[i]),
324                                        SkColorGetG(dstPixels[i])));
325        REPORTER_ASSERT(r, almost_equal(SkColorGetG(srcPixels[i]),
326                                        SkColorGetB(dstPixels[i])));
327        REPORTER_ASSERT(r, almost_equal(SkColorGetB(srcPixels[i]),
328                                        SkColorGetR(dstPixels[i])));
329    }
330}
331
332DEF_TEST(SkColorSpaceXform_LoadTail, r) {
333    std::unique_ptr<uint64_t[]> srcPixel(new uint64_t[1]);
334    srcPixel[0] = 0;
335    uint32_t dstPixel;
336    sk_sp<SkColorSpace> adobe = SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kAdobeRGB_Named);
337    sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
338    std::unique_ptr<SkColorSpaceXform> xform = SkColorSpaceXform::New(adobe.get(), srgb.get());
339
340    // ASAN will catch us if we read past the tail.
341    bool success = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, &dstPixel,
342                                SkColorSpaceXform::kRGBA_U16_BE_ColorFormat, srcPixel.get(), 1,
343                                kUnpremul_SkAlphaType);
344    REPORTER_ASSERT(r, success);
345}
346
347