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