• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
19 static constexpr int kChannels = 3;
20 
21 class ColorSpaceXformTest {
22 public:
CreateIdentityXform(const sk_sp<SkGammas> & gammas)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 
CreateIdentityXform_A2B(SkGammaNamed gammaNamed,const sk_sp<SkGammas> & gammas)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 
CreateA2BSpace(SkColorSpace_A2B::PCS pcs,SkColorSpace_Base::ICCTypeFlag iccType,std::vector<SkColorSpace_A2B::Element> elements)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 
almost_equal(int x,int y)70 static bool almost_equal(int x, int y) {
71     return SkTAbs(x - y) <= 1;
72 }
73 
test_identity_xform(skiatest::Reporter * r,const sk_sp<SkGammas> & gammas,bool repeat)74 static 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 
test_identity_xform_A2B(skiatest::Reporter * r,SkGammaNamed gammaNamed,const sk_sp<SkGammas> & gammas)110 static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNamed,
111                                     const sk_sp<SkGammas>& gammas) {
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])));
131         REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >>  8) & 0xFF),
132                                         SkGetPackedG32(dstPixels[i])));
133         REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF),
134                                         SkGetPackedR32(dstPixels[i])));
135         REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
136                                         SkGetPackedA32(dstPixels[i])));
137     }
138 }
139 
DEF_TEST(ColorSpaceXform_TableGamma,r)140 DEF_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 
153     table[0] = 0.00f;
154     table[1] = 0.05f;
155     table[2] = 0.10f;
156     table[3] = 0.15f;
157     table[4] = 0.25f;
158     table[5] = 0.35f;
159     table[6] = 0.45f;
160     table[7] = 0.60f;
161     table[8] = 0.75f;
162     table[9] = 1.00f;
163     test_identity_xform(r, gammas, true);
164     test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
165 }
166 
DEF_TEST(ColorSpaceXform_ParametricGamma,r)167 DEF_TEST(ColorSpaceXform_ParametricGamma, r) {
168     // Parametric gamma curves
169     void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
170     sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
171     for (int i = 0; i < kChannels; ++i) {
172         gammas->fType[i] = SkGammas::Type::kParam_Type;
173         gammas->fData[i].fParamOffset = 0;
174     }
175 
176     SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>
177             (memory, sizeof(SkGammas));
178 
179     // Interval.
180     params->fD = 0.04045f;
181 
182     // First equation:
183     params->fC = 1.0f / 12.92f;
184     params->fF = 0.0f;
185 
186     // Second equation:
187     // Note that the function is continuous (it's actually sRGB).
188     params->fA = 1.0f / 1.055f;
189     params->fB = 0.055f / 1.055f;
190     params->fE = 0.0f;
191     params->fG = 2.4f;
192     test_identity_xform(r, gammas, true);
193     test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
194 }
195 
DEF_TEST(ColorSpaceXform_ExponentialGamma,r)196 DEF_TEST(ColorSpaceXform_ExponentialGamma, r) {
197     // Exponential gamma curves
198     sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels));
199     for (int i = 0; i < kChannels; ++i) {
200         gammas->fType[i] = SkGammas::Type::kValue_Type;
201         gammas->fData[i].fValue = 1.4f;
202     }
203     test_identity_xform(r, gammas, true);
204     test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
205 }
206 
DEF_TEST(ColorSpaceXform_NamedGamma,r)207 DEF_TEST(ColorSpaceXform_NamedGamma, r) {
208     sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels));
209     gammas->fType[0] = gammas->fType[1] = gammas->fType[2] = SkGammas::Type::kNamed_Type;
210     gammas->fData[0].fNamed = kSRGB_SkGammaNamed;
211     gammas->fData[1].fNamed = k2Dot2Curve_SkGammaNamed;
212     gammas->fData[2].fNamed = kLinear_SkGammaNamed;
213     test_identity_xform(r, gammas, true);
214     test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
215     test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr);
216     test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr);
217     test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr);
218 }
219 
DEF_TEST(ColorSpaceXform_NonMatchingGamma,r)220 DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) {
221     constexpr size_t tableSize = 10;
222     void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize +
223                                    sizeof(SkColorSpaceTransferFn));
224     sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
225 
226     float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
227     table[0] = 0.00f;
228     table[1] = 0.15f;
229     table[2] = 0.20f;
230     table[3] = 0.25f;
231     table[4] = 0.35f;
232     table[5] = 0.45f;
233     table[6] = 0.55f;
234     table[7] = 0.70f;
235     table[8] = 0.85f;
236     table[9] = 1.00f;
237 
238     SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>(memory,
239             sizeof(SkGammas) + sizeof(float) * tableSize);
240     params->fA = 1.0f / 1.055f;
241     params->fB = 0.055f / 1.055f;
242     params->fC = 1.0f / 12.92f;
243     params->fD = 0.04045f;
244     params->fE = 0.0f;
245     params->fF = 0.0f;
246     params->fG = 2.4f;
247 
248     gammas->fType[0] = SkGammas::Type::kValue_Type;
249     gammas->fData[0].fValue = 1.2f;
250 
251     gammas->fType[1] = SkGammas::Type::kTable_Type;
252     gammas->fData[1].fTable.fSize = tableSize;
253     gammas->fData[1].fTable.fOffset = 0;
254 
255     gammas->fType[2] = SkGammas::Type::kParam_Type;
256     gammas->fData[2].fParamOffset = sizeof(float) * tableSize;
257 
258     test_identity_xform(r, gammas, true);
259     test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
260 }
261 
DEF_TEST(ColorSpaceXform_A2BCLUT,r)262 DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
263     constexpr int inputChannels = 3;
264     constexpr int gp            = 4; // # grid points
265 
266     constexpr int numEntries    = gp*gp*gp*3;
267     const uint8_t gridPoints[3] = {gp, gp, gp};
268     void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries);
269     sk_sp<SkColorLookUpTable> colorLUT(new (memory) SkColorLookUpTable(inputChannels, gridPoints));
270     // make a CLUT that rotates R, G, and B ie R->G, G->B, B->R
271     float* table = SkTAddOffset<float>(memory, sizeof(SkColorLookUpTable));
272     for (int r = 0; r < gp; ++r) {
273         for (int g = 0; g < gp; ++g) {
274             for (int b = 0; b < gp; ++b) {
275                 table[3*(gp*gp*r + gp*g + b) + 0] = g * (1.f / (gp - 1.f));
276                 table[3*(gp*gp*r + gp*g + b) + 1] = b * (1.f / (gp - 1.f));
277                 table[3*(gp*gp*r + gp*g + b) + 2] = r * (1.f / (gp - 1.f));
278             }
279         }
280     }
281 
282     // build an even distribution of pixels every (7 / 255) steps
283     // to test the xform on
284     constexpr int pixelgp   = 7;
285     constexpr int numPixels = pixelgp*pixelgp*pixelgp;
286     SkAutoTMalloc<uint32_t> srcPixels(numPixels);
287     int srcIndex = 0;
288     for (int r = 0; r < pixelgp; ++r) {
289         for (int g = 0; g < pixelgp; ++g) {
290             for (int b = 0; b < pixelgp; ++b) {
291                 const int red   = (int) (r * (255.f / (pixelgp - 1.f)));
292                 const int green = (int) (g * (255.f / (pixelgp - 1.f)));
293                 const int blue  = (int) (b * (255.f / (pixelgp - 1.f)));
294                 srcPixels[srcIndex] = SkColorSetRGB(red, green, blue);
295                 ++srcIndex;
296             }
297         }
298     }
299     SkAutoTMalloc<uint32_t> dstPixels(numPixels);
300 
301     // src space is identity besides CLUT
302     std::vector<SkColorSpace_A2B::Element> srcElements;
303     srcElements.push_back(SkColorSpace_A2B::Element(std::move(colorLUT)));
304     auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
305                                                         SkColorSpace_Base::kRGB_ICCTypeFlag,
306                                                         std::move(srcElements));
307     // dst space is entirely identity
308     auto dstSpace = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, SkMatrix44::I());
309     auto xform = SkColorSpaceXform::New(srcSpace.get(), dstSpace.get());
310     bool result = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, dstPixels.get(),
311                                SkColorSpaceXform::kRGBA_8888_ColorFormat, srcPixels.get(),
312                                numPixels, kOpaque_SkAlphaType);
313     REPORTER_ASSERT(r, result);
314 
315     for (int i = 0; i < numPixels; ++i) {
316         REPORTER_ASSERT(r, almost_equal(SkColorGetR(srcPixels[i]),
317                                         SkColorGetG(dstPixels[i])));
318         REPORTER_ASSERT(r, almost_equal(SkColorGetG(srcPixels[i]),
319                                         SkColorGetB(dstPixels[i])));
320         REPORTER_ASSERT(r, almost_equal(SkColorGetB(srcPixels[i]),
321                                         SkColorGetR(dstPixels[i])));
322     }
323 }
324 
DEF_TEST(SkColorSpaceXform_LoadTail,r)325 DEF_TEST(SkColorSpaceXform_LoadTail, r) {
326     std::unique_ptr<uint64_t[]> srcPixel(new uint64_t[1]);
327     srcPixel[0] = 0;
328     uint32_t dstPixel;
329     sk_sp<SkColorSpace> adobe = SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kAdobeRGB_Named);
330     sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
331     std::unique_ptr<SkColorSpaceXform> xform = SkColorSpaceXform::New(adobe.get(), srgb.get());
332 
333     // ASAN will catch us if we read past the tail.
334     bool success = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, &dstPixel,
335                                 SkColorSpaceXform::kRGBA_U16_BE_ColorFormat, srcPixel.get(), 1,
336                                 kUnpremul_SkAlphaType);
337     REPORTER_ASSERT(r, success);
338 }
339 
340