Chromium Code Reviews| 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 |