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 (classify_transfer_fn(transferFn) == Bad_TF) {
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     // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers
120     // already pass pointers to an skcms struct). Then remove this function, and update the two
121     // remaining callers to do the right thing with transferFn and classify.
122     this->transferFn(&coeffs->g);
123     return classify_transfer_fn(*coeffs) == sRGBish_TF;
124 }
125 
transferFn(float gabcdef[7]) const126 void SkColorSpace::transferFn(float gabcdef[7]) const {
127     memcpy(gabcdef, &fTransferFn, 7*sizeof(float));
128 }
129 
invTransferFn(float gabcdef[7]) const130 void SkColorSpace::invTransferFn(float gabcdef[7]) const {
131     this->computeLazyDstFields();
132     memcpy(gabcdef, &fInvTransferFn, 7*sizeof(float));
133 }
134 
toXYZD50(SkMatrix44 * toXYZD50) const135 bool SkColorSpace::toXYZD50(SkMatrix44* toXYZD50) const {
136     toXYZD50->set3x3RowMajorf(fToXYZD50_3x3);
137     return true;
138 }
139 
toXYZD50(skcms_Matrix3x3 * toXYZD50) const140 bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const {
141     memcpy(toXYZD50, fToXYZD50_3x3, 9*sizeof(float));
142     return true;
143 }
144 
gamutTransformTo(const SkColorSpace * dst,float src_to_dst[9]) const145 void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, float src_to_dst[9]) const {
146     dst->computeLazyDstFields();
147 
148     skcms_Matrix3x3 toXYZD50,
149                   fromXYZD50;
150 
151     memcpy(&  toXYZD50, this->  fToXYZD50_3x3, 9*sizeof(float));
152     memcpy(&fromXYZD50, dst ->fFromXYZD50_3x3, 9*sizeof(float));
153 
154     skcms_Matrix3x3 srcToDst = skcms_Matrix3x3_concat(&fromXYZD50, &toXYZD50);
155     memcpy(src_to_dst, &srcToDst, 9*sizeof(float));
156 }
157 
isSRGB() const158 bool SkColorSpace::isSRGB() const {
159     return sk_srgb_singleton() == this;
160 }
161 
gammaCloseToSRGB() const162 bool SkColorSpace::gammaCloseToSRGB() const {
163     // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
164     return memcmp(fTransferFn, &SkNamedTransferFn::kSRGB.g, 7*sizeof(float)) == 0;
165 }
166 
gammaIsLinear() const167 bool SkColorSpace::gammaIsLinear() const {
168     // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
169     return memcmp(fTransferFn, &SkNamedTransferFn::kLinear.g, 7*sizeof(float)) == 0;
170 }
171 
makeLinearGamma() const172 sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() const {
173     if (this->gammaIsLinear()) {
174         return sk_ref_sp(const_cast<SkColorSpace*>(this));
175     }
176     skcms_Matrix3x3 gamut;
177     this->toXYZD50(&gamut);
178     return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut);
179 }
180 
makeSRGBGamma() const181 sk_sp<SkColorSpace> SkColorSpace::makeSRGBGamma() const {
182     if (this->gammaCloseToSRGB()) {
183         return sk_ref_sp(const_cast<SkColorSpace*>(this));
184     }
185     skcms_Matrix3x3 gamut;
186     this->toXYZD50(&gamut);
187     return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
188 }
189 
makeColorSpin() const190 sk_sp<SkColorSpace> SkColorSpace::makeColorSpin() const {
191     skcms_Matrix3x3 spin = {{
192         { 0, 0, 1 },
193         { 1, 0, 0 },
194         { 0, 1, 0 },
195     }};
196 
197     skcms_Matrix3x3 toXYZ;
198     this->toXYZD50(&toXYZ);
199 
200     skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&toXYZ, &spin);
201 
202     return sk_sp<SkColorSpace>(new SkColorSpace(fTransferFn, spun));
203 }
204 
toProfile(skcms_ICCProfile * profile) const205 void SkColorSpace::toProfile(skcms_ICCProfile* profile) const {
206     skcms_TransferFunction tf;
207     skcms_Matrix3x3        toXYZD50;
208 
209     memcpy(&tf,       fTransferFn,   7*sizeof(float));
210     memcpy(&toXYZD50, fToXYZD50_3x3, 9*sizeof(float));
211 
212     skcms_Init               (profile);
213     skcms_SetTransferFunction(profile, &tf);
214     skcms_SetXYZD50          (profile, &toXYZD50);
215 }
216 
Make(const skcms_ICCProfile & profile)217 sk_sp<SkColorSpace> SkColorSpace::Make(const skcms_ICCProfile& profile) {
218     // TODO: move below ≈sRGB test?
219     if (!profile.has_toXYZD50 || !profile.has_trc) {
220         return nullptr;
221     }
222 
223     if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) {
224         return SkColorSpace::MakeSRGB();
225     }
226 
227     // TODO: can we save this work and skip lazily inverting the matrix later?
228     skcms_Matrix3x3 inv;
229     if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) {
230         return nullptr;
231     }
232 
233     // We can't work with tables or mismatched parametric curves,
234     // but if they all look close enough to sRGB, that's fine.
235     // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB?
236     const skcms_Curve* trc = profile.trc;
237     if (trc[0].table_entries != 0 ||
238         trc[1].table_entries != 0 ||
239         trc[2].table_entries != 0 ||
240         0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) ||
241         0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric)))
242     {
243         if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) {
244             return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50);
245         }
246         return nullptr;
247     }
248 
249     return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50);
250 }
251 
252 ///////////////////////////////////////////////////////////////////////////////////////////////////
253 
254 enum Version {
255     k0_Version, // Initial version, header + flags for matrix and profile
256     k1_Version, // Simple header (version tag) + 16 floats
257 
258     kCurrent_Version = k1_Version,
259 };
260 
261 enum NamedColorSpace {
262     kSRGB_NamedColorSpace,
263     kAdobeRGB_NamedColorSpace,
264     kSRGBLinear_NamedColorSpace,
265 };
266 
267 enum NamedGamma {
268     kLinear_NamedGamma,
269     kSRGB_NamedGamma,
270     k2Dot2_NamedGamma,
271 };
272 
273 struct ColorSpaceHeader {
274     // Flag values, only used by old (k0_Version) serialization
275     static constexpr uint8_t kMatrix_Flag     = 1 << 0;
276     static constexpr uint8_t kICC_Flag        = 1 << 1;
277     static constexpr uint8_t kTransferFn_Flag = 1 << 3;
278 
279     uint8_t fVersion = kCurrent_Version;
280 
281     // Other fields are only used by k0_Version. Could be re-purposed in future versions.
282     uint8_t fNamed      = 0;
283     uint8_t fGammaNamed = 0;
284     uint8_t fFlags      = 0;
285 };
286 
writeToMemory(void * memory) const287 size_t SkColorSpace::writeToMemory(void* memory) const {
288     if (memory) {
289         *((ColorSpaceHeader*) memory) = ColorSpaceHeader();
290         memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
291 
292         memcpy(memory, fTransferFn, 7 * sizeof(float));
293         memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
294 
295         memcpy(memory, fToXYZD50_3x3, 9 * sizeof(float));
296     }
297 
298     return sizeof(ColorSpaceHeader) + 16 * sizeof(float);
299 }
300 
serialize() const301 sk_sp<SkData> SkColorSpace::serialize() const {
302     sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr));
303     this->writeToMemory(data->writable_data());
304     return data;
305 }
306 
Deserialize(const void * data,size_t length)307 sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
308     if (length < sizeof(ColorSpaceHeader)) {
309         return nullptr;
310     }
311 
312     ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
313     data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
314     length -= sizeof(ColorSpaceHeader);
315     if (k1_Version == header.fVersion) {
316         if (length < 16 * sizeof(float)) {
317             return nullptr;
318         }
319 
320         skcms_TransferFunction transferFn;
321         memcpy(&transferFn, data, 7 * sizeof(float));
322         data = SkTAddOffset<const void>(data, 7 * sizeof(float));
323 
324         skcms_Matrix3x3 toXYZ;
325         memcpy(&toXYZ, data, 9 * sizeof(float));
326         return SkColorSpace::MakeRGB(transferFn, toXYZ);
327     } else if (k0_Version == header.fVersion) {
328         if (0 == header.fFlags) {
329             switch ((NamedColorSpace)header.fNamed) {
330                 case kSRGB_NamedColorSpace:
331                     return SkColorSpace::MakeSRGB();
332                 case kSRGBLinear_NamedColorSpace:
333                     return SkColorSpace::MakeSRGBLinear();
334                 case kAdobeRGB_NamedColorSpace:
335                     return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2,
336                                                  SkNamedGamut::kAdobeRGB);
337             }
338         }
339 
340         auto make_named_tf = [=](const skcms_TransferFunction& tf) {
341             if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
342                 return sk_sp<SkColorSpace>(nullptr);
343             }
344 
345             // Version 0 matrix is row-major 3x4
346             skcms_Matrix3x3 toXYZ;
347             memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
348             memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
349             memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
350             return SkColorSpace::MakeRGB(tf, toXYZ);
351         };
352 
353         switch ((NamedGamma) header.fGammaNamed) {
354             case kSRGB_NamedGamma:
355                 return make_named_tf(SkNamedTransferFn::kSRGB);
356             case k2Dot2_NamedGamma:
357                 return make_named_tf(SkNamedTransferFn::k2Dot2);
358             case kLinear_NamedGamma:
359                 return make_named_tf(SkNamedTransferFn::kLinear);
360             default:
361                 break;
362         }
363 
364         switch (header.fFlags) {
365             case ColorSpaceHeader::kICC_Flag: {
366                 // Deprecated and unsupported code path
367                 return nullptr;
368             }
369             case ColorSpaceHeader::kTransferFn_Flag: {
370                 if (length < 19 * sizeof(float)) {
371                     return nullptr;
372                 }
373 
374                 // Version 0 TF is in abcdefg order
375                 skcms_TransferFunction transferFn;
376                 transferFn.a = *(((const float*) data) + 0);
377                 transferFn.b = *(((const float*) data) + 1);
378                 transferFn.c = *(((const float*) data) + 2);
379                 transferFn.d = *(((const float*) data) + 3);
380                 transferFn.e = *(((const float*) data) + 4);
381                 transferFn.f = *(((const float*) data) + 5);
382                 transferFn.g = *(((const float*) data) + 6);
383                 data = SkTAddOffset<const void>(data, 7 * sizeof(float));
384 
385                 // Version 0 matrix is row-major 3x4
386                 skcms_Matrix3x3 toXYZ;
387                 memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
388                 memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
389                 memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
390                 return SkColorSpace::MakeRGB(transferFn, toXYZ);
391             }
392             default:
393                 return nullptr;
394         }
395     } else {
396         return nullptr;
397     }
398 }
399 
Equals(const SkColorSpace * x,const SkColorSpace * y)400 bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) {
401     if (x == y) {
402         return true;
403     }
404 
405     if (!x || !y) {
406         return false;
407     }
408 
409     if (x->hash() == y->hash()) {
410         for (int i = 0; i < 7; i++) {
411             SkASSERT(x->  fTransferFn[i] == y->  fTransferFn[i] && "Hash collsion");
412         }
413         for (int i = 0; i < 9; i++) {
414             SkASSERT(x->fToXYZD50_3x3[i] == y->fToXYZD50_3x3[i] && "Hash collsion");
415         }
416         return true;
417     }
418     return false;
419 }
420