• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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