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