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