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