• 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 }
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 skcms_TransferFunction* tf = &transferFn;
46 
47     if (is_almost_srgb(transferFn)) {
48         if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
49             return SkColorSpace::MakeSRGB();
50         }
51         tf = &SkNamedTransferFn::kSRGB;
52     } else if (is_almost_2dot2(transferFn)) {
53         tf = &SkNamedTransferFn::k2Dot2;
54     } else if (is_almost_linear(transferFn)) {
55         if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
56             return SkColorSpace::MakeSRGBLinear();
57         }
58         tf = &SkNamedTransferFn::kLinear;
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, 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             if (!skcms_Matrix3x3_invert(&fToXYZD50, &fFromXYZD50)) {
98                 SkAssertResult(skcms_Matrix3x3_invert(&skcms_sRGB_profile()->toXYZD50,
99                                                       &fFromXYZD50));
100             }
101         }
102 
103         // Invert transfer function, defaulting to sRGB if we can't.
104         {
105             if (!skcms_TransferFunction_invert(&fTransferFn, &fInvTransferFn)) {
106                 fInvTransferFn = *skcms_sRGB_Inverse_TransferFunction();
107             }
108         }
109 
110     });
111 }
112 
isNumericalTransferFn(skcms_TransferFunction * coeffs) const113 bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const {
114     // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers
115     // already pass pointers to an skcms struct). Then remove this function, and update the two
116     // remaining callers to do the right thing with transferFn and classify.
117     this->transferFn(coeffs);
118     return classify_transfer_fn(*coeffs) == sRGBish_TF;
119 }
120 
transferFn(float gabcdef[7]) const121 void SkColorSpace::transferFn(float gabcdef[7]) const {
122     memcpy(gabcdef, &fTransferFn, 7*sizeof(float));
123 }
124 
transferFn(skcms_TransferFunction * fn) const125 void SkColorSpace::transferFn(skcms_TransferFunction* fn) const {
126     *fn = fTransferFn;
127 }
128 
invTransferFn(skcms_TransferFunction * fn) const129 void SkColorSpace::invTransferFn(skcms_TransferFunction* fn) const {
130     this->computeLazyDstFields();
131     *fn = fInvTransferFn;
132 }
133 
toXYZD50(skcms_Matrix3x3 * toXYZD50) const134 bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const {
135     *toXYZD50 = fToXYZD50;
136     return true;
137 }
138 
gamutTransformTo(const SkColorSpace * dst,skcms_Matrix3x3 * src_to_dst) const139 void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, skcms_Matrix3x3* src_to_dst) const {
140     dst->computeLazyDstFields();
141     *src_to_dst = skcms_Matrix3x3_concat(&dst->fFromXYZD50, &fToXYZD50);
142 }
143 
isSRGB() const144 bool SkColorSpace::isSRGB() const {
145     return sk_srgb_singleton() == this;
146 }
147 
gammaCloseToSRGB() const148 bool SkColorSpace::gammaCloseToSRGB() const {
149     // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
150     return memcmp(&fTransferFn, &SkNamedTransferFn::kSRGB, 7*sizeof(float)) == 0;
151 }
152 
gammaIsLinear() const153 bool SkColorSpace::gammaIsLinear() const {
154     // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
155     return memcmp(&fTransferFn, &SkNamedTransferFn::kLinear, 7*sizeof(float)) == 0;
156 }
157 
makeLinearGamma() const158 sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() const {
159     if (this->gammaIsLinear()) {
160         return sk_ref_sp(const_cast<SkColorSpace*>(this));
161     }
162     return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, fToXYZD50);
163 }
164 
makeSRGBGamma() const165 sk_sp<SkColorSpace> SkColorSpace::makeSRGBGamma() const {
166     if (this->gammaCloseToSRGB()) {
167         return sk_ref_sp(const_cast<SkColorSpace*>(this));
168     }
169     return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, fToXYZD50);
170 }
171 
makeColorSpin() const172 sk_sp<SkColorSpace> SkColorSpace::makeColorSpin() const {
173     skcms_Matrix3x3 spin = {{
174         { 0, 0, 1 },
175         { 1, 0, 0 },
176         { 0, 1, 0 },
177     }};
178 
179     skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&fToXYZD50, &spin);
180 
181     return sk_sp<SkColorSpace>(new SkColorSpace(fTransferFn, spun));
182 }
183 
toProfile(skcms_ICCProfile * profile) const184 void SkColorSpace::toProfile(skcms_ICCProfile* profile) const {
185     skcms_Init               (profile);
186     skcms_SetTransferFunction(profile, &fTransferFn);
187     skcms_SetXYZD50          (profile, &fToXYZD50);
188 }
189 
Make(const skcms_ICCProfile & profile)190 sk_sp<SkColorSpace> SkColorSpace::Make(const skcms_ICCProfile& profile) {
191     // TODO: move below ≈sRGB test?
192     if (!profile.has_toXYZD50 || !profile.has_trc) {
193         return nullptr;
194     }
195 
196     if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) {
197         return SkColorSpace::MakeSRGB();
198     }
199 
200     // TODO: can we save this work and skip lazily inverting the matrix later?
201     skcms_Matrix3x3 inv;
202     if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) {
203         return nullptr;
204     }
205 
206     // We can't work with tables or mismatched parametric curves,
207     // but if they all look close enough to sRGB, that's fine.
208     // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB?
209     const skcms_Curve* trc = profile.trc;
210     if (trc[0].table_entries != 0 ||
211         trc[1].table_entries != 0 ||
212         trc[2].table_entries != 0 ||
213         0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) ||
214         0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric)))
215     {
216         if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) {
217             return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50);
218         }
219         return nullptr;
220     }
221 
222     return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50);
223 }
224 
225 ///////////////////////////////////////////////////////////////////////////////////////////////////
226 
227 enum Version {
228     k0_Version, // Initial version, header + flags for matrix and profile
229     k1_Version, // Simple header (version tag) + 16 floats
230 
231     kCurrent_Version = k1_Version,
232 };
233 
234 enum NamedColorSpace {
235     kSRGB_NamedColorSpace,
236     kAdobeRGB_NamedColorSpace,
237     kSRGBLinear_NamedColorSpace,
238 };
239 
240 enum NamedGamma {
241     kLinear_NamedGamma,
242     kSRGB_NamedGamma,
243     k2Dot2_NamedGamma,
244 };
245 
246 struct ColorSpaceHeader {
247     // Flag values, only used by old (k0_Version) serialization
248     inline static constexpr uint8_t kMatrix_Flag     = 1 << 0;
249     inline static constexpr uint8_t kICC_Flag        = 1 << 1;
250     inline static constexpr uint8_t kTransferFn_Flag = 1 << 3;
251 
252     uint8_t fVersion = kCurrent_Version;
253 
254     // Other fields are only used by k0_Version. Could be re-purposed in future versions.
255     uint8_t fNamed      = 0;
256     uint8_t fGammaNamed = 0;
257     uint8_t fFlags      = 0;
258 };
259 
writeToMemory(void * memory) const260 size_t SkColorSpace::writeToMemory(void* memory) const {
261     if (memory) {
262         *((ColorSpaceHeader*) memory) = ColorSpaceHeader();
263         memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
264 
265         memcpy(memory, &fTransferFn, 7 * sizeof(float));
266         memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
267 
268         memcpy(memory, &fToXYZD50, 9 * sizeof(float));
269     }
270 
271     return sizeof(ColorSpaceHeader) + 16 * sizeof(float);
272 }
273 
serialize() const274 sk_sp<SkData> SkColorSpace::serialize() const {
275     sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr));
276     this->writeToMemory(data->writable_data());
277     return data;
278 }
279 
Deserialize(const void * data,size_t length)280 sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
281     if (length < sizeof(ColorSpaceHeader)) {
282         return nullptr;
283     }
284 
285     ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
286     data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
287     length -= sizeof(ColorSpaceHeader);
288     if (k1_Version == header.fVersion) {
289         if (length < 16 * sizeof(float)) {
290             return nullptr;
291         }
292 
293         skcms_TransferFunction transferFn;
294         memcpy(&transferFn, data, 7 * sizeof(float));
295         data = SkTAddOffset<const void>(data, 7 * sizeof(float));
296 
297         skcms_Matrix3x3 toXYZ;
298         memcpy(&toXYZ, data, 9 * sizeof(float));
299         return SkColorSpace::MakeRGB(transferFn, toXYZ);
300     } else if (k0_Version == header.fVersion) {
301         if (0 == header.fFlags) {
302             switch ((NamedColorSpace)header.fNamed) {
303                 case kSRGB_NamedColorSpace:
304                     return SkColorSpace::MakeSRGB();
305                 case kSRGBLinear_NamedColorSpace:
306                     return SkColorSpace::MakeSRGBLinear();
307                 case kAdobeRGB_NamedColorSpace:
308                     return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2,
309                                                  SkNamedGamut::kAdobeRGB);
310             }
311         }
312 
313         auto make_named_tf = [=](const skcms_TransferFunction& tf) {
314             if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
315                 return sk_sp<SkColorSpace>(nullptr);
316             }
317 
318             // Version 0 matrix is row-major 3x4
319             skcms_Matrix3x3 toXYZ;
320             memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
321             memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
322             memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
323             return SkColorSpace::MakeRGB(tf, toXYZ);
324         };
325 
326         switch ((NamedGamma) header.fGammaNamed) {
327             case kSRGB_NamedGamma:
328                 return make_named_tf(SkNamedTransferFn::kSRGB);
329             case k2Dot2_NamedGamma:
330                 return make_named_tf(SkNamedTransferFn::k2Dot2);
331             case kLinear_NamedGamma:
332                 return make_named_tf(SkNamedTransferFn::kLinear);
333             default:
334                 break;
335         }
336 
337         switch (header.fFlags) {
338             case ColorSpaceHeader::kICC_Flag: {
339                 // Deprecated and unsupported code path
340                 return nullptr;
341             }
342             case ColorSpaceHeader::kTransferFn_Flag: {
343                 if (length < 19 * sizeof(float)) {
344                     return nullptr;
345                 }
346 
347                 // Version 0 TF is in abcdefg order
348                 skcms_TransferFunction transferFn;
349                 transferFn.a = *(((const float*) data) + 0);
350                 transferFn.b = *(((const float*) data) + 1);
351                 transferFn.c = *(((const float*) data) + 2);
352                 transferFn.d = *(((const float*) data) + 3);
353                 transferFn.e = *(((const float*) data) + 4);
354                 transferFn.f = *(((const float*) data) + 5);
355                 transferFn.g = *(((const float*) data) + 6);
356                 data = SkTAddOffset<const void>(data, 7 * sizeof(float));
357 
358                 // Version 0 matrix is row-major 3x4
359                 skcms_Matrix3x3 toXYZ;
360                 memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
361                 memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
362                 memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
363                 return SkColorSpace::MakeRGB(transferFn, toXYZ);
364             }
365             default:
366                 return nullptr;
367         }
368     } else {
369         return nullptr;
370     }
371 }
372 
Equals(const SkColorSpace * x,const SkColorSpace * y)373 bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) {
374     if (x == y) {
375         return true;
376     }
377 
378     if (!x || !y) {
379         return false;
380     }
381 
382     if (x->hash() == y->hash()) {
383     #if defined(SK_DEBUG)
384         // Do these floats function equivalently?
385         // This returns true more often than simple float comparison   (NaN vs. NaN) and,
386         // also returns true more often than simple bitwise comparison (+0 vs. -0) and,
387         // even returns true more often than those two OR'd together   (two different NaNs).
388         auto equiv = [](float X, float Y) {
389             return (X==Y)
390                 || (sk_float_isnan(X) && sk_float_isnan(Y));
391         };
392 
393         for (int i = 0; i < 7; i++) {
394             float X = (&x->fTransferFn.g)[i],
395                   Y = (&y->fTransferFn.g)[i];
396             SkASSERTF(equiv(X,Y), "Hash collision at tf[%d], !equiv(%g,%g)\n", i, X,Y);
397         }
398         for (int r = 0; r < 3; r++)
399         for (int c = 0; c < 3; c++) {
400             float X = x->fToXYZD50.vals[r][c],
401                   Y = y->fToXYZD50.vals[r][c];
402             SkASSERTF(equiv(X,Y), "Hash collision at toXYZD50[%d][%d], !equiv(%g,%g)\n", r,c, X,Y);
403         }
404     #endif
405         return true;
406     }
407     return false;
408 }
409