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/base/SkTemplates.h"
11 #include "modules/skcms/skcms.h"
12 #include "src/core/SkChecksum.h"
13 #include "src/core/SkColorSpacePriv.h"
14
15 #include <cmath>
16 #include <cstring>
17
18 namespace SkNamedPrimaries {
19
GetCicp(CicpId primaries,SkColorSpacePrimaries & sk_primaries)20 bool GetCicp(CicpId primaries, SkColorSpacePrimaries& sk_primaries) {
21 // Rec. ITU-T H.273, Table 2.
22 switch (primaries) {
23 case CicpId::kRec709:
24 sk_primaries = kRec709;
25 return true;
26 case CicpId::kRec470SystemM:
27 sk_primaries = kRec470SystemM;
28 return true;
29 case CicpId::kRec470SystemBG:
30 sk_primaries = kRec470SystemBG;
31 return true;
32 case CicpId::kRec601:
33 sk_primaries = kRec601;
34 return true;
35 case CicpId::kSMPTE_ST_240:
36 sk_primaries = kSMPTE_ST_240;
37 return true;
38 case CicpId::kGenericFilm:
39 sk_primaries = kGenericFilm;
40 return true;
41 case CicpId::kRec2020:
42 sk_primaries = kRec2020;
43 return true;
44 case CicpId::kSMPTE_ST_428_1:
45 sk_primaries = kSMPTE_ST_428_1;
46 return true;
47 case CicpId::kSMPTE_RP_431_2:
48 sk_primaries = kSMPTE_RP_431_2;
49 return true;
50 case CicpId::kSMPTE_EG_432_1:
51 sk_primaries = kSMPTE_EG_432_1;
52 return true;
53 case CicpId::kITU_T_H273_Value22:
54 sk_primaries = kITU_T_H273_Value22;
55 return true;
56 default:
57 // Reserved or unimplemented.
58 break;
59 }
60 return false;
61 }
62
63 } // namespace SkNamedPrimaries
64
65 namespace SkNamedTransferFn {
66
GetCicp(SkNamedTransferFn::CicpId transfer_characteristics,skcms_TransferFunction & trfn)67 bool GetCicp(SkNamedTransferFn::CicpId transfer_characteristics, skcms_TransferFunction& trfn) {
68 // Rec. ITU-T H.273, Table 3.
69 switch (transfer_characteristics) {
70 case SkNamedTransferFn::CicpId::kRec709:
71 trfn = kRec709;
72 return true;
73 case SkNamedTransferFn::CicpId::kRec470SystemM:
74 trfn = kRec470SystemM;
75 return true;
76 case SkNamedTransferFn::CicpId::kRec470SystemBG:
77 trfn = kRec470SystemBG;
78 return true;
79 case SkNamedTransferFn::CicpId::kRec601:
80 trfn = kRec601;
81 return true;
82 case SkNamedTransferFn::CicpId::kSMPTE_ST_240:
83 trfn = kSMPTE_ST_240;
84 return true;
85 case SkNamedTransferFn::CicpId::kLinear:
86 trfn = SkNamedTransferFn::kLinear;
87 return true;
88 case SkNamedTransferFn::CicpId::kIEC61966_2_4:
89 trfn = kIEC61966_2_4;
90 break;
91 case SkNamedTransferFn::CicpId::kIEC61966_2_1:
92 trfn = SkNamedTransferFn::kIEC61966_2_1;
93 return true;
94 case SkNamedTransferFn::CicpId::kRec2020_10bit:
95 trfn = kRec2020_10bit;
96 return true;
97 case SkNamedTransferFn::CicpId::kRec2020_12bit:
98 trfn = kRec2020_12bit;
99 return true;
100 case SkNamedTransferFn::CicpId::kPQ:
101 trfn = SkNamedTransferFn::kPQ;
102 return true;
103 case SkNamedTransferFn::CicpId::kSMPTE_ST_428_1:
104 trfn = kSMPTE_ST_428_1;
105 return true;
106 case SkNamedTransferFn::CicpId::kHLG:
107 trfn = SkNamedTransferFn::kHLG;
108 return true;
109 default:
110 // Reserved or unimplemented.
111 break;
112 }
113 return false;
114 }
115
116 } // namespace SkNamedTransferFn
117
toXYZD50(skcms_Matrix3x3 * toXYZ_D50) const118 bool SkColorSpacePrimaries::toXYZD50(skcms_Matrix3x3* toXYZ_D50) const {
119 return skcms_PrimariesToXYZD50(fRX, fRY, fGX, fGY, fBX, fBY, fWX, fWY, toXYZ_D50);
120 }
121
SkColorSpace(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & toXYZD50)122 SkColorSpace::SkColorSpace(const skcms_TransferFunction& transferFn,
123 const skcms_Matrix3x3& toXYZD50)
124 : fTransferFn(transferFn)
125 , fToXYZD50(toXYZD50) {
126 fTransferFnHash = SkChecksum::Hash32(&fTransferFn, 7*sizeof(float));
127 fToXYZD50Hash = SkChecksum::Hash32(&fToXYZD50, 9*sizeof(float));
128 }
129
xyz_almost_equal(const skcms_Matrix3x3 & mA,const skcms_Matrix3x3 & mB)130 static bool xyz_almost_equal(const skcms_Matrix3x3& mA, const skcms_Matrix3x3& mB) {
131 for (int r = 0; r < 3; ++r) {
132 for (int c = 0; c < 3; ++c) {
133 if (!color_space_almost_equal(mA.vals[r][c], mB.vals[r][c])) {
134 return false;
135 }
136 }
137 }
138
139 return true;
140 }
141
MakeRGB(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & toXYZ)142 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn,
143 const skcms_Matrix3x3& toXYZ) {
144 if (skcms_TransferFunction_getType(&transferFn) == skcms_TFType_Invalid) {
145 return nullptr;
146 }
147
148 const skcms_TransferFunction* tf = &transferFn;
149
150 if (is_almost_srgb(transferFn)) {
151 if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
152 return SkColorSpace::MakeSRGB();
153 }
154 tf = &SkNamedTransferFn::kSRGB;
155 } else if (is_almost_2dot2(transferFn)) {
156 tf = &SkNamedTransferFn::k2Dot2;
157 } else if (is_almost_linear(transferFn)) {
158 if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
159 return SkColorSpace::MakeSRGBLinear();
160 }
161 tf = &SkNamedTransferFn::kLinear;
162 }
163
164 return sk_sp<SkColorSpace>(new SkColorSpace(*tf, toXYZ));
165 }
166
MakeCICP(SkNamedPrimaries::CicpId color_primaries,SkNamedTransferFn::CicpId transfer_characteristics)167 sk_sp<SkColorSpace> SkColorSpace::MakeCICP(SkNamedPrimaries::CicpId color_primaries,
168 SkNamedTransferFn::CicpId transfer_characteristics) {
169 skcms_TransferFunction trfn;
170 if (!SkNamedTransferFn::GetCicp(transfer_characteristics, trfn)) {
171 return nullptr;
172 }
173
174 SkColorSpacePrimaries primaries;
175 if (!SkNamedPrimaries::GetCicp(color_primaries, primaries)) {
176 return nullptr;
177 }
178
179 skcms_Matrix3x3 primaries_matrix;
180 if (!primaries.toXYZD50(&primaries_matrix)) {
181 return nullptr;
182 }
183
184 return SkColorSpace::MakeRGB(trfn, primaries_matrix);
185 }
186
187 class SkColorSpaceSingletonFactory {
188 public:
Make(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & to_xyz)189 static SkColorSpace* Make(const skcms_TransferFunction& transferFn,
190 const skcms_Matrix3x3& to_xyz) {
191 return new SkColorSpace(transferFn, to_xyz);
192 }
193 };
194
sk_srgb_singleton()195 SkColorSpace* sk_srgb_singleton() {
196 static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kSRGB,
197 SkNamedGamut::kSRGB);
198 return cs;
199 }
200
sk_srgb_linear_singleton()201 SkColorSpace* sk_srgb_linear_singleton() {
202 static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kLinear,
203 SkNamedGamut::kSRGB);
204 return cs;
205 }
206
MakeSRGB()207 sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
208 return sk_ref_sp(sk_srgb_singleton());
209 }
210
MakeSRGBLinear()211 sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
212 return sk_ref_sp(sk_srgb_linear_singleton());
213 }
214
computeLazyDstFields() const215 void SkColorSpace::computeLazyDstFields() const {
216 fLazyDstFieldsOnce([this] {
217
218 // Invert 3x3 gamut, defaulting to sRGB if we can't.
219 {
220 if (!skcms_Matrix3x3_invert(&fToXYZD50, &fFromXYZD50)) {
221 SkAssertResult(skcms_Matrix3x3_invert(&skcms_sRGB_profile()->toXYZD50,
222 &fFromXYZD50));
223 }
224 }
225
226 // Invert transfer function, defaulting to sRGB if we can't.
227 {
228 if (!skcms_TransferFunction_invert(&fTransferFn, &fInvTransferFn)) {
229 fInvTransferFn = *skcms_sRGB_Inverse_TransferFunction();
230 }
231 }
232
233 });
234 }
235
isNumericalTransferFn(skcms_TransferFunction * coeffs) const236 bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const {
237 // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers
238 // already pass pointers to an skcms struct). Then remove this function, and update the two
239 // remaining callers to do the right thing with transferFn and classify.
240 this->transferFn(coeffs);
241 return skcms_TransferFunction_getType(coeffs) == skcms_TFType_sRGBish;
242 }
243
transferFn(float gabcdef[7]) const244 void SkColorSpace::transferFn(float gabcdef[7]) const {
245 memcpy(gabcdef, &fTransferFn, 7*sizeof(float));
246 }
247
transferFn(skcms_TransferFunction * fn) const248 void SkColorSpace::transferFn(skcms_TransferFunction* fn) const {
249 *fn = fTransferFn;
250 }
251
invTransferFn(skcms_TransferFunction * fn) const252 void SkColorSpace::invTransferFn(skcms_TransferFunction* fn) const {
253 this->computeLazyDstFields();
254 *fn = fInvTransferFn;
255 }
256
toXYZD50(skcms_Matrix3x3 * toXYZD50) const257 bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const {
258 *toXYZD50 = fToXYZD50;
259 return true;
260 }
261
gamutTransformTo(const SkColorSpace * dst,skcms_Matrix3x3 * src_to_dst) const262 void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, skcms_Matrix3x3* src_to_dst) const {
263 dst->computeLazyDstFields();
264 *src_to_dst = skcms_Matrix3x3_concat(&dst->fFromXYZD50, &fToXYZD50);
265 }
266
isSRGB() const267 bool SkColorSpace::isSRGB() const {
268 return sk_srgb_singleton() == this;
269 }
270
gammaCloseToSRGB() const271 bool SkColorSpace::gammaCloseToSRGB() const {
272 // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
273 return memcmp(&fTransferFn, &SkNamedTransferFn::kSRGB, 7*sizeof(float)) == 0;
274 }
275
gammaIsLinear() const276 bool SkColorSpace::gammaIsLinear() const {
277 // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
278 return memcmp(&fTransferFn, &SkNamedTransferFn::kLinear, 7*sizeof(float)) == 0;
279 }
280
makeLinearGamma() const281 sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() const {
282 if (this->gammaIsLinear()) {
283 return sk_ref_sp(const_cast<SkColorSpace*>(this));
284 }
285 return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, fToXYZD50);
286 }
287
makeSRGBGamma() const288 sk_sp<SkColorSpace> SkColorSpace::makeSRGBGamma() const {
289 if (this->gammaCloseToSRGB()) {
290 return sk_ref_sp(const_cast<SkColorSpace*>(this));
291 }
292 return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, fToXYZD50);
293 }
294
makeColorSpin() const295 sk_sp<SkColorSpace> SkColorSpace::makeColorSpin() const {
296 skcms_Matrix3x3 spin = {{
297 { 0, 0, 1 },
298 { 1, 0, 0 },
299 { 0, 1, 0 },
300 }};
301
302 skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&fToXYZD50, &spin);
303
304 return sk_sp<SkColorSpace>(new SkColorSpace(fTransferFn, spun));
305 }
306
toProfile(skcms_ICCProfile * profile) const307 void SkColorSpace::toProfile(skcms_ICCProfile* profile) const {
308 skcms_Init (profile);
309 skcms_SetTransferFunction(profile, &fTransferFn);
310 skcms_SetXYZD50 (profile, &fToXYZD50);
311 }
312
Make(const skcms_ICCProfile & profile)313 sk_sp<SkColorSpace> SkColorSpace::Make(const skcms_ICCProfile& profile) {
314 // TODO: move below ≈sRGB test?
315 if (!profile.has_toXYZD50 || !profile.has_trc) {
316 return nullptr;
317 }
318
319 if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) {
320 return SkColorSpace::MakeSRGB();
321 }
322
323 // TODO: can we save this work and skip lazily inverting the matrix later?
324 skcms_Matrix3x3 inv;
325 if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) {
326 return nullptr;
327 }
328
329 // We can't work with tables or mismatched parametric curves,
330 // but if they all look close enough to sRGB, that's fine.
331 // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB?
332 const skcms_Curve* trc = profile.trc;
333 if (trc[0].table_entries != 0 ||
334 trc[1].table_entries != 0 ||
335 trc[2].table_entries != 0 ||
336 0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) ||
337 0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric)))
338 {
339 if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) {
340 return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50);
341 }
342 return nullptr;
343 }
344
345 return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50);
346 }
347
348 ///////////////////////////////////////////////////////////////////////////////////////////////////
349
350 enum Version {
351 k0_Version, // Initial (deprecated) version, no longer supported
352 k1_Version, // Simple header (version tag) + 16 floats
353
354 kCurrent_Version = k1_Version,
355 };
356
357 struct ColorSpaceHeader {
358 uint8_t fVersion = kCurrent_Version;
359
360 // Other fields were only used by k0_Version. Could be re-purposed in future versions.
361 uint8_t fReserved0 = 0;
362 uint8_t fReserved1 = 0;
363 uint8_t fReserved2 = 0;
364 };
365
writeToMemory(void * memory) const366 size_t SkColorSpace::writeToMemory(void* memory) const {
367 if (memory) {
368 *((ColorSpaceHeader*) memory) = ColorSpaceHeader();
369 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
370
371 memcpy(memory, &fTransferFn, 7 * sizeof(float));
372 memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
373
374 memcpy(memory, &fToXYZD50, 9 * sizeof(float));
375 }
376
377 return sizeof(ColorSpaceHeader) + 16 * sizeof(float);
378 }
379
serialize() const380 sk_sp<SkData> SkColorSpace::serialize() const {
381 sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr));
382 this->writeToMemory(data->writable_data());
383 return data;
384 }
385
Deserialize(const void * data,size_t length)386 sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
387 if (length < sizeof(ColorSpaceHeader)) {
388 return nullptr;
389 }
390
391 ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
392 data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
393 length -= sizeof(ColorSpaceHeader);
394 if (header.fVersion != k1_Version) {
395 return nullptr;
396 }
397
398 if (length < 16 * sizeof(float)) {
399 return nullptr;
400 }
401
402 skcms_TransferFunction transferFn;
403 memcpy(&transferFn, data, 7 * sizeof(float));
404 data = SkTAddOffset<const void>(data, 7 * sizeof(float));
405
406 skcms_Matrix3x3 toXYZ;
407 memcpy(&toXYZ, data, 9 * sizeof(float));
408 return SkColorSpace::MakeRGB(transferFn, toXYZ);
409 }
410
Equals(const SkColorSpace * x,const SkColorSpace * y)411 bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) {
412 if (x == y) {
413 return true;
414 }
415
416 if (!x || !y) {
417 return false;
418 }
419
420 if (x->hash() == y->hash()) {
421 #if defined(SK_DEBUG)
422 // Do these floats function equivalently?
423 // This returns true more often than simple float comparison (NaN vs. NaN) and,
424 // also returns true more often than simple bitwise comparison (+0 vs. -0) and,
425 // even returns true more often than those two OR'd together (two different NaNs).
426 auto equiv = [](float X, float Y) {
427 return (X==Y)
428 || (std::isnan(X) && std::isnan(Y));
429 };
430
431 for (int i = 0; i < 7; i++) {
432 float X = (&x->fTransferFn.g)[i],
433 Y = (&y->fTransferFn.g)[i];
434 SkASSERTF(equiv(X,Y), "Hash collision at tf[%d], !equiv(%g,%g)\n", i, X,Y);
435 }
436 for (int r = 0; r < 3; r++)
437 for (int c = 0; c < 3; c++) {
438 float X = x->fToXYZD50.vals[r][c],
439 Y = y->fToXYZD50.vals[r][c];
440 SkASSERTF(equiv(X,Y), "Hash collision at toXYZD50[%d][%d], !equiv(%g,%g)\n", r,c, X,Y);
441 }
442 #endif
443 return true;
444 }
445 return false;
446 }
447