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 "SkColorSpaceXform_A2B.h"
9
10 #include "SkColorPriv.h"
11 #include "SkColorSpace_A2B.h"
12 #include "SkColorSpace_XYZ.h"
13 #include "SkColorSpacePriv.h"
14 #include "SkColorSpaceXformPriv.h"
15 #include "SkMakeUnique.h"
16 #include "SkNx.h"
17 #include "SkSRGB.h"
18 #include "SkTypes.h"
19 #include "../jumper/SkJumper.h"
20
onApply(ColorFormat dstFormat,void * dst,ColorFormat srcFormat,const void * src,int count,SkAlphaType alphaType) const21 bool SkColorSpaceXform_A2B::onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat,
22 const void* src, int count, SkAlphaType alphaType) const {
23 SkRasterPipeline_<256> pipeline;
24 switch (srcFormat) {
25 case kBGRA_8888_ColorFormat:
26 pipeline.append(SkRasterPipeline::load_bgra, &src);
27 break;
28 case kRGBA_8888_ColorFormat:
29 pipeline.append(SkRasterPipeline::load_8888, &src);
30 break;
31 case kRGBA_U16_BE_ColorFormat:
32 pipeline.append(SkRasterPipeline::load_u16_be, &src);
33 break;
34 case kRGB_U16_BE_ColorFormat:
35 pipeline.append(SkRasterPipeline::load_rgb_u16_be, &src);
36 break;
37 default:
38 SkCSXformPrintf("F16/F32 sources must be linear.\n");
39 return false;
40 }
41
42 pipeline.extend(fElementsPipeline);
43
44 if (kPremul_SkAlphaType == alphaType) {
45 pipeline.append(SkRasterPipeline::premul);
46 }
47
48 switch (dstFormat) {
49 case kBGRA_8888_ColorFormat:
50 pipeline.append(SkRasterPipeline::store_bgra, &dst);
51 break;
52 case kRGBA_8888_ColorFormat:
53 pipeline.append(SkRasterPipeline::store_8888, &dst);
54 break;
55 case kRGBA_F16_ColorFormat:
56 if (!fLinearDstGamma) {
57 return false;
58 }
59 pipeline.append(SkRasterPipeline::store_f16, &dst);
60 break;
61 case kRGBA_F32_ColorFormat:
62 if (!fLinearDstGamma) {
63 return false;
64 }
65 pipeline.append(SkRasterPipeline::store_f32, &dst);
66 break;
67 case kBGR_565_ColorFormat:
68 if (kOpaque_SkAlphaType != alphaType) {
69 return false;
70 }
71 pipeline.append(SkRasterPipeline::store_565, &dst);
72 break;
73 default:
74 return false;
75 }
76 pipeline.run(0,0, count);
77
78 return true;
79 }
80
gamma_to_parametric(SkColorSpaceTransferFn * coeffs,const SkGammas & gammas,int channel)81 static inline bool gamma_to_parametric(SkColorSpaceTransferFn* coeffs, const SkGammas& gammas,
82 int channel) {
83 switch (gammas.type(channel)) {
84 case SkGammas::Type::kNamed_Type:
85 return named_to_parametric(coeffs, gammas.data(channel).fNamed);
86 case SkGammas::Type::kValue_Type:
87 value_to_parametric(coeffs, gammas.data(channel).fValue);
88 return true;
89 case SkGammas::Type::kParam_Type:
90 *coeffs = gammas.params(channel);
91 return true;
92 default:
93 return false;
94 }
95 }
96
SkColorSpaceXform_A2B(SkColorSpace_A2B * srcSpace,SkColorSpace_XYZ * dstSpace)97 SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
98 SkColorSpace_XYZ* dstSpace)
99 : fElementsPipeline(&fAlloc)
100 , fLinearDstGamma(kLinear_SkGammaNamed == dstSpace->gammaNamed()) {
101 #if (SkCSXformPrintfDefined)
102 static const char* debugGammaNamed[4] = {
103 "Linear", "SRGB", "2.2", "NonStandard"
104 };
105 static const char* debugGammas[5] = {
106 "None", "Named", "Value", "Table", "Param"
107 };
108 #endif
109 int currentChannels;
110 switch (srcSpace->iccType()) {
111 case SkColorSpace_Base::kRGB_ICCTypeFlag:
112 currentChannels = 3;
113 break;
114 case SkColorSpace_Base::kCMYK_ICCTypeFlag: {
115 currentChannels = 4;
116 // CMYK images from JPEGs (the only format that supports it) are actually
117 // inverted CMYK, so we need to invert every channel.
118 // TransferFn is y = -x + 1 for x < 1.f, otherwise 0x + 0, ie y = 1 - x for x in [0,1]
119 SkColorSpaceTransferFn fn = {0,0,0,0,0,0,0};
120 fn.fG = 1;
121 fn.fA = 0;
122 fn.fB = 0;
123 fn.fC = -1;
124 fn.fD = 1;
125 fn.fE = 0;
126 fn.fF = 1;
127 this->addTransferFns(fn,4);
128 break;
129 }
130 default:
131 currentChannels = 0;
132 SkASSERT(false);
133 }
134 // add in all input color space -> PCS xforms
135 for (int i = 0; i < srcSpace->count(); ++i) {
136 const SkColorSpace_A2B::Element& e = srcSpace->element(i);
137 SkASSERT(e.inputChannels() == currentChannels);
138 currentChannels = e.outputChannels();
139 switch (e.type()) {
140 case SkColorSpace_A2B::Element::Type::kGammaNamed:
141 if (kLinear_SkGammaNamed == e.gammaNamed()) {
142 break;
143 }
144
145 // Take the fast path for ordinary sRGB.
146 if (3 == currentChannels && kSRGB_SkGammaNamed == e.gammaNamed()) {
147 SkCSXformPrintf("fast path from sRGB\n");
148 // Images should always start the pipeline as unpremul
149 fElementsPipeline.append_from_srgb(kUnpremul_SkAlphaType);
150 break;
151 }
152
153 SkCSXformPrintf("Gamma stage added: %s\n", debugGammaNamed[(int)e.gammaNamed()]);
154 SkColorSpaceTransferFn fn;
155 SkAssertResult(named_to_parametric(&fn, e.gammaNamed()));
156 this->addTransferFns(fn, currentChannels);
157 break;
158 case SkColorSpace_A2B::Element::Type::kGammas: {
159 const SkGammas& gammas = e.gammas();
160 SkCSXformPrintf("Gamma stage added:");
161 for (int channel = 0; channel < gammas.channels(); ++channel) {
162 SkCSXformPrintf(" %s", debugGammas[(int)gammas.type(channel)]);
163 }
164 SkCSXformPrintf("\n");
165 bool gammaNeedsRef = false;
166 for (int channel = 0; channel < gammas.channels(); ++channel) {
167 if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
168 SkTableTransferFn table = {
169 gammas.table(channel),
170 gammas.data(channel).fTable.fSize,
171 };
172
173 gammaNeedsRef |= !this->buildTableFn(&table);
174 this->addTableFn(table, channel);
175 } else {
176 SkColorSpaceTransferFn fn;
177 SkAssertResult(gamma_to_parametric(&fn, gammas, channel));
178 this->addTransferFn(fn, channel);
179 }
180 }
181 if (gammaNeedsRef) {
182 this->copy(sk_ref_sp(&gammas));
183 }
184 break;
185 }
186 case SkColorSpace_A2B::Element::Type::kCLUT: {
187 SkCSXformPrintf("CLUT (%d -> %d) stage added\n", e.colorLUT().inputChannels(),
188 e.colorLUT().outputChannels());
189 struct CallbackCtx : SkJumper_CallbackCtx {
190 sk_sp<const SkColorLookUpTable> clut;
191 // clut->interp() can't always safely alias its arguments,
192 // so we allocate a second buffer to hold our results.
193 float results[4*SkJumper_kMaxStride];
194 };
195 auto cb = fAlloc.make<CallbackCtx>();
196 cb->clut = sk_ref_sp(&e.colorLUT());
197 cb->read_from = cb->results;
198 cb->fn = [](SkJumper_CallbackCtx* ctx, int active_pixels) {
199 auto c = (CallbackCtx*)ctx;
200 for (int i = 0; i < active_pixels; i++) {
201 // Look up red, green, and blue for this pixel using 3-4 values from rgba.
202 c->clut->interp(c->results+4*i, c->rgba+4*i);
203
204 // If we used 3 inputs (rgb) preserve the fourth as alpha.
205 // If we used 4 inputs (cmyk) force alpha to 1.
206 c->results[4*i+3] = (3 == c->clut->inputChannels()) ? c->rgba[4*i+3] : 1.0f;
207 }
208 };
209 fElementsPipeline.append(SkRasterPipeline::callback, cb);
210 break;
211 }
212 case SkColorSpace_A2B::Element::Type::kMatrix:
213 if (!e.matrix().isIdentity()) {
214 SkCSXformPrintf("Matrix stage added\n");
215 addMatrix(e.matrix());
216 }
217 break;
218 }
219 }
220
221 // Lab PCS -> XYZ PCS
222 if (SkColorSpace_A2B::PCS::kLAB == srcSpace->pcs()) {
223 SkCSXformPrintf("Lab -> XYZ element added\n");
224 fElementsPipeline.append(SkRasterPipeline::lab_to_xyz);
225 }
226
227 // we should now be in XYZ PCS
228 SkASSERT(3 == currentChannels);
229
230 // and XYZ PCS -> output color space xforms
231 if (!dstSpace->fromXYZD50()->isIdentity()) {
232 addMatrix(*dstSpace->fromXYZD50());
233 }
234
235 switch (dstSpace->gammaNamed()) {
236 case kLinear_SkGammaNamed:
237 // do nothing
238 break;
239 case k2Dot2Curve_SkGammaNamed: {
240 SkColorSpaceTransferFn fn = {0,0,0,0,0,0,0};
241 fn.fG = 1/2.2f;
242 fn.fA = 1;
243 auto to_2dot2 = this->copy(fn);
244 fElementsPipeline.append(SkRasterPipeline::parametric_r, to_2dot2);
245 fElementsPipeline.append(SkRasterPipeline::parametric_g, to_2dot2);
246 fElementsPipeline.append(SkRasterPipeline::parametric_b, to_2dot2);
247 break;
248 }
249 case kSRGB_SkGammaNamed:
250 fElementsPipeline.append(SkRasterPipeline::to_srgb);
251 break;
252 case kNonStandard_SkGammaNamed: {
253 for (int channel = 0; channel < 3; ++channel) {
254 const SkGammas& gammas = *dstSpace->gammas();
255 if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
256 static constexpr int kInvTableSize = 256;
257 auto storage = fAlloc.makeArray<float>(kInvTableSize);
258 invert_table_gamma(storage, nullptr, kInvTableSize,
259 gammas.table(channel),
260 gammas.data(channel).fTable.fSize);
261 SkTableTransferFn table = { storage, kInvTableSize };
262 this->addTableFn(table, channel);
263 } else {
264 SkColorSpaceTransferFn fn;
265 SkAssertResult(gamma_to_parametric(&fn, gammas, channel));
266 this->addTransferFn(fn.invert(), channel);
267 }
268 }
269 }
270 break;
271 }
272 }
273
addTransferFns(const SkColorSpaceTransferFn & fn,int channelCount)274 void SkColorSpaceXform_A2B::addTransferFns(const SkColorSpaceTransferFn& fn, int channelCount) {
275 for (int i = 0; i < channelCount; ++i) {
276 this->addTransferFn(fn, i);
277 }
278 }
279
addTransferFn(const SkColorSpaceTransferFn & fn,int channelIndex)280 void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex) {
281 switch (channelIndex) {
282 case 0:
283 fElementsPipeline.append(SkRasterPipeline::parametric_r, this->copy(fn));
284 break;
285 case 1:
286 fElementsPipeline.append(SkRasterPipeline::parametric_g, this->copy(fn));
287 break;
288 case 2:
289 fElementsPipeline.append(SkRasterPipeline::parametric_b, this->copy(fn));
290 break;
291 case 3:
292 fElementsPipeline.append(SkRasterPipeline::parametric_a, this->copy(fn));
293 break;
294 default:
295 SkASSERT(false);
296 }
297 }
298
299 /**
300 * |fn| is an in-out parameter. If the table is too small to perform reasonable table-lookups
301 * without interpolation, we will build a bigger table.
302 *
303 * This returns false if we use the original table, meaning we do nothing here but need to keep
304 * a reference to the original table. This returns true if we build a new table and the original
305 * table can be discarded.
306 */
buildTableFn(SkTableTransferFn * fn)307 bool SkColorSpaceXform_A2B::buildTableFn(SkTableTransferFn* fn) {
308 // Arbitrary, but seems like a reasonable guess.
309 static constexpr int kMinTableSize = 256;
310
311 if (fn->fSize >= kMinTableSize) {
312 return false;
313 }
314
315 float* outTable = fAlloc.makeArray<float>(kMinTableSize);
316 float step = 1.0f / (kMinTableSize - 1);
317 for (int i = 0; i < kMinTableSize; i++) {
318 outTable[i] = interp_lut(i * step, fn->fData, fn->fSize);
319 }
320
321 fn->fData = outTable;
322 fn->fSize = kMinTableSize;
323 return true;
324 }
325
addTableFn(const SkTableTransferFn & fn,int channelIndex)326 void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, int channelIndex) {
327 switch (channelIndex) {
328 case 0:
329 fElementsPipeline.append(SkRasterPipeline::table_r, this->copy(fn));
330 break;
331 case 1:
332 fElementsPipeline.append(SkRasterPipeline::table_g, this->copy(fn));
333 break;
334 case 2:
335 fElementsPipeline.append(SkRasterPipeline::table_b, this->copy(fn));
336 break;
337 case 3:
338 fElementsPipeline.append(SkRasterPipeline::table_a, this->copy(fn));
339 break;
340 default:
341 SkASSERT(false);
342 }
343 }
344
addMatrix(const SkMatrix44 & m44)345 void SkColorSpaceXform_A2B::addMatrix(const SkMatrix44& m44) {
346 auto m = fAlloc.makeArray<float>(12);
347 m[0] = m44.get(0,0); m[ 1] = m44.get(1,0); m[ 2] = m44.get(2,0);
348 m[3] = m44.get(0,1); m[ 4] = m44.get(1,1); m[ 5] = m44.get(2,1);
349 m[6] = m44.get(0,2); m[ 7] = m44.get(1,2); m[ 8] = m44.get(2,2);
350 m[9] = m44.get(0,3); m[10] = m44.get(1,3); m[11] = m44.get(2,3);
351
352 SkASSERT(m44.get(3,0) == 0.0f);
353 SkASSERT(m44.get(3,1) == 0.0f);
354 SkASSERT(m44.get(3,2) == 0.0f);
355 SkASSERT(m44.get(3,3) == 1.0f);
356
357 fElementsPipeline.append(SkRasterPipeline::matrix_3x4, m);
358 fElementsPipeline.append(SkRasterPipeline::clamp_0);
359 fElementsPipeline.append(SkRasterPipeline::clamp_1);
360 }
361