OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright 2016 Google Inc. | 2 * Copyright 2016 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "SkColorPriv.h" | 8 #include "SkColorPriv.h" |
9 #include "SkColorSpace_Base.h" | 9 #include "SkColorSpace_Base.h" |
10 #include "SkColorSpaceXform.h" | 10 #include "SkColorSpaceXform.h" |
11 | 11 |
12 bool compute_gamut_xform(SkMatrix44* srcToDst, const SkMatrix44& srcToXYZ, | 12 bool compute_gamut_xform(SkMatrix44* srcToDst, const SkMatrix44& srcToXYZ, |
13 const SkMatrix44& dstToXYZ) { | 13 const SkMatrix44& dstToXYZ) { |
14 if (!dstToXYZ.invert(srcToDst)) { | 14 if (!dstToXYZ.invert(srcToDst)) { |
15 return false; | 15 return false; |
16 } | 16 } |
17 | 17 |
18 srcToDst->postConcat(srcToXYZ); | 18 srcToDst->postConcat(srcToXYZ); |
19 return true; | 19 return true; |
20 } | 20 } |
21 | 21 |
22 std::unique_ptr<SkColorSpaceXform> SkColorSpaceXform::New(const sk_sp<SkColorSpa ce>& srcSpace, | 22 std::unique_ptr<SkColorSpaceXform> SkColorSpaceXform::New(const sk_sp<SkColorSpa ce>& srcSpace, |
23 const sk_sp<SkColorSpa ce>& dstSpace) { | 23 const sk_sp<SkColorSpa ce>& dstSpace) { |
24 if (!srcSpace || !dstSpace) { | 24 if (!srcSpace || !dstSpace || as_CSB(srcSpace)->colorLUT() || as_CSB(dstSpac e)->colorLUT()) { |
scroggo
2016/06/06 14:06:44
Just making sure I understand - colorLUT is not ye
msarett
2016/06/06 17:33:44
Yes, this is a mix of unimplemented and invalid.
| |
25 return nullptr; | |
26 } | |
27 | |
28 SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); | |
29 if (!compute_gamut_xform(&srcToDst, srcSpace->xyz(), dstSpace->xyz())) { | |
25 return nullptr; | 30 return nullptr; |
26 } | 31 } |
27 | 32 |
28 if (as_CSB(srcSpace)->gammas()->isValues() && as_CSB(dstSpace)->gammas()->is Values()) { | 33 if (as_CSB(srcSpace)->gammas()->isValues() && as_CSB(dstSpace)->gammas()->is Values()) { |
29 SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); | |
30 if (!compute_gamut_xform(&srcToDst, srcSpace->xyz(), dstSpace->xyz())) { | |
31 return nullptr; | |
32 } | |
33 | |
34 float srcGammas[3]; | 34 float srcGammas[3]; |
35 float dstGammas[3]; | 35 float dstGammas[3]; |
36 srcGammas[0] = as_CSB(srcSpace)->gammas()->fRed.fValue; | 36 srcGammas[0] = as_CSB(srcSpace)->gammas()->fRed.fValue; |
37 srcGammas[1] = as_CSB(srcSpace)->gammas()->fGreen.fValue; | 37 srcGammas[1] = as_CSB(srcSpace)->gammas()->fGreen.fValue; |
38 srcGammas[2] = as_CSB(srcSpace)->gammas()->fBlue.fValue; | 38 srcGammas[2] = as_CSB(srcSpace)->gammas()->fBlue.fValue; |
39 dstGammas[0] = 1.0f / as_CSB(dstSpace)->gammas()->fRed.fValue; | 39 dstGammas[0] = 1.0f / as_CSB(dstSpace)->gammas()->fRed.fValue; |
40 dstGammas[1] = 1.0f / as_CSB(dstSpace)->gammas()->fGreen.fValue; | 40 dstGammas[1] = 1.0f / as_CSB(dstSpace)->gammas()->fGreen.fValue; |
41 dstGammas[2] = 1.0f / as_CSB(dstSpace)->gammas()->fBlue.fValue; | 41 dstGammas[2] = 1.0f / as_CSB(dstSpace)->gammas()->fBlue.fValue; |
42 | 42 |
43 return std::unique_ptr<SkColorSpaceXform>( | 43 return std::unique_ptr<SkColorSpaceXform>( |
44 new SkGammaByValueXform(srcGammas, srcToDst, dstGammas)); | 44 new SkGammaByValueXform(srcGammas, srcToDst, dstGammas)); |
45 } | 45 } |
46 | 46 |
47 // Unimplemeted | 47 return std::unique_ptr<SkColorSpaceXform>( |
48 return nullptr; | 48 new SkDefaultXform(as_CSB(srcSpace)->gammas(), srcToDst, as_CSB(dstS pace)->gammas())); |
49 } | 49 } |
50 | 50 |
51 //////////////////////////////////////////////////////////////////////////////// /////////////////// | 51 //////////////////////////////////////////////////////////////////////////////// /////////////////// |
52 | 52 |
53 SkGammaByValueXform::SkGammaByValueXform(float srcGammas[3], const SkMatrix44& s rcToDst, | 53 static float byte_to_float(uint8_t v) { |
scroggo
2016/06/06 14:06:44
Should this be inline?
msarett
2016/06/06 17:33:44
Yeah I think so. "inline" is just a suggestion to
| |
54 float dstGammas[3]) | 54 return ((float) v) * (1.0f / 255.0f); |
55 : fSrcToDst(srcToDst) | |
56 { | |
57 memcpy(fSrcGammas, srcGammas, 3 * sizeof(float)); | |
58 memcpy(fDstGammas, dstGammas, 3 * sizeof(float)); | |
59 } | 55 } |
60 | 56 |
61 static uint8_t clamp_float_to_byte(float v) { | 57 static uint8_t clamp_float_to_byte(float v) { |
62 v = v * 255.0f; | 58 v = v * 255.0f; |
63 if (v > 255.0f) { | 59 if (v > 255.0f) { |
64 return 255; | 60 return 255; |
65 } else if (v <= 0.0f) { | 61 } else if (v <= 0.0f) { |
66 return 0; | 62 return 0; |
67 } else { | 63 } else { |
68 return (uint8_t) (v + 0.5f); | 64 return (uint8_t) (v + 0.5f); |
69 } | 65 } |
70 } | 66 } |
71 | 67 |
68 //////////////////////////////////////////////////////////////////////////////// /////////////////// | |
69 | |
70 SkGammaByValueXform::SkGammaByValueXform(float srcGammas[3], const SkMatrix44& s rcToDst, | |
71 float dstGammas[3]) | |
72 : fSrcToDst(srcToDst) | |
73 { | |
74 memcpy(fSrcGammas, srcGammas, 3 * sizeof(float)); | |
75 memcpy(fDstGammas, dstGammas, 3 * sizeof(float)); | |
76 } | |
77 | |
72 void SkGammaByValueXform::xform_RGBA_8888(uint32_t* dst, const uint32_t* src, ui nt32_t len) const { | 78 void SkGammaByValueXform::xform_RGBA_8888(uint32_t* dst, const uint32_t* src, ui nt32_t len) const { |
73 while (len-- > 0) { | 79 while (len-- > 0) { |
74 float srcFloats[3]; | 80 float srcFloats[3]; |
75 srcFloats[0] = ((*src >> 0) & 0xFF) * (1.0f / 255.0f); | 81 srcFloats[0] = byte_to_float((*src >> 0) & 0xFF); |
76 srcFloats[1] = ((*src >> 8) & 0xFF) * (1.0f / 255.0f); | 82 srcFloats[1] = byte_to_float((*src >> 8) & 0xFF); |
77 srcFloats[2] = ((*src >> 16) & 0xFF) * (1.0f / 255.0f); | 83 srcFloats[2] = byte_to_float((*src >> 16) & 0xFF); |
78 | 84 |
79 // Convert to linear. | 85 // Convert to linear. |
80 srcFloats[0] = pow(srcFloats[0], fSrcGammas[0]); | 86 srcFloats[0] = pow(srcFloats[0], fSrcGammas[0]); |
81 srcFloats[1] = pow(srcFloats[1], fSrcGammas[1]); | 87 srcFloats[1] = pow(srcFloats[1], fSrcGammas[1]); |
82 srcFloats[2] = pow(srcFloats[2], fSrcGammas[2]); | 88 srcFloats[2] = pow(srcFloats[2], fSrcGammas[2]); |
83 | 89 |
84 // Convert to dst gamut. | 90 // Convert to dst gamut. |
85 float dstFloats[3]; | 91 float dstFloats[3]; |
86 dstFloats[0] = srcFloats[0] * fSrcToDst.getFloat(0, 0) + | 92 dstFloats[0] = srcFloats[0] * fSrcToDst.getFloat(0, 0) + |
87 srcFloats[1] * fSrcToDst.getFloat(1, 0) + | 93 srcFloats[1] * fSrcToDst.getFloat(1, 0) + |
(...skipping 12 matching lines...) Expand all Loading... | |
100 | 106 |
101 *dst = SkPackARGB32NoCheck(((*src >> 24) & 0xFF), | 107 *dst = SkPackARGB32NoCheck(((*src >> 24) & 0xFF), |
102 clamp_float_to_byte(dstFloats[0]), | 108 clamp_float_to_byte(dstFloats[0]), |
103 clamp_float_to_byte(dstFloats[1]), | 109 clamp_float_to_byte(dstFloats[1]), |
104 clamp_float_to_byte(dstFloats[2])); | 110 clamp_float_to_byte(dstFloats[2])); |
105 | 111 |
106 dst++; | 112 dst++; |
107 src++; | 113 src++; |
108 } | 114 } |
109 } | 115 } |
116 | |
117 //////////////////////////////////////////////////////////////////////////////// /////////////////// | |
118 | |
119 // Interpolating lookup in a variably sized table. | |
120 float interp_lut(uint8_t byte, float* table, size_t tableSize) { | |
121 float index = byte * (1.0f / 255.0f) * (tableSize - 1); | |
scroggo
2016/06/06 14:06:44
nit:
byte * (1.0f / 255.0f)
Won't "byte" get
msarett
2016/06/06 17:33:43
Yes it is, thanks!
| |
122 float diff = index - floor(index); | |
123 return table[(int) floor(index)] * (1.0f - diff) + table[(int) ceil(index)] * diff; | |
scroggo
2016/06/06 14:06:44
Note: We have macros for sk_float_floor2int and sk
msarett
2016/06/06 17:33:43
Agreed, done.
| |
124 } | |
125 | |
126 // Inverse table lookup. Ex: what index corresponds to the input value? This w ill | |
127 // have strange results when the table is non-increasing. But any sane gamma | |
scroggo
2016/06/06 14:06:44
Have you seen any insane gamma functions? In that
msarett
2016/06/06 17:33:44
I have not seen any insane gammas. If we did enco
scroggo
2016/06/06 17:40:50
FIXME sounds good to me.
| |
128 // function will be increasing. | |
129 float interp_lut_inv(float input, float* table, size_t tableSize) { | |
130 if (input <= table[0]) { | |
131 return table[0]; | |
132 } else if (input >= table[tableSize - 1]) { | |
133 return 1.0f; | |
134 } | |
135 | |
136 for (uint32_t i = 1; i < tableSize; i++) { | |
scroggo
2016/06/06 14:06:44
Would it be worth it to use something faster than
msarett
2016/06/06 17:33:44
If we verify that this function is increasing befo
scroggo
2016/06/06 17:40:50
sgtm
| |
137 if (table[i] >= input) { | |
138 // We are guaranteed that input is greater than table[i - 1]. | |
139 float diff = input - table[i - 1]; | |
140 float distance = table[i] - table[i - 1]; | |
141 float index = (i - 1) + diff / distance; | |
142 return index / (tableSize - 1); | |
143 } | |
144 } | |
145 | |
146 // Should be unreachable, since we'll return before the loop if input is | |
147 // larger than the last entry. | |
148 SkASSERT(false); | |
149 return 0.0f; | |
150 } | |
151 | |
152 SkDefaultXform::SkDefaultXform(const sk_sp<SkGammas>& srcGammas, const SkMatrix4 4& srcToDst, | |
153 const sk_sp<SkGammas>& dstGammas) | |
154 : fSrcGammas(srcGammas) | |
155 , fSrcToDst(srcToDst) | |
156 , fDstGammas(dstGammas) | |
157 {} | |
158 | |
159 void SkDefaultXform::xform_RGBA_8888(uint32_t* dst, const uint32_t* src, uint32_ t len) const { | |
160 while (len-- > 0) { | |
161 // Convert to linear. | |
162 // FIXME (msarett): | |
163 // Rather than support three different strategies of transforming gamma, QCMS | |
164 // builds a 256 entry float lookup table from the gamma info. This hand les | |
165 // the gamma transform and the conversion from bytes to floats. This ma y | |
166 // be simpler and faster than our current approach. | |
167 float srcFloats[3]; | |
168 for (int i = 0; i < 3; i++) { | |
169 const SkGammaCurve& gamma = (*fSrcGammas)[i]; | |
170 uint8_t byte = (*src >> (8 * i)) & 0xFF; | |
171 if (gamma.isValue()) { | |
172 srcFloats[i] = pow(byte_to_float(byte), gamma.fValue); | |
173 } else if (gamma.isTable()) { | |
174 srcFloats[i] = interp_lut(byte, gamma.fTable.get(), gamma.fTable Size); | |
175 } else { | |
176 SkASSERT(gamma.isParametric()); | |
177 float component = byte_to_float(byte); | |
178 if (component < gamma.fD) { | |
179 // Y = E * X + F | |
180 srcFloats[i] = gamma.fE * component + gamma.fF; | |
181 } else { | |
182 // Y = (A * X + B)^G + C | |
183 srcFloats[i] = pow(gamma.fA * component + gamma.fB, gamma.fG ) + gamma.fC; | |
184 } | |
185 } | |
186 } | |
187 | |
188 // Convert to dst gamut. | |
189 float dstFloats[3]; | |
190 dstFloats[0] = srcFloats[0] * fSrcToDst.getFloat(0, 0) + | |
191 srcFloats[1] * fSrcToDst.getFloat(1, 0) + | |
192 srcFloats[2] * fSrcToDst.getFloat(2, 0) + fSrcToDst.getFl oat(3, 0); | |
193 dstFloats[1] = srcFloats[0] * fSrcToDst.getFloat(0, 1) + | |
194 srcFloats[1] * fSrcToDst.getFloat(1, 1) + | |
195 srcFloats[2] * fSrcToDst.getFloat(2, 1) + fSrcToDst.getFl oat(3, 1); | |
196 dstFloats[2] = srcFloats[0] * fSrcToDst.getFloat(0, 2) + | |
197 srcFloats[1] * fSrcToDst.getFloat(1, 2) + | |
198 srcFloats[2] * fSrcToDst.getFloat(2, 2) + fSrcToDst.getFl oat(3, 2); | |
199 | |
200 // Convert to dst gamma. | |
201 // FIXME (msarett): | |
202 // Rather than support three different strategies of transforming invers e gamma, | |
203 // QCMS builds a large float lookup table from the gamma info. Is this faster or | |
204 // better than our approach? | |
205 for (int i = 0; i < 3; i++) { | |
206 const SkGammaCurve& gamma = (*fDstGammas)[i]; | |
207 if (gamma.isValue()) { | |
208 dstFloats[i] = pow(dstFloats[i], 1.0f / gamma.fValue); | |
209 } else if (gamma.isTable()) { | |
210 // FIXME (msarett): | |
211 // An inverse table lookup is particularly strange and non-optim al. | |
212 dstFloats[i] = interp_lut_inv(dstFloats[i], gamma.fTable.get(), gamma.fTableSize); | |
213 } else { | |
214 SkASSERT(gamma.isParametric()); | |
215 // We need to take the inverse of a piecewise function. Assume that | |
216 // the gamma function is continuous, or this won't make much sen se | |
217 // anyway. | |
218 // Plug in |fD| to the first equation to calculate the new piece wise | |
219 // interval. Then simply use the inverse of the original functi ons. | |
220 float interval = gamma.fE * gamma.fD + gamma.fF; | |
221 | |
222 // FIXME (msarett): | |
223 // Is this math safe? Are we at risk of dividing by zero? | |
scroggo
2016/06/06 14:06:44
The risk is dividing by E or G. Those were read fr
msarett
2016/06/06 17:33:44
You're right. I added some checks and removed the
| |
224 if (dstFloats[i] < interval) { | |
225 // X = (Y - F) / E | |
226 dstFloats[i] = (dstFloats[i] - gamma.fF) / gamma.fE; | |
227 } else { | |
228 // X = (Y - C)^(1 / G) - B | |
229 dstFloats[i] = pow(dstFloats[i] - gamma.fC, 1.0f / gamma.fG) - gamma.fB; | |
230 } | |
231 } | |
232 } | |
233 | |
234 *dst = SkPackARGB32NoCheck(((*src >> 24) & 0xFF), | |
scroggo
2016/06/06 14:06:44
Why is it safe to use the NoCheck version? Is this
msarett
2016/06/06 17:33:44
This function is (currently) opaque->opaque or unp
| |
235 clamp_float_to_byte(dstFloats[0]), | |
236 clamp_float_to_byte(dstFloats[1]), | |
237 clamp_float_to_byte(dstFloats[2])); | |
238 | |
239 dst++; | |
240 src++; | |
241 } | |
242 } | |
OLD | NEW |