1 /*
2 * Copyright 2018 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/third_party/skcms/skcms.h"
9 #include "src/core/SkColorSpacePriv.h"
10 #include "src/core/SkColorSpaceXformSteps.h"
11 #include "src/core/SkRasterPipeline.h"
12 #include "src/core/SkVM.h"
13
14 // See skia.org/user/color (== site/user/color.md).
15
SkColorSpaceXformSteps(const SkColorSpace * src,SkAlphaType srcAT,const SkColorSpace * dst,SkAlphaType dstAT)16 SkColorSpaceXformSteps::SkColorSpaceXformSteps(const SkColorSpace* src, SkAlphaType srcAT,
17 const SkColorSpace* dst, SkAlphaType dstAT) {
18 // Opaque outputs are treated as the same alpha type as the source input.
19 // TODO: we'd really like to have a good way of explaining why we think this is useful.
20 if (dstAT == kOpaque_SkAlphaType) {
21 dstAT = srcAT;
22 }
23
24 // We have some options about what to do with null src or dst here.
25 // This pair seems to be the most consistent with legacy expectations.
26 if (!src) { src = sk_srgb_singleton(); }
27 if (!dst) { dst = src; }
28
29 if (src->hash() == dst->hash() && srcAT == dstAT) {
30 SkASSERT(SkColorSpace::Equals(src,dst));
31 return;
32 }
33
34 this->flags.unpremul = srcAT == kPremul_SkAlphaType;
35 this->flags.linearize = !src->gammaIsLinear();
36 this->flags.gamut_transform = src->toXYZD50Hash() != dst->toXYZD50Hash();
37 this->flags.encode = !dst->gammaIsLinear();
38 this->flags.premul = srcAT != kOpaque_SkAlphaType && dstAT == kPremul_SkAlphaType;
39
40 if (this->flags.gamut_transform) {
41 skcms_Matrix3x3 src_to_dst; // TODO: switch src_to_dst_matrix to row-major
42 src->gamutTransformTo(dst, &src_to_dst);
43
44 this->src_to_dst_matrix[0] = src_to_dst.vals[0][0];
45 this->src_to_dst_matrix[1] = src_to_dst.vals[1][0];
46 this->src_to_dst_matrix[2] = src_to_dst.vals[2][0];
47
48 this->src_to_dst_matrix[3] = src_to_dst.vals[0][1];
49 this->src_to_dst_matrix[4] = src_to_dst.vals[1][1];
50 this->src_to_dst_matrix[5] = src_to_dst.vals[2][1];
51
52 this->src_to_dst_matrix[6] = src_to_dst.vals[0][2];
53 this->src_to_dst_matrix[7] = src_to_dst.vals[1][2];
54 this->src_to_dst_matrix[8] = src_to_dst.vals[2][2];
55 } else {
56 #ifdef SK_DEBUG
57 skcms_Matrix3x3 srcM, dstM;
58 src->toXYZD50(&srcM);
59 dst->toXYZD50(&dstM);
60 SkASSERT(0 == memcmp(&srcM, &dstM, 9*sizeof(float)) && "Hash collision");
61 #endif
62 }
63
64 // Fill out all the transfer functions we'll use.
65 src-> transferFn(&this->srcTF );
66 dst->invTransferFn(&this->dstTFInv);
67
68 // If we linearize then immediately reencode with the same transfer function, skip both.
69 if ( this->flags.linearize &&
70 !this->flags.gamut_transform &&
71 this->flags.encode &&
72 src->transferFnHash() == dst->transferFnHash())
73 {
74 #ifdef SK_DEBUG
75 skcms_TransferFunction dstTF;
76 dst->transferFn(&dstTF);
77 for (int i = 0; i < 7; i++) {
78 SkASSERT( (&srcTF.g)[i] == (&dstTF.g)[i] && "Hash collision" );
79 }
80 #endif
81 this->flags.linearize = false;
82 this->flags.encode = false;
83 }
84
85 // Skip unpremul...premul if there are no non-linear operations between.
86 if ( this->flags.unpremul &&
87 !this->flags.linearize &&
88 !this->flags.encode &&
89 this->flags.premul)
90 {
91 this->flags.unpremul = false;
92 this->flags.premul = false;
93 }
94 }
95
apply(float * rgba) const96 void SkColorSpaceXformSteps::apply(float* rgba) const {
97 if (flags.unpremul) {
98 // I don't know why isfinite(x) stopped working on the Chromecast bots...
99 auto is_finite = [](float x) { return x*0 == 0; };
100
101 float invA = is_finite(1.0f / rgba[3]) ? 1.0f / rgba[3] : 0;
102 rgba[0] *= invA;
103 rgba[1] *= invA;
104 rgba[2] *= invA;
105 }
106 if (flags.linearize) {
107 rgba[0] = skcms_TransferFunction_eval(&srcTF, rgba[0]);
108 rgba[1] = skcms_TransferFunction_eval(&srcTF, rgba[1]);
109 rgba[2] = skcms_TransferFunction_eval(&srcTF, rgba[2]);
110 }
111 if (flags.gamut_transform) {
112 float temp[3] = { rgba[0], rgba[1], rgba[2] };
113 for (int i = 0; i < 3; ++i) {
114 rgba[i] = src_to_dst_matrix[ i] * temp[0] +
115 src_to_dst_matrix[3 + i] * temp[1] +
116 src_to_dst_matrix[6 + i] * temp[2];
117 }
118 }
119 if (flags.encode) {
120 rgba[0] = skcms_TransferFunction_eval(&dstTFInv, rgba[0]);
121 rgba[1] = skcms_TransferFunction_eval(&dstTFInv, rgba[1]);
122 rgba[2] = skcms_TransferFunction_eval(&dstTFInv, rgba[2]);
123 }
124 if (flags.premul) {
125 rgba[0] *= rgba[3];
126 rgba[1] *= rgba[3];
127 rgba[2] *= rgba[3];
128 }
129 }
130
apply(SkRasterPipeline * p) const131 void SkColorSpaceXformSteps::apply(SkRasterPipeline* p) const {
132 if (flags.unpremul) { p->append(SkRasterPipeline::unpremul); }
133 if (flags.linearize) { p->append_transfer_function(srcTF); }
134 if (flags.gamut_transform) { p->append(SkRasterPipeline::matrix_3x3, &src_to_dst_matrix); }
135 if (flags.encode) { p->append_transfer_function(dstTFInv); }
136 if (flags.premul) { p->append(SkRasterPipeline::premul); }
137 }
138
sk_program_transfer_fn(skvm::Builder * p,skvm::Uniforms * uniforms,const skcms_TransferFunction & tf,skvm::Color c)139 skvm::Color sk_program_transfer_fn(skvm::Builder* p, skvm::Uniforms* uniforms,
140 const skcms_TransferFunction& tf, skvm::Color c) {
141 skvm::F32 G = p->uniformF(uniforms->pushF(tf.g)),
142 A = p->uniformF(uniforms->pushF(tf.a)),
143 B = p->uniformF(uniforms->pushF(tf.b)),
144 C = p->uniformF(uniforms->pushF(tf.c)),
145 D = p->uniformF(uniforms->pushF(tf.d)),
146 E = p->uniformF(uniforms->pushF(tf.e)),
147 F = p->uniformF(uniforms->pushF(tf.f));
148
149 auto apply = [&](skvm::F32 v) -> skvm::F32 {
150 // Strip off the sign bit and save it for later.
151 skvm::I32 bits = pun_to_I32(v),
152 sign = bits & 0x80000000;
153 v = pun_to_F32(bits ^ sign);
154
155 switch (classify_transfer_fn(tf)) {
156 case Bad_TF: SkASSERT(false); break;
157
158 case sRGBish_TF:
159 v = select(v <= D, C*v + F
160 , approx_powf(A*v + B, G) + E);
161 break;
162
163 case PQish_TF: {
164 skvm::F32 vC = approx_powf(v, C);
165 v = approx_powf(max(B * vC + A, 0.0f) / (E * vC + D), F);
166 } break;
167
168 case HLGish_TF: {
169 skvm::F32 vA = v*A,
170 K = F + 1.0f;
171 v = K*select(vA <= 1.0f, approx_powf(vA, B)
172 , approx_exp((v-E) * C + D));
173 } break;
174
175 case HLGinvish_TF:
176 skvm::F32 K = F + 1.0f;
177 v /= K;
178 v = select(v <= 1.0f, A * approx_powf(v, B)
179 , C * approx_log(v-D) + E);
180 break;
181 }
182
183 // Re-apply the original sign bit on our way out the door.
184 return pun_to_F32(sign | pun_to_I32(v));
185 };
186
187 return {apply(c.r), apply(c.g), apply(c.b), c.a};
188 }
189
program(skvm::Builder * p,skvm::Uniforms * uniforms,skvm::Color c) const190 skvm::Color SkColorSpaceXformSteps::program(skvm::Builder* p, skvm::Uniforms* uniforms,
191 skvm::Color c) const {
192 if (flags.unpremul) {
193 c = unpremul(c);
194 }
195 if (flags.linearize) {
196 c = sk_program_transfer_fn(p, uniforms, srcTF, c);
197 }
198 if (flags.gamut_transform) {
199 auto m = [&](int index) {
200 return p->uniformF(uniforms->pushF(src_to_dst_matrix[index]));
201 };
202 auto R = c.r * m(0) + c.g * m(3) + c.b * m(6),
203 G = c.r * m(1) + c.g * m(4) + c.b * m(7),
204 B = c.r * m(2) + c.g * m(5) + c.b * m(8);
205 c = {R, G, B, c.a};
206 }
207 if (flags.encode) {
208 c = sk_program_transfer_fn(p, uniforms, dstTFInv, c);
209 }
210 if (flags.premul) {
211 c = premul(c);
212 }
213 return c;
214 }
215