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
16SkColorSpaceXformSteps::SkColorSpaceXformSteps(SkColorSpace* src, SkAlphaType srcAT,
17 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 this->srcTF_is_sRGB = src->gammaCloseToSRGB();
69 this->dstTF_is_sRGB = dst->gammaCloseToSRGB();
70
71 // If we linearize then immediately reencode with the same transfer function, skip both.
72 if ( this->flags.linearize &&
73 !this->flags.gamut_transform &&
74 this->flags.encode &&
75 src->transferFnHash() == dst->transferFnHash())
76 {
77 #ifdef SK_DEBUG
78 skcms_TransferFunction dstTF;
79 dst->transferFn(&dstTF);
80 for (int i = 0; i < 7; i++) {
81 SkASSERT( (&srcTF.g)[i] == (&dstTF.g)[i] && "Hash collision" );
82 }
83 #endif
84 this->flags.linearize = false;
85 this->flags.encode = false;
86 }
87
88 // Skip unpremul...premul if there are no non-linear operations between.
89 if ( this->flags.unpremul &&
90 !this->flags.linearize &&
91 !this->flags.encode &&
92 this->flags.premul)
93 {
94 this->flags.unpremul = false;
95 this->flags.premul = false;
96 }
97}
98
99void SkColorSpaceXformSteps::apply(float* rgba) const {
100 if (flags.unpremul) {
101 // I don't know why isfinite(x) stopped working on the Chromecast bots...
102 auto is_finite = [](float x) { return x*0 == 0; };
103
104 float invA = is_finite(1.0f / rgba[3]) ? 1.0f / rgba[3] : 0;
105 rgba[0] *= invA;
106 rgba[1] *= invA;
107 rgba[2] *= invA;
108 }
109 if (flags.linearize) {
110 rgba[0] = skcms_TransferFunction_eval(&srcTF, rgba[0]);
111 rgba[1] = skcms_TransferFunction_eval(&srcTF, rgba[1]);
112 rgba[2] = skcms_TransferFunction_eval(&srcTF, rgba[2]);
113 }
114 if (flags.gamut_transform) {
115 float temp[3] = { rgba[0], rgba[1], rgba[2] };
116 for (int i = 0; i < 3; ++i) {
117 rgba[i] = src_to_dst_matrix[ i] * temp[0] +
118 src_to_dst_matrix[3 + i] * temp[1] +
119 src_to_dst_matrix[6 + i] * temp[2];
120 }
121 }
122 if (flags.encode) {
123 rgba[0] = skcms_TransferFunction_eval(&dstTFInv, rgba[0]);
124 rgba[1] = skcms_TransferFunction_eval(&dstTFInv, rgba[1]);
125 rgba[2] = skcms_TransferFunction_eval(&dstTFInv, rgba[2]);
126 }
127 if (flags.premul) {
128 rgba[0] *= rgba[3];
129 rgba[1] *= rgba[3];
130 rgba[2] *= rgba[3];
131 }
132}
133
134void SkColorSpaceXformSteps::apply(SkRasterPipeline* p, bool src_is_normalized) const {
135#if defined(SK_LEGACY_SRGB_STAGE_CHOICE)
136 src_is_normalized = true;
137#endif
138 if (flags.unpremul) { p->append(SkRasterPipeline::unpremul); }
139 if (flags.linearize) {
140 if (src_is_normalized && srcTF_is_sRGB) {
141 p->append(SkRasterPipeline::from_srgb);
142 } else {
143 p->append_transfer_function(srcTF);
144 }
145 }
146 if (flags.gamut_transform) {
147 p->append(SkRasterPipeline::matrix_3x3, &src_to_dst_matrix);
148 }
149 if (flags.encode) {
150 if (src_is_normalized && dstTF_is_sRGB) {
151 p->append(SkRasterPipeline::to_srgb);
152 } else {
153 p->append_transfer_function(dstTFInv);
154 }
155 }
156 if (flags.premul) { p->append(SkRasterPipeline::premul); }
157}
158
159skvm::Color sk_program_transfer_fn(skvm::Builder* p, skvm::Uniforms* uniforms,
160 const skcms_TransferFunction& tf, skvm::Color c) {
161 skvm::F32 G = p->uniformF(uniforms->pushF(tf.g)),
162 A = p->uniformF(uniforms->pushF(tf.a)),
163 B = p->uniformF(uniforms->pushF(tf.b)),
164 C = p->uniformF(uniforms->pushF(tf.c)),
165 D = p->uniformF(uniforms->pushF(tf.d)),
166 E = p->uniformF(uniforms->pushF(tf.e)),
167 F = p->uniformF(uniforms->pushF(tf.f));
168
169 auto apply = [&](skvm::F32 v) -> skvm::F32 {
170 // Strip off the sign bit and save it for later.
171 skvm::I32 bits = bit_cast(v),
172 sign = bits & 0x80000000;
173 v = bit_cast(bits ^ sign);
174
175 switch (classify_transfer_fn(tf)) {
176 case Bad_TF: SkASSERT(false); break;
177
178 case sRGBish_TF:
179 v = select(v <= D, C*v + F
180 , approx_powf(A*v + B, G) + E);
181 break;
182
183 case PQish_TF: {
184 auto vC = approx_powf(v, C);
185 v = approx_powf(max(B * vC + A, 0.0f) / (E * vC + D), F);
186 } break;
187
188 case HLGish_TF: {
189 auto vA = v * A;
190 v = select(vA <= 1.0f, approx_powf(vA, B)
191 , approx_exp((v-E) * C + D));
192 } break;
193
194 case HLGinvish_TF:
195 v = select(v <= 1.0f, A * approx_powf(v, B)
196 , C * approx_log(v-D) + E);
197 break;
198 }
199
200 // Re-apply the original sign bit on our way out the door.
201 return bit_cast(sign | bit_cast(v));
202 };
203
204 return {apply(c.r), apply(c.g), apply(c.b), c.a};
205}
206
207skvm::Color SkColorSpaceXformSteps::program(skvm::Builder* p, skvm::Uniforms* uniforms,
208 skvm::Color c) const {
209 if (flags.unpremul) {
210 c = unpremul(c);
211 }
212 if (flags.linearize) {
213 c = sk_program_transfer_fn(p, uniforms, srcTF, c);
214 }
215 if (flags.gamut_transform) {
216 auto m = [&](int index) {
217 return p->uniformF(uniforms->pushF(src_to_dst_matrix[index]));
218 };
219 auto R = c.r * m(0) + c.g * m(3) + c.b * m(6),
220 G = c.r * m(1) + c.g * m(4) + c.b * m(7),
221 B = c.r * m(2) + c.g * m(5) + c.b * m(8);
222 c = {R, G, B, c.a};
223 }
224 if (flags.encode) {
225 c = sk_program_transfer_fn(p, uniforms, dstTFInv, c);
226 }
227 if (flags.premul) {
228 c = premul(c);
229 }
230 return c;
231}
232