• 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/base/SkTemplates.h"
11 #include "modules/skcms/skcms.h"
12 #include "src/core/SkChecksum.h"
13 #include "src/core/SkColorSpacePriv.h"
14 
15 #include <cmath>
16 #include <cstring>
17 
toXYZD50(skcms_Matrix3x3 * toXYZ_D50) const18 bool SkColorSpacePrimaries::toXYZD50(skcms_Matrix3x3* toXYZ_D50) const {
19     return skcms_PrimariesToXYZD50(fRX, fRY, fGX, fGY, fBX, fBY, fWX, fWY, toXYZ_D50);
20 }
21 
SkColorSpace(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & toXYZD50)22 SkColorSpace::SkColorSpace(const skcms_TransferFunction& transferFn,
23                            const skcms_Matrix3x3& toXYZD50)
24         : fTransferFn(transferFn)
25         , fToXYZD50(toXYZD50) {
26     fTransferFnHash = SkChecksum::Hash32(&fTransferFn, 7*sizeof(float));
27     fToXYZD50Hash = SkChecksum::Hash32(&fToXYZD50, 9*sizeof(float));
28 }
29 
xyz_almost_equal(const skcms_Matrix3x3 & mA,const skcms_Matrix3x3 & mB)30 static bool xyz_almost_equal(const skcms_Matrix3x3& mA, const skcms_Matrix3x3& mB) {
31     for (int r = 0; r < 3; ++r) {
32         for (int c = 0; c < 3; ++c) {
33             if (!color_space_almost_equal(mA.vals[r][c], mB.vals[r][c])) {
34                 return false;
35             }
36         }
37     }
38 
39     return true;
40 }
41 
MakeRGB(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & toXYZ)42 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn,
43                                           const skcms_Matrix3x3& toXYZ) {
44     if (skcms_TransferFunction_getType(&transferFn) == skcms_TFType_Invalid) {
45         return nullptr;
46     }
47 
48     const skcms_TransferFunction* tf = &transferFn;
49 
50     if (is_almost_srgb(transferFn)) {
51         if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
52             return SkColorSpace::MakeSRGB();
53         }
54         tf = &SkNamedTransferFn::kSRGB;
55     } else if (is_almost_2dot2(transferFn)) {
56         tf = &SkNamedTransferFn::k2Dot2;
57     } else if (is_almost_linear(transferFn)) {
58         if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
59             return SkColorSpace::MakeSRGBLinear();
60         }
61         tf = &SkNamedTransferFn::kLinear;
62     }
63 
64     return sk_sp<SkColorSpace>(new SkColorSpace(*tf, toXYZ));
65 }
66 
67 class SkColorSpaceSingletonFactory {
68 public:
Make(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & to_xyz)69     static SkColorSpace* Make(const skcms_TransferFunction& transferFn,
70                               const skcms_Matrix3x3& to_xyz) {
71         return new SkColorSpace(transferFn, to_xyz);
72     }
73 };
74 
sk_srgb_singleton()75 SkColorSpace* sk_srgb_singleton() {
76     static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kSRGB,
77                                                                  SkNamedGamut::kSRGB);
78     return cs;
79 }
80 
sk_srgb_linear_singleton()81 SkColorSpace* sk_srgb_linear_singleton() {
82     static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kLinear,
83                                                                  SkNamedGamut::kSRGB);
84     return cs;
85 }
86 
MakeSRGB()87 sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
88     return sk_ref_sp(sk_srgb_singleton());
89 }
90 
MakeSRGBLinear()91 sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
92     return sk_ref_sp(sk_srgb_linear_singleton());
93 }
94 
computeLazyDstFields() const95 void SkColorSpace::computeLazyDstFields() const {
96     fLazyDstFieldsOnce([this] {
97 
98         // Invert 3x3 gamut, defaulting to sRGB if we can't.
99         {
100             if (!skcms_Matrix3x3_invert(&fToXYZD50, &fFromXYZD50)) {
101                 SkAssertResult(skcms_Matrix3x3_invert(&skcms_sRGB_profile()->toXYZD50,
102                                                       &fFromXYZD50));
103             }
104         }
105 
106         // Invert transfer function, defaulting to sRGB if we can't.
107         {
108             if (!skcms_TransferFunction_invert(&fTransferFn, &fInvTransferFn)) {
109                 fInvTransferFn = *skcms_sRGB_Inverse_TransferFunction();
110             }
111         }
112 
113     });
114 }
115 
isNumericalTransferFn(skcms_TransferFunction * coeffs) const116 bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const {
117     // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers
118     // already pass pointers to an skcms struct). Then remove this function, and update the two
119     // remaining callers to do the right thing with transferFn and classify.
120     this->transferFn(coeffs);
121     return skcms_TransferFunction_getType(coeffs) == skcms_TFType_sRGBish;
122 }
123 
transferFn(float gabcdef[7]) const124 void SkColorSpace::transferFn(float gabcdef[7]) const {
125     memcpy(gabcdef, &fTransferFn, 7*sizeof(float));
126 }
127 
transferFn(skcms_TransferFunction * fn) const128 void SkColorSpace::transferFn(skcms_TransferFunction* fn) const {
129     *fn = fTransferFn;
130 }
131 
invTransferFn(skcms_TransferFunction * fn) const132 void SkColorSpace::invTransferFn(skcms_TransferFunction* fn) const {
133     this->computeLazyDstFields();
134     *fn = fInvTransferFn;
135 }
136 
toXYZD50(skcms_Matrix3x3 * toXYZD50) const137 bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const {
138     *toXYZD50 = fToXYZD50;
139     return true;
140 }
141 
gamutTransformTo(const SkColorSpace * dst,skcms_Matrix3x3 * src_to_dst) const142 void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, skcms_Matrix3x3* src_to_dst) const {
143     dst->computeLazyDstFields();
144     *src_to_dst = skcms_Matrix3x3_concat(&dst->fFromXYZD50, &fToXYZD50);
145 }
146 
isSRGB() const147 bool SkColorSpace::isSRGB() const {
148     return sk_srgb_singleton() == this;
149 }
150 
gammaCloseToSRGB() const151 bool SkColorSpace::gammaCloseToSRGB() const {
152     // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
153     return memcmp(&fTransferFn, &SkNamedTransferFn::kSRGB, 7*sizeof(float)) == 0;
154 }
155 
gammaIsLinear() const156 bool SkColorSpace::gammaIsLinear() const {
157     // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
158     return memcmp(&fTransferFn, &SkNamedTransferFn::kLinear, 7*sizeof(float)) == 0;
159 }
160 
makeLinearGamma() const161 sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() const {
162     if (this->gammaIsLinear()) {
163         return sk_ref_sp(const_cast<SkColorSpace*>(this));
164     }
165     return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, fToXYZD50);
166 }
167 
makeSRGBGamma() const168 sk_sp<SkColorSpace> SkColorSpace::makeSRGBGamma() const {
169     if (this->gammaCloseToSRGB()) {
170         return sk_ref_sp(const_cast<SkColorSpace*>(this));
171     }
172     return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, fToXYZD50);
173 }
174 
makeColorSpin() const175 sk_sp<SkColorSpace> SkColorSpace::makeColorSpin() const {
176     skcms_Matrix3x3 spin = {{
177         { 0, 0, 1 },
178         { 1, 0, 0 },
179         { 0, 1, 0 },
180     }};
181 
182     skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&fToXYZD50, &spin);
183 
184     return sk_sp<SkColorSpace>(new SkColorSpace(fTransferFn, spun));
185 }
186 
toProfile(skcms_ICCProfile * profile) const187 void SkColorSpace::toProfile(skcms_ICCProfile* profile) const {
188     skcms_Init               (profile);
189     skcms_SetTransferFunction(profile, &fTransferFn);
190     skcms_SetXYZD50          (profile, &fToXYZD50);
191 }
192 
Make(const skcms_ICCProfile & profile)193 sk_sp<SkColorSpace> SkColorSpace::Make(const skcms_ICCProfile& profile) {
194     // TODO: move below ≈sRGB test?
195     if (!profile.has_toXYZD50 || !profile.has_trc) {
196         return nullptr;
197     }
198 
199     if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) {
200         return SkColorSpace::MakeSRGB();
201     }
202 
203     // TODO: can we save this work and skip lazily inverting the matrix later?
204     skcms_Matrix3x3 inv;
205     if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) {
206         return nullptr;
207     }
208 
209     // We can't work with tables or mismatched parametric curves,
210     // but if they all look close enough to sRGB, that's fine.
211     // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB?
212     const skcms_Curve* trc = profile.trc;
213     if (trc[0].table_entries != 0 ||
214         trc[1].table_entries != 0 ||
215         trc[2].table_entries != 0 ||
216         0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) ||
217         0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric)))
218     {
219         if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) {
220             return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50);
221         }
222         return nullptr;
223     }
224 
225     return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50);
226 }
227 
228 ///////////////////////////////////////////////////////////////////////////////////////////////////
229 
230 enum Version {
231     k0_Version, // Initial (deprecated) version, no longer supported
232     k1_Version, // Simple header (version tag) + 16 floats
233 
234     kCurrent_Version = k1_Version,
235 };
236 
237 struct ColorSpaceHeader {
238     uint8_t fVersion = kCurrent_Version;
239 
240     // Other fields were only used by k0_Version. Could be re-purposed in future versions.
241     uint8_t fReserved0 = 0;
242     uint8_t fReserved1 = 0;
243     uint8_t fReserved2 = 0;
244 };
245 
writeToMemory(void * memory) const246 size_t SkColorSpace::writeToMemory(void* memory) const {
247     if (memory) {
248         *((ColorSpaceHeader*) memory) = ColorSpaceHeader();
249         memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
250 
251         memcpy(memory, &fTransferFn, 7 * sizeof(float));
252         memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
253 
254         memcpy(memory, &fToXYZD50, 9 * sizeof(float));
255     }
256 
257     return sizeof(ColorSpaceHeader) + 16 * sizeof(float);
258 }
259 
serialize() const260 sk_sp<SkData> SkColorSpace::serialize() const {
261     sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr));
262     this->writeToMemory(data->writable_data());
263     return data;
264 }
265 
Deserialize(const void * data,size_t length)266 sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
267     if (length < sizeof(ColorSpaceHeader)) {
268         return nullptr;
269     }
270 
271     ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
272     data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
273     length -= sizeof(ColorSpaceHeader);
274     if (header.fVersion != k1_Version) {
275         return nullptr;
276     }
277 
278     if (length < 16 * sizeof(float)) {
279         return nullptr;
280     }
281 
282     skcms_TransferFunction transferFn;
283     memcpy(&transferFn, data, 7 * sizeof(float));
284     data = SkTAddOffset<const void>(data, 7 * sizeof(float));
285 
286     skcms_Matrix3x3 toXYZ;
287     memcpy(&toXYZ, data, 9 * sizeof(float));
288     return SkColorSpace::MakeRGB(transferFn, toXYZ);
289 }
290 
Equals(const SkColorSpace * x,const SkColorSpace * y)291 bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) {
292     if (x == y) {
293         return true;
294     }
295 
296     if (!x || !y) {
297         return false;
298     }
299 
300     if (x->hash() == y->hash()) {
301     #if defined(SK_DEBUG)
302         // Do these floats function equivalently?
303         // This returns true more often than simple float comparison   (NaN vs. NaN) and,
304         // also returns true more often than simple bitwise comparison (+0 vs. -0) and,
305         // even returns true more often than those two OR'd together   (two different NaNs).
306         auto equiv = [](float X, float Y) {
307             return (X==Y)
308                 || (std::isnan(X) && std::isnan(Y));
309         };
310 
311         for (int i = 0; i < 7; i++) {
312             float X = (&x->fTransferFn.g)[i],
313                   Y = (&y->fTransferFn.g)[i];
314             SkASSERTF(equiv(X,Y), "Hash collision at tf[%d], !equiv(%g,%g)\n", i, X,Y);
315         }
316         for (int r = 0; r < 3; r++)
317         for (int c = 0; c < 3; c++) {
318             float X = x->fToXYZD50.vals[r][c],
319                   Y = y->fToXYZD50.vals[r][c];
320             SkASSERTF(equiv(X,Y), "Hash collision at toXYZD50[%d][%d], !equiv(%g,%g)\n", r,c, X,Y);
321         }
322     #endif
323         return true;
324     }
325     return false;
326 }
327