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 "include/core/SkColorSpace.h"
9 #include "include/core/SkData.h"
10 #include "include/private/SkTemplates.h"
11 #include "include/third_party/skcms/skcms.h"
12 #include "src/core/SkColorSpacePriv.h"
13 #include "src/core/SkOpts.h"
14
toXYZD50(skcms_Matrix3x3 * toXYZ_D50) const15 bool SkColorSpacePrimaries::toXYZD50(skcms_Matrix3x3* toXYZ_D50) const {
16 return skcms_PrimariesToXYZD50(fRX, fRY, fGX, fGY, fBX, fBY, fWX, fWY, toXYZ_D50);
17 }
18
SkColorSpace(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & toXYZD50)19 SkColorSpace::SkColorSpace(const skcms_TransferFunction& transferFn,
20 const skcms_Matrix3x3& toXYZD50)
21 : fTransferFn(transferFn)
22 , fToXYZD50(toXYZD50) {
23 fTransferFnHash = SkOpts::hash_fn(&fTransferFn, 7*sizeof(float), 0);
24 fToXYZD50Hash = SkOpts::hash_fn(&fToXYZD50, 9*sizeof(float), 0);
25 hasCicp = false;
26 }
27
xyz_almost_equal(const skcms_Matrix3x3 & mA,const skcms_Matrix3x3 & mB)28 static bool xyz_almost_equal(const skcms_Matrix3x3& mA, const skcms_Matrix3x3& mB) {
29 for (int r = 0; r < 3; ++r) {
30 for (int c = 0; c < 3; ++c) {
31 if (!color_space_almost_equal(mA.vals[r][c], mB.vals[r][c])) {
32 return false;
33 }
34 }
35 }
36
37 return true;
38 }
39
MakeRGB(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & toXYZ)40 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn,
41 const skcms_Matrix3x3& toXYZ) {
42 if (classify_transfer_fn(transferFn) == Bad_TF) {
43 return nullptr;
44 }
45
46 const skcms_TransferFunction* tf = &transferFn;
47
48 if (is_almost_srgb(transferFn)) {
49 if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
50 return SkColorSpace::MakeSRGB();
51 }
52 tf = &SkNamedTransferFn::kSRGB;
53 } else if (is_almost_2dot2(transferFn)) {
54 tf = &SkNamedTransferFn::k2Dot2;
55 } else if (is_almost_linear(transferFn)) {
56 if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
57 return SkColorSpace::MakeSRGBLinear();
58 }
59 tf = &SkNamedTransferFn::kLinear;
60 }
61
62 return sk_sp<SkColorSpace>(new SkColorSpace(*tf, toXYZ));
63 }
64
65 class SkColorSpaceSingletonFactory {
66 public:
Make(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & to_xyz)67 static SkColorSpace* Make(const skcms_TransferFunction& transferFn,
68 const skcms_Matrix3x3& to_xyz) {
69 return new SkColorSpace(transferFn, to_xyz);
70 }
71 };
72
sk_srgb_singleton()73 SkColorSpace* sk_srgb_singleton() {
74 static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kSRGB,
75 SkNamedGamut::kSRGB);
76 return cs;
77 }
78
sk_srgb_linear_singleton()79 SkColorSpace* sk_srgb_linear_singleton() {
80 static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kLinear,
81 SkNamedGamut::kSRGB);
82 return cs;
83 }
84
MakeSRGB()85 sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
86 return sk_ref_sp(sk_srgb_singleton());
87 }
88
MakeSRGBLinear()89 sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
90 return sk_ref_sp(sk_srgb_linear_singleton());
91 }
92
computeLazyDstFields() const93 void SkColorSpace::computeLazyDstFields() const {
94 fLazyDstFieldsOnce([this] {
95
96 // Invert 3x3 gamut, defaulting to sRGB if we can't.
97 {
98 if (!skcms_Matrix3x3_invert(&fToXYZD50, &fFromXYZD50)) {
99 SkAssertResult(skcms_Matrix3x3_invert(&skcms_sRGB_profile()->toXYZD50,
100 &fFromXYZD50));
101 }
102 }
103
104 // Invert transfer function, defaulting to sRGB if we can't.
105 {
106 if (!skcms_TransferFunction_invert(&fTransferFn, &fInvTransferFn)) {
107 fInvTransferFn = *skcms_sRGB_Inverse_TransferFunction();
108 }
109 }
110
111 });
112 }
113
isNumericalTransferFn(skcms_TransferFunction * coeffs) const114 bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const {
115 // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers
116 // already pass pointers to an skcms struct). Then remove this function, and update the two
117 // remaining callers to do the right thing with transferFn and classify.
118 this->transferFn(coeffs);
119 return classify_transfer_fn(*coeffs) == sRGBish_TF;
120 }
121
transferFn(float gabcdef[7]) const122 void SkColorSpace::transferFn(float gabcdef[7]) const {
123 memcpy(gabcdef, &fTransferFn, 7*sizeof(float));
124 }
125
transferFn(skcms_TransferFunction * fn) const126 void SkColorSpace::transferFn(skcms_TransferFunction* fn) const {
127 *fn = fTransferFn;
128 }
129
invTransferFn(skcms_TransferFunction * fn) const130 void SkColorSpace::invTransferFn(skcms_TransferFunction* fn) const {
131 this->computeLazyDstFields();
132 *fn = fInvTransferFn;
133 }
134
toXYZD50(skcms_Matrix3x3 * toXYZD50) const135 bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const {
136 *toXYZD50 = fToXYZD50;
137 return true;
138 }
139
gamutTransformTo(const SkColorSpace * dst,skcms_Matrix3x3 * src_to_dst) const140 void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, skcms_Matrix3x3* src_to_dst) const {
141 dst->computeLazyDstFields();
142 *src_to_dst = skcms_Matrix3x3_concat(&dst->fFromXYZD50, &fToXYZD50);
143 }
144
isSRGB() const145 bool SkColorSpace::isSRGB() const {
146 return sk_srgb_singleton() == this;
147 }
148
gammaCloseToSRGB() const149 bool SkColorSpace::gammaCloseToSRGB() const {
150 // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
151 return memcmp(&fTransferFn, &SkNamedTransferFn::kSRGB, 7*sizeof(float)) == 0;
152 }
153
gammaIsLinear() const154 bool SkColorSpace::gammaIsLinear() const {
155 // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
156 return memcmp(&fTransferFn, &SkNamedTransferFn::kLinear, 7*sizeof(float)) == 0;
157 }
158
makeLinearGamma() const159 sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() const {
160 if (this->gammaIsLinear()) {
161 return sk_ref_sp(const_cast<SkColorSpace*>(this));
162 }
163 return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, fToXYZD50);
164 }
165
makeSRGBGamma() const166 sk_sp<SkColorSpace> SkColorSpace::makeSRGBGamma() const {
167 if (this->gammaCloseToSRGB()) {
168 return sk_ref_sp(const_cast<SkColorSpace*>(this));
169 }
170 return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, fToXYZD50);
171 }
172
makeColorSpin() const173 sk_sp<SkColorSpace> SkColorSpace::makeColorSpin() const {
174 skcms_Matrix3x3 spin = {{
175 { 0, 0, 1 },
176 { 1, 0, 0 },
177 { 0, 1, 0 },
178 }};
179
180 skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&fToXYZD50, &spin);
181
182 return sk_sp<SkColorSpace>(new SkColorSpace(fTransferFn, spun));
183 }
184
SetIccCicp(const skcms_CICP cicp)185 void SkColorSpace::SetIccCicp(const skcms_CICP cicp) {
186 fcicp = cicp;
187 hasCicp = true;
188 }
189
GetIccCicp(skcms_CICP * cicp) const190 bool SkColorSpace::GetIccCicp(skcms_CICP* cicp) const {
191 if (hasCicp) {
192 *cicp = fcicp;
193 return true;
194 }
195 return false;
196 }
197
toProfile(skcms_ICCProfile * profile) const198 void SkColorSpace::toProfile(skcms_ICCProfile* profile) const {
199 skcms_Init (profile);
200 skcms_SetTransferFunction(profile, &fTransferFn);
201 skcms_SetXYZD50 (profile, &fToXYZD50);
202 }
203
Make(const skcms_ICCProfile & profile)204 sk_sp<SkColorSpace> SkColorSpace::Make(const skcms_ICCProfile& profile) {
205 // TODO: move below ≈sRGB test?
206 if (!profile.has_toXYZD50 || !profile.has_trc) {
207 return nullptr;
208 }
209
210 if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) {
211 return SkColorSpace::MakeSRGB();
212 }
213
214 // TODO: can we save this work and skip lazily inverting the matrix later?
215 skcms_Matrix3x3 inv;
216 if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) {
217 return nullptr;
218 }
219
220 // We can't work with tables or mismatched parametric curves,
221 // but if they all look close enough to sRGB, that's fine.
222 // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB?
223 const skcms_Curve* trc = profile.trc;
224 if (trc[0].table_entries != 0 ||
225 trc[1].table_entries != 0 ||
226 trc[2].table_entries != 0 ||
227 0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) ||
228 0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric)))
229 {
230 if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) {
231 return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50);
232 }
233 return nullptr;
234 }
235
236 return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50);
237 }
238
239 ///////////////////////////////////////////////////////////////////////////////////////////////////
240
241 enum Version {
242 k0_Version, // Initial version, header + flags for matrix and profile
243 k1_Version, // Simple header (version tag) + 16 floats
244
245 kCurrent_Version = k1_Version,
246 };
247
248 enum NamedColorSpace {
249 kSRGB_NamedColorSpace,
250 kAdobeRGB_NamedColorSpace,
251 kSRGBLinear_NamedColorSpace,
252 };
253
254 enum NamedGamma {
255 kLinear_NamedGamma,
256 kSRGB_NamedGamma,
257 k2Dot2_NamedGamma,
258 };
259
260 struct ColorSpaceHeader {
261 // Flag values, only used by old (k0_Version) serialization
262 inline static constexpr uint8_t kMatrix_Flag = 1 << 0;
263 inline static constexpr uint8_t kICC_Flag = 1 << 1;
264 inline static constexpr uint8_t kTransferFn_Flag = 1 << 3;
265
266 uint8_t fVersion = kCurrent_Version;
267
268 // Other fields are only used by k0_Version. Could be re-purposed in future versions.
269 uint8_t fNamed = 0;
270 uint8_t fGammaNamed = 0;
271 uint8_t fFlags = 0;
272 };
273
writeToMemory(void * memory) const274 size_t SkColorSpace::writeToMemory(void* memory) const {
275 if (memory) {
276 *((ColorSpaceHeader*) memory) = ColorSpaceHeader();
277 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
278
279 memcpy(memory, &fTransferFn, 7 * sizeof(float));
280 memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
281
282 memcpy(memory, &fToXYZD50, 9 * sizeof(float));
283 }
284
285 return sizeof(ColorSpaceHeader) + 16 * sizeof(float);
286 }
287
serialize() const288 sk_sp<SkData> SkColorSpace::serialize() const {
289 sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr));
290 this->writeToMemory(data->writable_data());
291 return data;
292 }
293
Deserialize(const void * data,size_t length)294 sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
295 if (length < sizeof(ColorSpaceHeader)) {
296 return nullptr;
297 }
298
299 ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
300 data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
301 length -= sizeof(ColorSpaceHeader);
302 if (k1_Version == header.fVersion) {
303 if (length < 16 * sizeof(float)) {
304 return nullptr;
305 }
306
307 skcms_TransferFunction transferFn;
308 memcpy(&transferFn, data, 7 * sizeof(float));
309 data = SkTAddOffset<const void>(data, 7 * sizeof(float));
310
311 skcms_Matrix3x3 toXYZ;
312 memcpy(&toXYZ, data, 9 * sizeof(float));
313 return SkColorSpace::MakeRGB(transferFn, toXYZ);
314 } else if (k0_Version == header.fVersion) {
315 if (0 == header.fFlags) {
316 switch ((NamedColorSpace)header.fNamed) {
317 case kSRGB_NamedColorSpace:
318 return SkColorSpace::MakeSRGB();
319 case kSRGBLinear_NamedColorSpace:
320 return SkColorSpace::MakeSRGBLinear();
321 case kAdobeRGB_NamedColorSpace:
322 return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2,
323 SkNamedGamut::kAdobeRGB);
324 }
325 }
326
327 auto make_named_tf = [=](const skcms_TransferFunction& tf) {
328 if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
329 return sk_sp<SkColorSpace>(nullptr);
330 }
331
332 // Version 0 matrix is row-major 3x4
333 skcms_Matrix3x3 toXYZ;
334 memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
335 memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
336 memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
337 return SkColorSpace::MakeRGB(tf, toXYZ);
338 };
339
340 switch ((NamedGamma) header.fGammaNamed) {
341 case kSRGB_NamedGamma:
342 return make_named_tf(SkNamedTransferFn::kSRGB);
343 case k2Dot2_NamedGamma:
344 return make_named_tf(SkNamedTransferFn::k2Dot2);
345 case kLinear_NamedGamma:
346 return make_named_tf(SkNamedTransferFn::kLinear);
347 default:
348 break;
349 }
350
351 switch (header.fFlags) {
352 case ColorSpaceHeader::kICC_Flag: {
353 // Deprecated and unsupported code path
354 return nullptr;
355 }
356 case ColorSpaceHeader::kTransferFn_Flag: {
357 if (length < 19 * sizeof(float)) {
358 return nullptr;
359 }
360
361 // Version 0 TF is in abcdefg order
362 skcms_TransferFunction transferFn;
363 transferFn.a = *(((const float*) data) + 0);
364 transferFn.b = *(((const float*) data) + 1);
365 transferFn.c = *(((const float*) data) + 2);
366 transferFn.d = *(((const float*) data) + 3);
367 transferFn.e = *(((const float*) data) + 4);
368 transferFn.f = *(((const float*) data) + 5);
369 transferFn.g = *(((const float*) data) + 6);
370 data = SkTAddOffset<const void>(data, 7 * sizeof(float));
371
372 // Version 0 matrix is row-major 3x4
373 skcms_Matrix3x3 toXYZ;
374 memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
375 memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
376 memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
377 return SkColorSpace::MakeRGB(transferFn, toXYZ);
378 }
379 default:
380 return nullptr;
381 }
382 } else {
383 return nullptr;
384 }
385 }
386
Equals(const SkColorSpace * x,const SkColorSpace * y)387 bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) {
388 if (x == y) {
389 return true;
390 }
391
392 if (!x || !y) {
393 return false;
394 }
395
396 if (x->hash() == y->hash()) {
397 #if defined(SK_DEBUG)
398 // Do these floats function equivalently?
399 // This returns true more often than simple float comparison (NaN vs. NaN) and,
400 // also returns true more often than simple bitwise comparison (+0 vs. -0) and,
401 // even returns true more often than those two OR'd together (two different NaNs).
402 auto equiv = [](float X, float Y) {
403 return (X==Y)
404 || (sk_float_isnan(X) && sk_float_isnan(Y));
405 };
406
407 for (int i = 0; i < 7; i++) {
408 float X = (&x->fTransferFn.g)[i],
409 Y = (&y->fTransferFn.g)[i];
410 SkASSERTF(equiv(X,Y), "Hash collision at tf[%d], !equiv(%g,%g)\n", i, X,Y);
411 }
412 for (int r = 0; r < 3; r++)
413 for (int c = 0; c < 3; c++) {
414 float X = x->fToXYZD50.vals[r][c],
415 Y = y->fToXYZD50.vals[r][c];
416 SkASSERTF(equiv(X,Y), "Hash collision at toXYZD50[%d][%d], !equiv(%g,%g)\n", r,c, X,Y);
417 }
418 #endif
419 return true;
420 }
421 return false;
422 }
423