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