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