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