blob: 0e67fe497d5afd81c74e9096487c3ab7a600beb3 [file] [log] [blame]
msarettdc27a642016-06-06 12:02:31 -07001/*
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"
msarettc0444612016-09-16 11:45:58 -070010#include "SkCodecPriv.h"
msarettdc27a642016-06-06 12:02:31 -070011#include "SkColorPriv.h"
12#include "SkColorSpace.h"
raftias25636012016-11-11 15:27:39 -080013#include "SkColorSpace_A2B.h"
msarettdc27a642016-06-06 12:02:31 -070014#include "SkColorSpace_Base.h"
raftias94888332016-10-18 10:02:51 -070015#include "SkColorSpace_XYZ.h"
msarett31d097e82016-10-11 12:15:03 -070016#include "SkColorSpaceXform_Base.h"
msarettdc27a642016-06-06 12:02:31 -070017#include "Test.h"
18
19class ColorSpaceXformTest {
20public:
mtkleinb4359632016-07-18 11:16:14 -070021 static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform(const sk_sp<SkGammas>& gammas) {
22 // Logically we can pass any matrix here. For simplicty, pass I(), i.e. D50 XYZ gamut.
raftias94888332016-10-18 10:02:51 -070023 sk_sp<SkColorSpace> space(new SkColorSpace_XYZ(
24 kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr));
msarett9dc6cf62016-08-23 17:53:06 -070025
26 // Use special testing entry point, so we don't skip the xform, even though src == dst.
raftias94888332016-10-18 10:02:51 -070027 return SlowIdentityXform(static_cast<SkColorSpace_XYZ*>(space.get()));
msarettdc27a642016-06-06 12:02:31 -070028 }
raftias25636012016-11-11 15:27:39 -080029
30 static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform_A2B(
31 SkGammaNamed gammaNamed, const sk_sp<SkGammas>& gammas) {
32 std::vector<SkColorSpace_A2B::Element> srcElements;
33 // sRGB
34 const float values[16] = {
35 0.4358f, 0.3853f, 0.1430f, 0.0f,
36 0.2224f, 0.7170f, 0.0606f, 0.0f,
37 0.0139f, 0.0971f, 0.7139f, 0.0f,
38 0.0000f, 0.0000f, 0.0000f, 1.0f
39 };
40 SkMatrix44 arbitraryMatrix{SkMatrix44::kUninitialized_Constructor};
41 arbitraryMatrix.setRowMajorf(values);
42 if (kNonStandard_SkGammaNamed == gammaNamed) {
43 srcElements.push_back(SkColorSpace_A2B::Element(gammas));
44 } else {
45 srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed));
46 }
47 srcElements.push_back(SkColorSpace_A2B::Element(arbitraryMatrix));
48 auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
49 std::move(srcElements));
50 sk_sp<SkColorSpace> dstSpace(new SkColorSpace_XYZ(gammaNamed, gammas, arbitraryMatrix,
51 nullptr));
52
53 return SkColorSpaceXform::New(static_cast<SkColorSpace_A2B*>(srcSpace.get()),
54 static_cast<SkColorSpace_XYZ*>(dstSpace.get()));
55 }
56
57 static sk_sp<SkColorSpace> CreateA2BSpace(SkColorSpace_A2B::PCS pcs,
58 std::vector<SkColorSpace_A2B::Element> elements) {
59 return sk_sp<SkColorSpace>(new SkColorSpace_A2B(pcs, nullptr, std::move(elements)));
60 }
msarettdc27a642016-06-06 12:02:31 -070061};
62
msarettb3906762016-06-22 14:07:48 -070063static bool almost_equal(int x, int y) {
raftias25636012016-11-11 15:27:39 -080064 return SkTAbs(x - y) <= 1 ;
msarettb3906762016-06-22 14:07:48 -070065}
66
Matt Sarettf4898862016-10-16 10:20:41 -040067static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& gammas,
68 bool repeat) {
msarettdc27a642016-06-06 12:02:31 -070069 // Arbitrary set of 10 pixels
70 constexpr int width = 10;
71 constexpr uint32_t srcPixels[width] = {
72 0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271,
msarettb3906762016-06-22 14:07:48 -070073 0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, };
msarettdc27a642016-06-06 12:02:31 -070074 uint32_t dstPixels[width];
75
mtkleinb4359632016-07-18 11:16:14 -070076 // Create and perform an identity xform.
77 std::unique_ptr<SkColorSpaceXform> xform = ColorSpaceXformTest::CreateIdentityXform(gammas);
msarett31d097e82016-10-11 12:15:03 -070078 bool result = xform->apply(select_xform_format(kN32_SkColorType), dstPixels,
79 SkColorSpaceXform::kBGRA_8888_ColorFormat, srcPixels, width,
80 kOpaque_SkAlphaType);
81 REPORTER_ASSERT(r, result);
msarettdc27a642016-06-06 12:02:31 -070082
mtkleinb4359632016-07-18 11:16:14 -070083 // Since the src->dst matrix is the identity, and the gamma curves match,
84 // the pixels should be unchanged.
msarettdc27a642016-06-06 12:02:31 -070085 for (int i = 0; i < width; i++) {
msarettb3906762016-06-22 14:07:48 -070086 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 0) & 0xFF),
msarettcf7b8772016-09-22 12:37:04 -070087 SkGetPackedB32(dstPixels[i])));
msarettb3906762016-06-22 14:07:48 -070088 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 8) & 0xFF),
msarett9dc6cf62016-08-23 17:53:06 -070089 SkGetPackedG32(dstPixels[i])));
msarettb3906762016-06-22 14:07:48 -070090 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF),
msarettcf7b8772016-09-22 12:37:04 -070091 SkGetPackedR32(dstPixels[i])));
msarettb3906762016-06-22 14:07:48 -070092 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
msarett9dc6cf62016-08-23 17:53:06 -070093 SkGetPackedA32(dstPixels[i])));
msarettdc27a642016-06-06 12:02:31 -070094 }
Matt Sarettf4898862016-10-16 10:20:41 -040095
96 if (repeat) {
97 // We should cache part of the transform after the run. So it is interesting
98 // to make sure it still runs correctly the second time.
99 test_identity_xform(r, gammas, false);
100 }
msarettdc27a642016-06-06 12:02:31 -0700101}
102
raftias25636012016-11-11 15:27:39 -0800103static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNamed,
104 const sk_sp<SkGammas>& gammas, bool repeat) {
105 // Arbitrary set of 10 pixels
106 constexpr int width = 10;
107 constexpr uint32_t srcPixels[width] = {
108 0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271,
109 0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, };
110 uint32_t dstPixels[width];
111
112 // Create and perform an identity xform.
113 auto xform = ColorSpaceXformTest::CreateIdentityXform_A2B(gammaNamed, gammas);
114 bool result = xform->apply(select_xform_format(kN32_SkColorType), dstPixels,
115 SkColorSpaceXform::kBGRA_8888_ColorFormat, srcPixels, width,
116 kOpaque_SkAlphaType);
117 REPORTER_ASSERT(r, result);
118
119 // Since the src->dst matrix is the identity, and the gamma curves match,
120 // the pixels should be unchanged.
121 for (int i = 0; i < width; i++) {
122 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 0) & 0xFF),
123 SkGetPackedB32(dstPixels[i])));
124 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 8) & 0xFF),
125 SkGetPackedG32(dstPixels[i])));
126 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF),
127 SkGetPackedR32(dstPixels[i])));
128 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
129 SkGetPackedA32(dstPixels[i])));
130 }
131
132 if (repeat) {
133 // We should cache part of the transform after the run. So it is interesting
134 // to make sure it still runs correctly the second time.
135 test_identity_xform_A2B(r, gammaNamed, gammas, false);
136 }
137}
138
msarettdc27a642016-06-06 12:02:31 -0700139DEF_TEST(ColorSpaceXform_TableGamma, r) {
140 // Lookup-table based gamma curves
msarettdc27a642016-06-06 12:02:31 -0700141 constexpr size_t tableSize = 10;
msarett1b93bd12016-07-21 07:11:26 -0700142 void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize);
143 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas());
144 gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kTable_Type;
145 gammas->fRedData.fTable.fSize = gammas->fGreenData.fTable.fSize =
146 gammas->fBlueData.fTable.fSize = tableSize;
147 gammas->fRedData.fTable.fOffset = gammas->fGreenData.fTable.fOffset =
148 gammas->fBlueData.fTable.fOffset = 0;
149 float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
150
151 table[0] = 0.00f;
152 table[1] = 0.05f;
153 table[2] = 0.10f;
154 table[3] = 0.15f;
155 table[4] = 0.25f;
156 table[5] = 0.35f;
157 table[6] = 0.45f;
158 table[7] = 0.60f;
159 table[8] = 0.75f;
160 table[9] = 1.00f;
Matt Sarettf4898862016-10-16 10:20:41 -0400161 test_identity_xform(r, gammas, true);
raftias25636012016-11-11 15:27:39 -0800162 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
msarettdc27a642016-06-06 12:02:31 -0700163}
164
165DEF_TEST(ColorSpaceXform_ParametricGamma, r) {
166 // Parametric gamma curves
Matt Sarettdf44fc52016-10-11 16:57:50 -0400167 void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
msarett1b93bd12016-07-21 07:11:26 -0700168 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas());
169 gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kParam_Type;
170 gammas->fRedData.fParamOffset = gammas->fGreenData.fParamOffset =
171 gammas->fBlueData.fParamOffset = 0;
Matt Sarettdf44fc52016-10-11 16:57:50 -0400172 SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>
173 (memory, sizeof(SkGammas));
msarettdc27a642016-06-06 12:02:31 -0700174
msarettb3906762016-06-22 14:07:48 -0700175 // Interval, switch xforms at 0.0031308f
msarett1b93bd12016-07-21 07:11:26 -0700176 params->fD = 0.04045f;
msarettdc27a642016-06-06 12:02:31 -0700177
msarettb3906762016-06-22 14:07:48 -0700178 // First equation:
msarett1b93bd12016-07-21 07:11:26 -0700179 params->fE = 1.0f / 12.92f;
180 params->fF = 0.0f;
msarettdc27a642016-06-06 12:02:31 -0700181
msarettb3906762016-06-22 14:07:48 -0700182 // Second equation:
183 // Note that the function is continuous (it's actually sRGB).
msarett1b93bd12016-07-21 07:11:26 -0700184 params->fA = 1.0f / 1.055f;
185 params->fB = 0.055f / 1.055f;
186 params->fC = 0.0f;
187 params->fG = 2.4f;
Matt Sarettf4898862016-10-16 10:20:41 -0400188 test_identity_xform(r, gammas, true);
raftias25636012016-11-11 15:27:39 -0800189 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
msarettdc27a642016-06-06 12:02:31 -0700190}
191
192DEF_TEST(ColorSpaceXform_ExponentialGamma, r) {
193 // Exponential gamma curves
msarett1b93bd12016-07-21 07:11:26 -0700194 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas());
195 gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kValue_Type;
196 gammas->fRedData.fValue = gammas->fGreenData.fValue = gammas->fBlueData.fValue = 1.4f;
Matt Sarettf4898862016-10-16 10:20:41 -0400197 test_identity_xform(r, gammas, true);
raftias25636012016-11-11 15:27:39 -0800198 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
msarett1b93bd12016-07-21 07:11:26 -0700199}
200
msarett55bcc8e2016-09-09 07:48:05 -0700201DEF_TEST(ColorSpaceXform_NamedGamma, r) {
202 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas());
203 gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kNamed_Type;
204 gammas->fRedData.fNamed = kSRGB_SkGammaNamed;
205 gammas->fGreenData.fNamed = k2Dot2Curve_SkGammaNamed;
206 gammas->fBlueData.fNamed = kLinear_SkGammaNamed;
Matt Sarettf4898862016-10-16 10:20:41 -0400207 test_identity_xform(r, gammas, true);
raftias25636012016-11-11 15:27:39 -0800208 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
209 test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr, true);
210 test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr, true);
211 test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr, true);
msarett55bcc8e2016-09-09 07:48:05 -0700212}
213
msarett1b93bd12016-07-21 07:11:26 -0700214DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) {
215 constexpr size_t tableSize = 10;
216 void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize +
Matt Sarettdf44fc52016-10-11 16:57:50 -0400217 sizeof(SkColorSpaceTransferFn));
msarett1b93bd12016-07-21 07:11:26 -0700218 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas());
219
220 float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
221 table[0] = 0.00f;
222 table[1] = 0.15f;
223 table[2] = 0.20f;
224 table[3] = 0.25f;
225 table[4] = 0.35f;
226 table[5] = 0.45f;
227 table[6] = 0.55f;
228 table[7] = 0.70f;
229 table[8] = 0.85f;
230 table[9] = 1.00f;
231
Matt Sarettdf44fc52016-10-11 16:57:50 -0400232 SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>(memory,
233 sizeof(SkGammas) + sizeof(float) * tableSize);
msarett1b93bd12016-07-21 07:11:26 -0700234 params->fA = 1.0f / 1.055f;
235 params->fB = 0.055f / 1.055f;
236 params->fC = 0.0f;
237 params->fD = 0.04045f;
238 params->fE = 1.0f / 12.92f;
239 params->fF = 0.0f;
240 params->fG = 2.4f;
241
242 gammas->fRedType = SkGammas::Type::kValue_Type;
243 gammas->fRedData.fValue = 1.2f;
244
245 gammas->fGreenType = SkGammas::Type::kTable_Type;
246 gammas->fGreenData.fTable.fSize = tableSize;
247 gammas->fGreenData.fTable.fOffset = 0;
248
249 gammas->fBlueType = SkGammas::Type::kParam_Type;
250 gammas->fBlueData.fParamOffset = sizeof(float) * tableSize;
251
Matt Sarettf4898862016-10-16 10:20:41 -0400252 test_identity_xform(r, gammas, true);
raftias25636012016-11-11 15:27:39 -0800253 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
254}
255
256DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
257 constexpr int inputChannels = 3;
258 constexpr int gp = 4; // # grid points
259
260 constexpr int numEntries = gp*gp*gp*3;
261 uint8_t gridPoints[3] = {gp, gp, gp};
262 void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries);
263 sk_sp<SkColorLookUpTable> colorLUT(new (memory) SkColorLookUpTable(inputChannels, gridPoints));
264 // make a CLUT that rotates R, G, and B ie R->G, G->B, B->R
265 float* table = SkTAddOffset<float>(memory, sizeof(SkColorLookUpTable));
266 for (int r = 0; r < gp; ++r) {
267 for (int g = 0; g < gp; ++g) {
268 for (int b = 0; b < gp; ++b) {
269 table[3*(gp*gp*r + gp*g + b) + 0] = g * (1.f / (gp - 1.f));
270 table[3*(gp*gp*r + gp*g + b) + 1] = b * (1.f / (gp - 1.f));
271 table[3*(gp*gp*r + gp*g + b) + 2] = r * (1.f / (gp - 1.f));
272 }
273 }
274 }
275
276 // build an even distribution of pixels every (7 / 255) steps
277 // to test the xform on
278 constexpr int pixelgp = 7;
279 constexpr int numPixels = pixelgp*pixelgp*pixelgp;
280 SkAutoTMalloc<uint32_t> srcPixels(numPixels);
281 int srcIndex = 0;
282 for (int r = 0; r < pixelgp; ++r) {
283 for (int g = 0; g < pixelgp; ++g) {
284 for (int b = 0; b < pixelgp; ++b) {
285 const int red = (int) (r * (255.f / (pixelgp - 1.f)));
286 const int green = (int) (g * (255.f / (pixelgp - 1.f)));
287 const int blue = (int) (b * (255.f / (pixelgp - 1.f)));
288 srcPixels[srcIndex] = SkColorSetRGB(red, green, blue);
289 ++srcIndex;
290 }
291 }
292 }
293 SkAutoTMalloc<uint32_t> dstPixels(numPixels);
294
295 // src space is identity besides CLUT
296 std::vector<SkColorSpace_A2B::Element> srcElements;
297 srcElements.push_back(SkColorSpace_A2B::Element(std::move(colorLUT)));
298 auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
299 std::move(srcElements));
300 // dst space is entirely identity
301 auto dstSpace = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, SkMatrix44::I());
302 auto xform = SkColorSpaceXform::New(srcSpace.get(), dstSpace.get());
303 bool result = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, dstPixels.get(),
304 SkColorSpaceXform::kRGBA_8888_ColorFormat, srcPixels.get(),
305 numPixels, kOpaque_SkAlphaType);
306 REPORTER_ASSERT(r, result);
307
308 for (int i = 0; i < numPixels; ++i) {
309 REPORTER_ASSERT(r, almost_equal(SkColorGetR(srcPixels[i]),
310 SkColorGetG(dstPixels[i])));
311 REPORTER_ASSERT(r, almost_equal(SkColorGetG(srcPixels[i]),
312 SkColorGetB(dstPixels[i])));
313 REPORTER_ASSERT(r, almost_equal(SkColorGetB(srcPixels[i]),
314 SkColorGetR(dstPixels[i])));
315 }
msarettdc27a642016-06-06 12:02:31 -0700316}
raftiasc6cc28c2016-09-29 14:31:44 -0400317