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 "SkColorSpace.h"
9 #include "SkColorSpace_Base.h"
10 #include "SkColorSpace_XYZ.h"
11 #include "SkColorSpacePriv.h"
12 #include "SkOnce.h"
13 #include "SkPoint3.h"
14
toXYZD50(SkMatrix44 * toXYZ_D50) const15 bool SkColorSpacePrimaries::toXYZD50(SkMatrix44* toXYZ_D50) const {
16 if (!is_zero_to_one(fRX) || !is_zero_to_one(fRY) ||
17 !is_zero_to_one(fGX) || !is_zero_to_one(fGY) ||
18 !is_zero_to_one(fBX) || !is_zero_to_one(fBY) ||
19 !is_zero_to_one(fWX) || !is_zero_to_one(fWY))
20 {
21 return false;
22 }
23
24 // First, we need to convert xy values (primaries) to XYZ.
25 SkMatrix primaries;
26 primaries.setAll( fRX, fGX, fBX,
27 fRY, fGY, fBY,
28 1.0f - fRX - fRY, 1.0f - fGX - fGY, 1.0f - fBX - fBY);
29 SkMatrix primariesInv;
30 if (!primaries.invert(&primariesInv)) {
31 return false;
32 }
33
34 // Assumes that Y is 1.0f.
35 SkVector3 wXYZ = SkVector3::Make(fWX / fWY, 1.0f, (1.0f - fWX - fWY) / fWY);
36 SkVector3 XYZ;
37 XYZ.fX = primariesInv[0] * wXYZ.fX + primariesInv[1] * wXYZ.fY + primariesInv[2] * wXYZ.fZ;
38 XYZ.fY = primariesInv[3] * wXYZ.fX + primariesInv[4] * wXYZ.fY + primariesInv[5] * wXYZ.fZ;
39 XYZ.fZ = primariesInv[6] * wXYZ.fX + primariesInv[7] * wXYZ.fY + primariesInv[8] * wXYZ.fZ;
40 SkMatrix toXYZ;
41 toXYZ.setAll(XYZ.fX, 0.0f, 0.0f,
42 0.0f, XYZ.fY, 0.0f,
43 0.0f, 0.0f, XYZ.fZ);
44 toXYZ.postConcat(primaries);
45
46 // Now convert toXYZ matrix to toXYZD50.
47 SkVector3 wXYZD50 = SkVector3::Make(0.96422f, 1.0f, 0.82521f);
48
49 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
50 // the matrices below. The Bradford method is used by Adobe and is widely considered
51 // to be the best.
52 SkMatrix mA, mAInv;
53 mA.setAll(+0.8951f, +0.2664f, -0.1614f,
54 -0.7502f, +1.7135f, +0.0367f,
55 +0.0389f, -0.0685f, +1.0296f);
56 mAInv.setAll(+0.9869929f, -0.1470543f, +0.1599627f,
57 +0.4323053f, +0.5183603f, +0.0492912f,
58 -0.0085287f, +0.0400428f, +0.9684867f);
59
60 SkVector3 srcCone;
61 srcCone.fX = mA[0] * wXYZ.fX + mA[1] * wXYZ.fY + mA[2] * wXYZ.fZ;
62 srcCone.fY = mA[3] * wXYZ.fX + mA[4] * wXYZ.fY + mA[5] * wXYZ.fZ;
63 srcCone.fZ = mA[6] * wXYZ.fX + mA[7] * wXYZ.fY + mA[8] * wXYZ.fZ;
64 SkVector3 dstCone;
65 dstCone.fX = mA[0] * wXYZD50.fX + mA[1] * wXYZD50.fY + mA[2] * wXYZD50.fZ;
66 dstCone.fY = mA[3] * wXYZD50.fX + mA[4] * wXYZD50.fY + mA[5] * wXYZD50.fZ;
67 dstCone.fZ = mA[6] * wXYZD50.fX + mA[7] * wXYZD50.fY + mA[8] * wXYZD50.fZ;
68
69 SkMatrix DXToD50;
70 DXToD50.setIdentity();
71 DXToD50[0] = dstCone.fX / srcCone.fX;
72 DXToD50[4] = dstCone.fY / srcCone.fY;
73 DXToD50[8] = dstCone.fZ / srcCone.fZ;
74 DXToD50.postConcat(mAInv);
75 DXToD50.preConcat(mA);
76
77 toXYZ.postConcat(DXToD50);
78 toXYZ_D50->set3x3(toXYZ[0], toXYZ[3], toXYZ[6],
79 toXYZ[1], toXYZ[4], toXYZ[7],
80 toXYZ[2], toXYZ[5], toXYZ[8]);
81 return true;
82 }
83
84 ///////////////////////////////////////////////////////////////////////////////////////////////////
85
SkColorSpace_Base(sk_sp<SkData> profileData)86 SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkData> profileData)
87 : fProfileData(std::move(profileData))
88 {}
89
90 /**
91 * Checks if our toXYZ matrix is a close match to a known color gamut.
92 *
93 * @param toXYZD50 transformation matrix deduced from profile data
94 * @param standard 3x3 canonical transformation matrix
95 */
xyz_almost_equal(const SkMatrix44 & toXYZD50,const float * standard)96 static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) {
97 return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) &&
98 color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) &&
99 color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) &&
100 color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) &&
101 color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) &&
102 color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) &&
103 color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) &&
104 color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) &&
105 color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) &&
106 color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) &&
107 color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) &&
108 color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) &&
109 color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) &&
110 color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) &&
111 color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) &&
112 color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f);
113 }
114
MakeRGB(SkGammaNamed gammaNamed,const SkMatrix44 & toXYZD50)115 sk_sp<SkColorSpace> SkColorSpace_Base::MakeRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
116 {
117 switch (gammaNamed) {
118 case kSRGB_SkGammaNamed:
119 if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
120 return SkColorSpace_Base::MakeNamed(kSRGB_Named);
121 }
122 break;
123 case k2Dot2Curve_SkGammaNamed:
124 if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
125 return SkColorSpace_Base::MakeNamed(kAdobeRGB_Named);
126 }
127 break;
128 case kLinear_SkGammaNamed:
129 if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
130 return SkColorSpace_Base::MakeNamed(kSRGBLinear_Named);
131 }
132 break;
133 case kNonStandard_SkGammaNamed:
134 // This is not allowed.
135 return nullptr;
136 default:
137 break;
138 }
139
140 return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, toXYZD50));
141 }
142
MakeRGB(RenderTargetGamma gamma,const SkMatrix44 & toXYZD50)143 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) {
144 switch (gamma) {
145 case kLinear_RenderTargetGamma:
146 return SkColorSpace_Base::MakeRGB(kLinear_SkGammaNamed, toXYZD50);
147 case kSRGB_RenderTargetGamma:
148 return SkColorSpace_Base::MakeRGB(kSRGB_SkGammaNamed, toXYZD50);
149 default:
150 return nullptr;
151 }
152 }
153
MakeRGB(const SkColorSpaceTransferFn & coeffs,const SkMatrix44 & toXYZD50)154 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const SkColorSpaceTransferFn& coeffs,
155 const SkMatrix44& toXYZD50) {
156 if (!is_valid_transfer_fn(coeffs)) {
157 return nullptr;
158 }
159
160 if (is_almost_srgb(coeffs)) {
161 return SkColorSpace::MakeRGB(kSRGB_RenderTargetGamma, toXYZD50);
162 }
163
164 if (is_almost_2dot2(coeffs)) {
165 return SkColorSpace_Base::MakeRGB(k2Dot2Curve_SkGammaNamed, toXYZD50);
166 }
167
168 if (is_almost_linear(coeffs)) {
169 return SkColorSpace_Base::MakeRGB(kLinear_SkGammaNamed, toXYZD50);
170 }
171
172 void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
173 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
174 SkColorSpaceTransferFn* fn = SkTAddOffset<SkColorSpaceTransferFn>(memory, sizeof(SkGammas));
175 *fn = coeffs;
176 SkGammas::Data data;
177 data.fParamOffset = 0;
178 for (int channel = 0; channel < 3; ++channel) {
179 gammas->fType[channel] = SkGammas::Type::kParam_Type;
180 gammas->fData[channel] = data;
181 }
182 return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
183 std::move(gammas), toXYZD50, nullptr));
184 }
185
MakeRGB(RenderTargetGamma gamma,Gamut gamut)186 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(RenderTargetGamma gamma, Gamut gamut) {
187 SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
188 to_xyz_d50(&toXYZD50, gamut);
189 return SkColorSpace::MakeRGB(gamma, toXYZD50);
190 }
191
MakeRGB(const SkColorSpaceTransferFn & coeffs,Gamut gamut)192 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const SkColorSpaceTransferFn& coeffs, Gamut gamut) {
193 SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
194 to_xyz_d50(&toXYZD50, gamut);
195 return SkColorSpace::MakeRGB(coeffs, toXYZD50);
196 }
197
198 static SkColorSpace* gAdobeRGB;
199 static SkColorSpace* gSRGB;
200 static SkColorSpace* gSRGBLinear;
201
MakeNamed(Named named)202 sk_sp<SkColorSpace> SkColorSpace_Base::MakeNamed(Named named) {
203 static SkOnce sRGBOnce;
204 static SkOnce adobeRGBOnce;
205 static SkOnce sRGBLinearOnce;
206
207 switch (named) {
208 case kSRGB_Named: {
209 sRGBOnce([] {
210 SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
211 srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
212
213 // Force the mutable type mask to be computed. This avoids races.
214 (void)srgbToxyzD50.getType();
215 gSRGB = new SkColorSpace_XYZ(kSRGB_SkGammaNamed, srgbToxyzD50);
216 });
217 return sk_ref_sp<SkColorSpace>(gSRGB);
218 }
219 case kAdobeRGB_Named: {
220 adobeRGBOnce([] {
221 SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor);
222 adobergbToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50);
223
224 // Force the mutable type mask to be computed. This avoids races.
225 (void)adobergbToxyzD50.getType();
226 gAdobeRGB = new SkColorSpace_XYZ(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50);
227 });
228 return sk_ref_sp<SkColorSpace>(gAdobeRGB);
229 }
230 case kSRGBLinear_Named: {
231 sRGBLinearOnce([] {
232 SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
233 srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
234
235 // Force the mutable type mask to be computed. This avoids races.
236 (void)srgbToxyzD50.getType();
237 gSRGBLinear = new SkColorSpace_XYZ(kLinear_SkGammaNamed, srgbToxyzD50);
238 });
239 return sk_ref_sp<SkColorSpace>(gSRGBLinear);
240 }
241 default:
242 break;
243 }
244 return nullptr;
245 }
246
MakeSRGB()247 sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
248 return SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kSRGB_Named);
249 }
250
MakeSRGBLinear()251 sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
252 return SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kSRGBLinear_Named);
253 }
254
255 ///////////////////////////////////////////////////////////////////////////////////////////////////
256
gammaCloseToSRGB() const257 bool SkColorSpace::gammaCloseToSRGB() const {
258 return as_CSB(this)->onGammaCloseToSRGB();
259 }
260
gammaIsLinear() const261 bool SkColorSpace::gammaIsLinear() const {
262 return as_CSB(this)->onGammaIsLinear();
263 }
264
isNumericalTransferFn(SkColorSpaceTransferFn * fn) const265 bool SkColorSpace::isNumericalTransferFn(SkColorSpaceTransferFn* fn) const {
266 return as_CSB(this)->onIsNumericalTransferFn(fn);
267 }
268
toXYZD50(SkMatrix44 * toXYZD50) const269 bool SkColorSpace::toXYZD50(SkMatrix44* toXYZD50) const {
270 const SkMatrix44* matrix = as_CSB(this)->toXYZD50();
271 if (matrix) {
272 *toXYZD50 = *matrix;
273 return true;
274 }
275
276 return false;
277 }
278
isSRGB() const279 bool SkColorSpace::isSRGB() const {
280 return gSRGB == this;
281 }
282
283 ///////////////////////////////////////////////////////////////////////////////////////////////////
284
285 enum Version {
286 k0_Version, // Initial version, header + flags for matrix and profile
287 };
288
289 struct ColorSpaceHeader {
290 /**
291 * It is only valid to set zero or one flags.
292 * Setting multiple flags is invalid.
293 */
294
295 /**
296 * If kMatrix_Flag is set, we will write 12 floats after the header.
297 */
298 static constexpr uint8_t kMatrix_Flag = 1 << 0;
299
300 /**
301 * If kICC_Flag is set, we will write an ICC profile after the header.
302 * The ICC profile will be written as a uint32 size, followed immediately
303 * by the data (padded to 4 bytes).
304 */
305 static constexpr uint8_t kICC_Flag = 1 << 1;
306
307 /**
308 * If kTransferFn_Flag is set, we will write 19 floats after the header.
309 * The first seven represent the transfer fn, and the next twelve are the
310 * matrix.
311 */
312 static constexpr uint8_t kTransferFn_Flag = 1 << 3;
313
PackColorSpaceHeader314 static ColorSpaceHeader Pack(Version version, uint8_t named, uint8_t gammaNamed, uint8_t flags)
315 {
316 ColorSpaceHeader header;
317
318 SkASSERT(k0_Version == version);
319 header.fVersion = (uint8_t) version;
320
321 SkASSERT(named <= SkColorSpace_Base::kSRGBLinear_Named);
322 header.fNamed = (uint8_t) named;
323
324 SkASSERT(gammaNamed <= kNonStandard_SkGammaNamed);
325 header.fGammaNamed = (uint8_t) gammaNamed;
326
327 SkASSERT(flags <= kTransferFn_Flag);
328 header.fFlags = flags;
329 return header;
330 }
331
332 uint8_t fVersion; // Always zero
333 uint8_t fNamed; // Must be a SkColorSpace::Named
334 uint8_t fGammaNamed; // Must be a SkGammaNamed
335 uint8_t fFlags;
336 };
337
writeToMemory(void * memory) const338 size_t SkColorSpace::writeToMemory(void* memory) const {
339 // Start by trying the serialization fast path. If we haven't saved ICC profile data,
340 // we must have a profile that we can serialize easily.
341 if (!as_CSB(this)->fProfileData) {
342 // Profile data is mandatory for A2B0 color spaces.
343 SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(this)->type());
344 const SkColorSpace_XYZ* thisXYZ = static_cast<const SkColorSpace_XYZ*>(this);
345 // If we have a named profile, only write the enum.
346 const SkGammaNamed gammaNamed = thisXYZ->gammaNamed();
347 if (this == gSRGB) {
348 if (memory) {
349 *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
350 k0_Version, SkColorSpace_Base::kSRGB_Named, gammaNamed, 0);
351 }
352 return sizeof(ColorSpaceHeader);
353 } else if (this == gAdobeRGB) {
354 if (memory) {
355 *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
356 k0_Version, SkColorSpace_Base::kAdobeRGB_Named, gammaNamed, 0);
357 }
358 return sizeof(ColorSpaceHeader);
359 } else if (this == gSRGBLinear) {
360 if (memory) {
361 *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
362 k0_Version, SkColorSpace_Base::kSRGBLinear_Named, gammaNamed, 0);
363 }
364 return sizeof(ColorSpaceHeader);
365 }
366
367 // If we have a named gamma, write the enum and the matrix.
368 switch (gammaNamed) {
369 case kSRGB_SkGammaNamed:
370 case k2Dot2Curve_SkGammaNamed:
371 case kLinear_SkGammaNamed: {
372 if (memory) {
373 *((ColorSpaceHeader*) memory) =
374 ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
375 ColorSpaceHeader::kMatrix_Flag);
376 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
377 thisXYZ->toXYZD50()->as3x4RowMajorf((float*) memory);
378 }
379 return sizeof(ColorSpaceHeader) + 12 * sizeof(float);
380 }
381 default: {
382 const SkGammas* gammas = thisXYZ->gammas();
383 SkASSERT(gammas);
384 SkASSERT(gammas->isParametric(0));
385 SkASSERT(gammas->isParametric(1));
386 SkASSERT(gammas->isParametric(2));
387 SkASSERT(gammas->data(0) == gammas->data(1));
388 SkASSERT(gammas->data(0) == gammas->data(2));
389
390 if (memory) {
391 *((ColorSpaceHeader*) memory) =
392 ColorSpaceHeader::Pack(k0_Version, 0, thisXYZ->fGammaNamed,
393 ColorSpaceHeader::kTransferFn_Flag);
394 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
395
396 *(((float*) memory) + 0) = gammas->params(0).fA;
397 *(((float*) memory) + 1) = gammas->params(0).fB;
398 *(((float*) memory) + 2) = gammas->params(0).fC;
399 *(((float*) memory) + 3) = gammas->params(0).fD;
400 *(((float*) memory) + 4) = gammas->params(0).fE;
401 *(((float*) memory) + 5) = gammas->params(0).fF;
402 *(((float*) memory) + 6) = gammas->params(0).fG;
403 memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
404
405 thisXYZ->fToXYZD50.as3x4RowMajorf((float*) memory);
406 }
407
408 return sizeof(ColorSpaceHeader) + 19 * sizeof(float);
409 }
410 }
411 }
412
413 // Otherwise, serialize the ICC data.
414 size_t profileSize = as_CSB(this)->fProfileData->size();
415 if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) {
416 return 0;
417 }
418
419 if (memory) {
420 *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, 0,
421 kNonStandard_SkGammaNamed,
422 ColorSpaceHeader::kICC_Flag);
423 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
424
425 *((uint32_t*) memory) = (uint32_t) SkAlign4(profileSize);
426 memory = SkTAddOffset<void>(memory, sizeof(uint32_t));
427
428 memcpy(memory, as_CSB(this)->fProfileData->data(), profileSize);
429 memset(SkTAddOffset<void>(memory, profileSize), 0, SkAlign4(profileSize) - profileSize);
430 }
431 return sizeof(ColorSpaceHeader) + sizeof(uint32_t) + SkAlign4(profileSize);
432 }
433
serialize() const434 sk_sp<SkData> SkColorSpace::serialize() const {
435 size_t size = this->writeToMemory(nullptr);
436 if (0 == size) {
437 return nullptr;
438 }
439
440 sk_sp<SkData> data = SkData::MakeUninitialized(size);
441 this->writeToMemory(data->writable_data());
442 return data;
443 }
444
Deserialize(const void * data,size_t length)445 sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
446 if (length < sizeof(ColorSpaceHeader)) {
447 return nullptr;
448 }
449
450 ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
451 data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
452 length -= sizeof(ColorSpaceHeader);
453 if (0 == header.fFlags) {
454 return SkColorSpace_Base::MakeNamed((SkColorSpace_Base::Named) header.fNamed);
455 }
456
457 switch ((SkGammaNamed) header.fGammaNamed) {
458 case kSRGB_SkGammaNamed:
459 case k2Dot2Curve_SkGammaNamed:
460 case kLinear_SkGammaNamed: {
461 if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
462 return nullptr;
463 }
464
465 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
466 toXYZ.set3x4RowMajorf((const float*) data);
467 return SkColorSpace_Base::MakeRGB((SkGammaNamed) header.fGammaNamed, toXYZ);
468 }
469 default:
470 break;
471 }
472
473 switch (header.fFlags) {
474 case ColorSpaceHeader::kICC_Flag: {
475 if (length < sizeof(uint32_t)) {
476 return nullptr;
477 }
478
479 uint32_t profileSize = *((uint32_t*) data);
480 data = SkTAddOffset<const void>(data, sizeof(uint32_t));
481 length -= sizeof(uint32_t);
482 if (length < profileSize) {
483 return nullptr;
484 }
485
486 return MakeICC(data, profileSize);
487 }
488 case ColorSpaceHeader::kTransferFn_Flag: {
489 if (length < 19 * sizeof(float)) {
490 return nullptr;
491 }
492
493 SkColorSpaceTransferFn transferFn;
494 transferFn.fA = *(((const float*) data) + 0);
495 transferFn.fB = *(((const float*) data) + 1);
496 transferFn.fC = *(((const float*) data) + 2);
497 transferFn.fD = *(((const float*) data) + 3);
498 transferFn.fE = *(((const float*) data) + 4);
499 transferFn.fF = *(((const float*) data) + 5);
500 transferFn.fG = *(((const float*) data) + 6);
501 data = SkTAddOffset<const void>(data, 7 * sizeof(float));
502
503 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
504 toXYZ.set3x4RowMajorf((const float*) data);
505 return SkColorSpace::MakeRGB(transferFn, toXYZ);
506 }
507 default:
508 return nullptr;
509 }
510 }
511
Equals(const SkColorSpace * src,const SkColorSpace * dst)512 bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) {
513 if (src == dst) {
514 return true;
515 }
516
517 if (!src || !dst) {
518 return false;
519 }
520
521 SkData* srcData = as_CSB(src)->fProfileData.get();
522 SkData* dstData = as_CSB(dst)->fProfileData.get();
523 if (srcData || dstData) {
524 if (srcData && dstData) {
525 return srcData->size() == dstData->size() &&
526 0 == memcmp(srcData->data(), dstData->data(), srcData->size());
527 }
528
529 return false;
530 }
531
532 // profiles are mandatory for A2B0 color spaces
533 SkASSERT(as_CSB(src)->type() == SkColorSpace_Base::Type::kXYZ);
534 const SkColorSpace_XYZ* srcXYZ = static_cast<const SkColorSpace_XYZ*>(src);
535 const SkColorSpace_XYZ* dstXYZ = static_cast<const SkColorSpace_XYZ*>(dst);
536
537 if (srcXYZ->gammaNamed() != dstXYZ->gammaNamed()) {
538 return false;
539 }
540
541 switch (srcXYZ->gammaNamed()) {
542 case kSRGB_SkGammaNamed:
543 case k2Dot2Curve_SkGammaNamed:
544 case kLinear_SkGammaNamed:
545 if (srcXYZ->toXYZD50Hash() == dstXYZ->toXYZD50Hash()) {
546 SkASSERT(*srcXYZ->toXYZD50() == *dstXYZ->toXYZD50() && "Hash collision");
547 return true;
548 }
549 return false;
550 default:
551 // It is unlikely that we will reach this case.
552 sk_sp<SkData> serializedSrcData = src->serialize();
553 sk_sp<SkData> serializedDstData = dst->serialize();
554 return serializedSrcData->size() == serializedDstData->size() &&
555 0 == memcmp(serializedSrcData->data(), serializedDstData->data(),
556 serializedSrcData->size());
557 }
558 }
559
invert() const560 SkColorSpaceTransferFn SkColorSpaceTransferFn::invert() const {
561 // Original equation is: y = (ax + b)^g + e for x >= d
562 // y = cx + f otherwise
563 //
564 // so 1st inverse is: (y - e)^(1/g) = ax + b
565 // x = ((y - e)^(1/g) - b) / a
566 //
567 // which can be re-written as: x = (1/a)(y - e)^(1/g) - b/a
568 // x = ((1/a)^g)^(1/g) * (y - e)^(1/g) - b/a
569 // x = ([(1/a)^g]y + [-((1/a)^g)e]) ^ [1/g] + [-b/a]
570 //
571 // and 2nd inverse is: x = (y - f) / c
572 // which can be re-written as: x = [1/c]y + [-f/c]
573 //
574 // and now both can be expressed in terms of the same parametric form as the
575 // original - parameters are enclosed in square brackets.
576 SkColorSpaceTransferFn inv = { 0, 0, 0, 0, 0, 0, 0 };
577
578 // find inverse for linear segment (if possible)
579 if (!transfer_fn_almost_equal(0.f, fC)) {
580 inv.fC = 1.f / fC;
581 inv.fF = -fF / fC;
582 } else {
583 // otherwise assume it should be 0 as it is the lower segment
584 // as y = f is a constant function
585 }
586
587 // find inverse for the other segment (if possible)
588 if (transfer_fn_almost_equal(0.f, fA) || transfer_fn_almost_equal(0.f, fG)) {
589 // otherwise assume it should be 1 as it is the top segment
590 // as you can't invert the constant functions y = b^g + c, or y = 1 + c
591 inv.fG = 1.f;
592 inv.fE = 1.f;
593 } else {
594 inv.fG = 1.f / fG;
595 inv.fA = powf(1.f / fA, fG);
596 inv.fB = -inv.fA * fE;
597 inv.fE = -fB / fA;
598 }
599 inv.fD = fC * fD + fF;
600
601 return inv;
602 }
603