Line data Source code
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 :
15 0 : bool SkColorSpacePrimaries::toXYZD50(SkMatrix44* toXYZ_D50) const {
16 0 : if (!is_zero_to_one(fRX) || !is_zero_to_one(fRY) ||
17 0 : !is_zero_to_one(fGX) || !is_zero_to_one(fGY) ||
18 0 : !is_zero_to_one(fBX) || !is_zero_to_one(fBY) ||
19 0 : !is_zero_to_one(fWX) || !is_zero_to_one(fWY))
20 : {
21 0 : return false;
22 : }
23 :
24 : // First, we need to convert xy values (primaries) to XYZ.
25 : SkMatrix primaries;
26 0 : primaries.setAll( fRX, fGX, fBX,
27 0 : fRY, fGY, fBY,
28 0 : 1.0f - fRX - fRY, 1.0f - fGX - fGY, 1.0f - fBX - fBY);
29 : SkMatrix primariesInv;
30 0 : if (!primaries.invert(&primariesInv)) {
31 0 : return false;
32 : }
33 :
34 : // Assumes that Y is 1.0f.
35 0 : SkVector3 wXYZ = SkVector3::Make(fWX / fWY, 1.0f, (1.0f - fWX - fWY) / fWY);
36 : SkVector3 XYZ;
37 0 : XYZ.fX = primariesInv[0] * wXYZ.fX + primariesInv[1] * wXYZ.fY + primariesInv[2] * wXYZ.fZ;
38 0 : XYZ.fY = primariesInv[3] * wXYZ.fX + primariesInv[4] * wXYZ.fY + primariesInv[5] * wXYZ.fZ;
39 0 : XYZ.fZ = primariesInv[6] * wXYZ.fX + primariesInv[7] * wXYZ.fY + primariesInv[8] * wXYZ.fZ;
40 : SkMatrix toXYZ;
41 0 : toXYZ.setAll(XYZ.fX, 0.0f, 0.0f,
42 : 0.0f, XYZ.fY, 0.0f,
43 0 : 0.0f, 0.0f, XYZ.fZ);
44 0 : toXYZ.postConcat(primaries);
45 :
46 : // Now convert toXYZ matrix to toXYZD50.
47 0 : 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 : +0.0389f, -0.0685f, +1.0296f);
56 : mAInv.setAll(+0.9869929f, -0.1470543f, +0.1599627f,
57 : +0.4323053f, +0.5183603f, +0.0492912f,
58 0 : -0.0085287f, +0.0400428f, +0.9684867f);
59 :
60 : SkVector3 srcCone;
61 0 : srcCone.fX = mA[0] * wXYZ.fX + mA[1] * wXYZ.fY + mA[2] * wXYZ.fZ;
62 0 : srcCone.fY = mA[3] * wXYZ.fX + mA[4] * wXYZ.fY + mA[5] * wXYZ.fZ;
63 0 : srcCone.fZ = mA[6] * wXYZ.fX + mA[7] * wXYZ.fY + mA[8] * wXYZ.fZ;
64 : SkVector3 dstCone;
65 0 : dstCone.fX = mA[0] * wXYZD50.fX + mA[1] * wXYZD50.fY + mA[2] * wXYZD50.fZ;
66 0 : dstCone.fY = mA[3] * wXYZD50.fX + mA[4] * wXYZD50.fY + mA[5] * wXYZD50.fZ;
67 0 : dstCone.fZ = mA[6] * wXYZD50.fX + mA[7] * wXYZD50.fY + mA[8] * wXYZD50.fZ;
68 :
69 : SkMatrix DXToD50;
70 0 : DXToD50.setIdentity();
71 0 : DXToD50[0] = dstCone.fX / srcCone.fX;
72 0 : DXToD50[4] = dstCone.fY / srcCone.fY;
73 0 : DXToD50[8] = dstCone.fZ / srcCone.fZ;
74 0 : DXToD50.postConcat(mAInv);
75 0 : DXToD50.preConcat(mA);
76 :
77 0 : toXYZ.postConcat(DXToD50);
78 0 : toXYZ_D50->set3x3(toXYZ[0], toXYZ[3], toXYZ[6],
79 0 : toXYZ[1], toXYZ[4], toXYZ[7],
80 0 : toXYZ[2], toXYZ[5], toXYZ[8]);
81 0 : return true;
82 : }
83 :
84 : ///////////////////////////////////////////////////////////////////////////////////////////////////
85 :
86 1 : SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkData> profileData)
87 1 : : fProfileData(std::move(profileData))
88 1 : {}
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 : */
96 0 : static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) {
97 0 : return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) &&
98 0 : color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) &&
99 0 : color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) &&
100 0 : color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) &&
101 0 : color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) &&
102 0 : color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) &&
103 0 : color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) &&
104 0 : color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) &&
105 0 : color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) &&
106 0 : color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) &&
107 0 : color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) &&
108 0 : color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) &&
109 0 : color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) &&
110 0 : color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) &&
111 0 : color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) &&
112 0 : color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f);
113 : }
114 :
115 0 : sk_sp<SkColorSpace> SkColorSpace_Base::MakeRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
116 : {
117 0 : switch (gammaNamed) {
118 : case kSRGB_SkGammaNamed:
119 0 : if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
120 0 : return SkColorSpace_Base::MakeNamed(kSRGB_Named);
121 : }
122 0 : break;
123 : case k2Dot2Curve_SkGammaNamed:
124 0 : if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
125 0 : return SkColorSpace_Base::MakeNamed(kAdobeRGB_Named);
126 : }
127 0 : break;
128 : case kLinear_SkGammaNamed:
129 0 : if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
130 0 : return SkColorSpace_Base::MakeNamed(kSRGBLinear_Named);
131 : }
132 0 : break;
133 : case kNonStandard_SkGammaNamed:
134 : // This is not allowed.
135 0 : return nullptr;
136 : default:
137 0 : break;
138 : }
139 :
140 0 : return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, toXYZD50));
141 : }
142 :
143 0 : sk_sp<SkColorSpace> SkColorSpace::MakeRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) {
144 0 : switch (gamma) {
145 : case kLinear_RenderTargetGamma:
146 0 : return SkColorSpace_Base::MakeRGB(kLinear_SkGammaNamed, toXYZD50);
147 : case kSRGB_RenderTargetGamma:
148 0 : return SkColorSpace_Base::MakeRGB(kSRGB_SkGammaNamed, toXYZD50);
149 : default:
150 0 : return nullptr;
151 : }
152 : }
153 :
154 0 : sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const SkColorSpaceTransferFn& coeffs,
155 : const SkMatrix44& toXYZD50) {
156 0 : if (!is_valid_transfer_fn(coeffs)) {
157 0 : return nullptr;
158 : }
159 :
160 0 : if (is_almost_srgb(coeffs)) {
161 0 : return SkColorSpace::MakeRGB(kSRGB_RenderTargetGamma, toXYZD50);
162 : }
163 :
164 0 : if (is_almost_2dot2(coeffs)) {
165 0 : return SkColorSpace_Base::MakeRGB(k2Dot2Curve_SkGammaNamed, toXYZD50);
166 : }
167 :
168 0 : if (is_almost_linear(coeffs)) {
169 0 : return SkColorSpace_Base::MakeRGB(kLinear_SkGammaNamed, toXYZD50);
170 : }
171 :
172 0 : void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
173 0 : sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
174 0 : SkColorSpaceTransferFn* fn = SkTAddOffset<SkColorSpaceTransferFn>(memory, sizeof(SkGammas));
175 0 : *fn = coeffs;
176 0 : SkGammas::Data data;
177 0 : data.fParamOffset = 0;
178 0 : for (int channel = 0; channel < 3; ++channel) {
179 0 : gammas->fType[channel] = SkGammas::Type::kParam_Type;
180 0 : gammas->fData[channel] = data;
181 : }
182 : return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
183 0 : std::move(gammas), toXYZD50, nullptr));
184 : }
185 :
186 0 : sk_sp<SkColorSpace> SkColorSpace::MakeRGB(RenderTargetGamma gamma, Gamut gamut) {
187 0 : SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
188 0 : to_xyz_d50(&toXYZD50, gamut);
189 0 : return SkColorSpace::MakeRGB(gamma, toXYZD50);
190 : }
191 :
192 0 : sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const SkColorSpaceTransferFn& coeffs, Gamut gamut) {
193 0 : SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
194 0 : to_xyz_d50(&toXYZD50, gamut);
195 0 : return SkColorSpace::MakeRGB(coeffs, toXYZD50);
196 : }
197 :
198 : static SkColorSpace* gAdobeRGB;
199 : static SkColorSpace* gSRGB;
200 : static SkColorSpace* gSRGBLinear;
201 :
202 25 : sk_sp<SkColorSpace> SkColorSpace_Base::MakeNamed(Named named) {
203 : static SkOnce sRGBOnce;
204 : static SkOnce adobeRGBOnce;
205 : static SkOnce sRGBLinearOnce;
206 :
207 25 : switch (named) {
208 : case kSRGB_Named: {
209 0 : sRGBOnce([] {
210 0 : SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
211 0 : srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
212 :
213 : // Force the mutable type mask to be computed. This avoids races.
214 0 : (void)srgbToxyzD50.getType();
215 0 : gSRGB = new SkColorSpace_XYZ(kSRGB_SkGammaNamed, srgbToxyzD50);
216 0 : });
217 0 : return sk_ref_sp<SkColorSpace>(gSRGB);
218 : }
219 : case kAdobeRGB_Named: {
220 0 : adobeRGBOnce([] {
221 0 : SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor);
222 0 : adobergbToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50);
223 :
224 : // Force the mutable type mask to be computed. This avoids races.
225 0 : (void)adobergbToxyzD50.getType();
226 0 : gAdobeRGB = new SkColorSpace_XYZ(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50);
227 0 : });
228 0 : return sk_ref_sp<SkColorSpace>(gAdobeRGB);
229 : }
230 : case kSRGBLinear_Named: {
231 26 : sRGBLinearOnce([] {
232 1 : SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
233 1 : srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
234 :
235 : // Force the mutable type mask to be computed. This avoids races.
236 1 : (void)srgbToxyzD50.getType();
237 1 : gSRGBLinear = new SkColorSpace_XYZ(kLinear_SkGammaNamed, srgbToxyzD50);
238 26 : });
239 25 : return sk_ref_sp<SkColorSpace>(gSRGBLinear);
240 : }
241 : default:
242 0 : break;
243 : }
244 0 : return nullptr;
245 : }
246 :
247 0 : sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
248 0 : return SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kSRGB_Named);
249 : }
250 :
251 25 : sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
252 25 : return SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kSRGBLinear_Named);
253 : }
254 :
255 : ///////////////////////////////////////////////////////////////////////////////////////////////////
256 :
257 0 : bool SkColorSpace::gammaCloseToSRGB() const {
258 0 : return as_CSB(this)->onGammaCloseToSRGB();
259 : }
260 :
261 0 : bool SkColorSpace::gammaIsLinear() const {
262 0 : return as_CSB(this)->onGammaIsLinear();
263 : }
264 :
265 0 : bool SkColorSpace::isNumericalTransferFn(SkColorSpaceTransferFn* fn) const {
266 0 : return as_CSB(this)->onIsNumericalTransferFn(fn);
267 : }
268 :
269 0 : bool SkColorSpace::toXYZD50(SkMatrix44* toXYZD50) const {
270 0 : const SkMatrix44* matrix = as_CSB(this)->toXYZD50();
271 0 : if (matrix) {
272 0 : *toXYZD50 = *matrix;
273 0 : return true;
274 : }
275 :
276 0 : return false;
277 : }
278 :
279 0 : bool SkColorSpace::isSRGB() const {
280 0 : 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 :
314 0 : static ColorSpaceHeader Pack(Version version, uint8_t named, uint8_t gammaNamed, uint8_t flags)
315 : {
316 : ColorSpaceHeader header;
317 :
318 0 : SkASSERT(k0_Version == version);
319 0 : header.fVersion = (uint8_t) version;
320 :
321 0 : SkASSERT(named <= SkColorSpace_Base::kSRGBLinear_Named);
322 0 : header.fNamed = (uint8_t) named;
323 :
324 0 : SkASSERT(gammaNamed <= kNonStandard_SkGammaNamed);
325 0 : header.fGammaNamed = (uint8_t) gammaNamed;
326 :
327 0 : SkASSERT(flags <= kTransferFn_Flag);
328 0 : header.fFlags = flags;
329 0 : 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 :
338 0 : 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 0 : if (!as_CSB(this)->fProfileData) {
342 : // Profile data is mandatory for A2B0 color spaces.
343 0 : SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(this)->type());
344 0 : const SkColorSpace_XYZ* thisXYZ = static_cast<const SkColorSpace_XYZ*>(this);
345 : // If we have a named profile, only write the enum.
346 0 : const SkGammaNamed gammaNamed = thisXYZ->gammaNamed();
347 0 : if (this == gSRGB) {
348 0 : if (memory) {
349 : *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
350 0 : k0_Version, SkColorSpace_Base::kSRGB_Named, gammaNamed, 0);
351 : }
352 0 : return sizeof(ColorSpaceHeader);
353 0 : } else if (this == gAdobeRGB) {
354 0 : if (memory) {
355 : *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
356 0 : k0_Version, SkColorSpace_Base::kAdobeRGB_Named, gammaNamed, 0);
357 : }
358 0 : return sizeof(ColorSpaceHeader);
359 0 : } else if (this == gSRGBLinear) {
360 0 : if (memory) {
361 : *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
362 0 : k0_Version, SkColorSpace_Base::kSRGBLinear_Named, gammaNamed, 0);
363 : }
364 0 : return sizeof(ColorSpaceHeader);
365 : }
366 :
367 : // If we have a named gamma, write the enum and the matrix.
368 0 : switch (gammaNamed) {
369 : case kSRGB_SkGammaNamed:
370 : case k2Dot2Curve_SkGammaNamed:
371 : case kLinear_SkGammaNamed: {
372 0 : if (memory) {
373 : *((ColorSpaceHeader*) memory) =
374 : ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
375 0 : ColorSpaceHeader::kMatrix_Flag);
376 0 : memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
377 0 : thisXYZ->toXYZD50()->as3x4RowMajorf((float*) memory);
378 : }
379 0 : return sizeof(ColorSpaceHeader) + 12 * sizeof(float);
380 : }
381 : default: {
382 0 : const SkGammas* gammas = thisXYZ->gammas();
383 0 : SkASSERT(gammas);
384 0 : SkASSERT(gammas->isParametric(0));
385 0 : SkASSERT(gammas->isParametric(1));
386 0 : SkASSERT(gammas->isParametric(2));
387 0 : SkASSERT(gammas->data(0) == gammas->data(1));
388 0 : SkASSERT(gammas->data(0) == gammas->data(2));
389 :
390 0 : if (memory) {
391 : *((ColorSpaceHeader*) memory) =
392 0 : ColorSpaceHeader::Pack(k0_Version, 0, thisXYZ->fGammaNamed,
393 0 : ColorSpaceHeader::kTransferFn_Flag);
394 0 : memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
395 :
396 0 : *(((float*) memory) + 0) = gammas->params(0).fA;
397 0 : *(((float*) memory) + 1) = gammas->params(0).fB;
398 0 : *(((float*) memory) + 2) = gammas->params(0).fC;
399 0 : *(((float*) memory) + 3) = gammas->params(0).fD;
400 0 : *(((float*) memory) + 4) = gammas->params(0).fE;
401 0 : *(((float*) memory) + 5) = gammas->params(0).fF;
402 0 : *(((float*) memory) + 6) = gammas->params(0).fG;
403 0 : memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
404 :
405 0 : thisXYZ->fToXYZD50.as3x4RowMajorf((float*) memory);
406 : }
407 :
408 0 : return sizeof(ColorSpaceHeader) + 19 * sizeof(float);
409 : }
410 : }
411 : }
412 :
413 : // Otherwise, serialize the ICC data.
414 0 : size_t profileSize = as_CSB(this)->fProfileData->size();
415 0 : if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) {
416 0 : return 0;
417 : }
418 :
419 0 : if (memory) {
420 : *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, 0,
421 : kNonStandard_SkGammaNamed,
422 0 : ColorSpaceHeader::kICC_Flag);
423 0 : memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
424 :
425 0 : *((uint32_t*) memory) = (uint32_t) SkAlign4(profileSize);
426 0 : memory = SkTAddOffset<void>(memory, sizeof(uint32_t));
427 :
428 0 : memcpy(memory, as_CSB(this)->fProfileData->data(), profileSize);
429 0 : memset(SkTAddOffset<void>(memory, profileSize), 0, SkAlign4(profileSize) - profileSize);
430 : }
431 0 : return sizeof(ColorSpaceHeader) + sizeof(uint32_t) + SkAlign4(profileSize);
432 : }
433 :
434 0 : sk_sp<SkData> SkColorSpace::serialize() const {
435 0 : size_t size = this->writeToMemory(nullptr);
436 0 : if (0 == size) {
437 0 : return nullptr;
438 : }
439 :
440 0 : sk_sp<SkData> data = SkData::MakeUninitialized(size);
441 0 : this->writeToMemory(data->writable_data());
442 0 : return data;
443 : }
444 :
445 0 : sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
446 0 : if (length < sizeof(ColorSpaceHeader)) {
447 0 : return nullptr;
448 : }
449 :
450 0 : ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
451 0 : data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
452 0 : length -= sizeof(ColorSpaceHeader);
453 0 : if (0 == header.fFlags) {
454 0 : return SkColorSpace_Base::MakeNamed((SkColorSpace_Base::Named) header.fNamed);
455 : }
456 :
457 0 : switch ((SkGammaNamed) header.fGammaNamed) {
458 : case kSRGB_SkGammaNamed:
459 : case k2Dot2Curve_SkGammaNamed:
460 : case kLinear_SkGammaNamed: {
461 0 : if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
462 0 : return nullptr;
463 : }
464 :
465 0 : SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
466 0 : toXYZ.set3x4RowMajorf((const float*) data);
467 0 : return SkColorSpace_Base::MakeRGB((SkGammaNamed) header.fGammaNamed, toXYZ);
468 : }
469 : default:
470 0 : break;
471 : }
472 :
473 0 : switch (header.fFlags) {
474 : case ColorSpaceHeader::kICC_Flag: {
475 0 : if (length < sizeof(uint32_t)) {
476 0 : return nullptr;
477 : }
478 :
479 0 : uint32_t profileSize = *((uint32_t*) data);
480 0 : data = SkTAddOffset<const void>(data, sizeof(uint32_t));
481 0 : length -= sizeof(uint32_t);
482 0 : if (length < profileSize) {
483 0 : return nullptr;
484 : }
485 :
486 0 : return MakeICC(data, profileSize);
487 : }
488 : case ColorSpaceHeader::kTransferFn_Flag: {
489 0 : if (length < 19 * sizeof(float)) {
490 0 : return nullptr;
491 : }
492 :
493 : SkColorSpaceTransferFn transferFn;
494 0 : transferFn.fA = *(((const float*) data) + 0);
495 0 : transferFn.fB = *(((const float*) data) + 1);
496 0 : transferFn.fC = *(((const float*) data) + 2);
497 0 : transferFn.fD = *(((const float*) data) + 3);
498 0 : transferFn.fE = *(((const float*) data) + 4);
499 0 : transferFn.fF = *(((const float*) data) + 5);
500 0 : transferFn.fG = *(((const float*) data) + 6);
501 0 : data = SkTAddOffset<const void>(data, 7 * sizeof(float));
502 :
503 0 : SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
504 0 : toXYZ.set3x4RowMajorf((const float*) data);
505 0 : return SkColorSpace::MakeRGB(transferFn, toXYZ);
506 : }
507 : default:
508 0 : return nullptr;
509 : }
510 : }
511 :
512 0 : bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) {
513 0 : if (src == dst) {
514 0 : return true;
515 : }
516 :
517 0 : if (!src || !dst) {
518 0 : return false;
519 : }
520 :
521 0 : SkData* srcData = as_CSB(src)->fProfileData.get();
522 0 : SkData* dstData = as_CSB(dst)->fProfileData.get();
523 0 : if (srcData || dstData) {
524 0 : if (srcData && dstData) {
525 0 : return srcData->size() == dstData->size() &&
526 0 : 0 == memcmp(srcData->data(), dstData->data(), srcData->size());
527 : }
528 :
529 0 : return false;
530 : }
531 :
532 : // profiles are mandatory for A2B0 color spaces
533 0 : SkASSERT(as_CSB(src)->type() == SkColorSpace_Base::Type::kXYZ);
534 0 : const SkColorSpace_XYZ* srcXYZ = static_cast<const SkColorSpace_XYZ*>(src);
535 0 : const SkColorSpace_XYZ* dstXYZ = static_cast<const SkColorSpace_XYZ*>(dst);
536 :
537 0 : if (srcXYZ->gammaNamed() != dstXYZ->gammaNamed()) {
538 0 : return false;
539 : }
540 :
541 0 : switch (srcXYZ->gammaNamed()) {
542 : case kSRGB_SkGammaNamed:
543 : case k2Dot2Curve_SkGammaNamed:
544 : case kLinear_SkGammaNamed:
545 0 : if (srcXYZ->toXYZD50Hash() == dstXYZ->toXYZD50Hash()) {
546 0 : SkASSERT(*srcXYZ->toXYZD50() == *dstXYZ->toXYZD50() && "Hash collision");
547 0 : return true;
548 : }
549 0 : return false;
550 : default:
551 : // It is unlikely that we will reach this case.
552 0 : sk_sp<SkData> serializedSrcData = src->serialize();
553 0 : sk_sp<SkData> serializedDstData = dst->serialize();
554 0 : return serializedSrcData->size() == serializedDstData->size() &&
555 0 : 0 == memcmp(serializedSrcData->data(), serializedDstData->data(),
556 0 : serializedSrcData->size());
557 : }
558 : }
559 :
560 0 : 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 0 : SkColorSpaceTransferFn inv = { 0, 0, 0, 0, 0, 0, 0 };
577 :
578 : // find inverse for linear segment (if possible)
579 0 : if (!transfer_fn_almost_equal(0.f, fC)) {
580 0 : inv.fC = 1.f / fC;
581 0 : 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 0 : 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 0 : inv.fG = 1.f;
592 0 : inv.fE = 1.f;
593 : } else {
594 0 : inv.fG = 1.f / fG;
595 0 : inv.fA = powf(1.f / fA, fG);
596 0 : inv.fB = -inv.fA * fE;
597 0 : inv.fE = -fB / fA;
598 : }
599 0 : inv.fD = fC * fD + fF;
600 :
601 0 : return inv;
602 : }
|