2 * Copyright 2016 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "SkColorSpaceXform_A2B.h"
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"
19 #include "../jumper/SkJumper.h"
21 bool SkColorSpaceXform_A2B::onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat,
22 const void* src, int count, SkAlphaType alphaType) const {
23 SkRasterPipeline_<256> pipeline;
25 case kBGRA_8888_ColorFormat:
26 pipeline.append(SkRasterPipeline::load_8888, &src);
27 pipeline.append(SkRasterPipeline::swap_rb);
29 case kRGBA_8888_ColorFormat:
30 pipeline.append(SkRasterPipeline::load_8888, &src);
32 case kRGBA_U16_BE_ColorFormat:
33 pipeline.append(SkRasterPipeline::load_u16_be, &src);
35 case kRGB_U16_BE_ColorFormat:
36 pipeline.append(SkRasterPipeline::load_rgb_u16_be, &src);
39 SkCSXformPrintf("F16/F32 sources must be linear.\n");
43 pipeline.extend(fElementsPipeline);
45 if (kPremul_SkAlphaType == alphaType) {
46 pipeline.append(SkRasterPipeline::premul);
50 case kBGRA_8888_ColorFormat:
51 pipeline.append(SkRasterPipeline::swap_rb);
52 pipeline.append(SkRasterPipeline::store_8888, &dst);
54 case kRGBA_8888_ColorFormat:
55 pipeline.append(SkRasterPipeline::store_8888, &dst);
57 case kRGBA_F16_ColorFormat:
58 if (!fLinearDstGamma) {
61 pipeline.append(SkRasterPipeline::store_f16, &dst);
63 case kRGBA_F32_ColorFormat:
64 if (!fLinearDstGamma) {
67 pipeline.append(SkRasterPipeline::store_f32, &dst);
69 case kBGR_565_ColorFormat:
70 if (kOpaque_SkAlphaType != alphaType) {
73 pipeline.append(SkRasterPipeline::store_565, &dst);
78 pipeline.run(0,count);
83 static inline bool gamma_to_parametric(SkColorSpaceTransferFn* coeffs, const SkGammas& gammas,
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);
91 case SkGammas::Type::kParam_Type:
92 *coeffs = gammas.params(channel);
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"
107 static const char* debugGammas[5] = {
108 "None", "Named", "Value", "Table", "Param"
112 switch (srcSpace->iccType()) {
113 case SkColorSpace_Base::kRGB_ICCTypeFlag:
116 case SkColorSpace_Base::kCMYK_ICCTypeFlag: {
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};
129 this->addTransferFns(fn,4);
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();
142 case SkColorSpace_A2B::Element::Type::kGammaNamed:
143 if (kLinear_SkGammaNamed == e.gammaNamed()) {
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);
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);
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)]);
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,
175 gammaNeedsRef |= !this->buildTableFn(&table);
176 this->addTableFn(table, channel);
178 SkColorSpaceTransferFn fn;
179 SkAssertResult(gamma_to_parametric(&fn, gammas, channel));
180 this->addTransferFn(fn, channel);
184 this->copy(sk_ref_sp(&gammas));
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];
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);
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;
211 fElementsPipeline.append(SkRasterPipeline::callback, cb);
214 case SkColorSpace_A2B::Element::Type::kMatrix:
215 if (!e.matrix().isIdentity()) {
216 SkCSXformPrintf("Matrix stage added\n");
217 addMatrix(e.matrix());
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);
229 // we should now be in XYZ PCS
230 SkASSERT(3 == currentChannels);
232 // and XYZ PCS -> output color space xforms
233 if (!dstSpace->fromXYZD50()->isIdentity()) {
234 addMatrix(*dstSpace->fromXYZD50());
237 switch (dstSpace->gammaNamed()) {
238 case kLinear_SkGammaNamed:
241 case k2Dot2Curve_SkGammaNamed: {
242 SkColorSpaceTransferFn fn = {0,0,0,0,0,0,0};
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);
251 case kSRGB_SkGammaNamed:
252 fElementsPipeline.append(SkRasterPipeline::to_srgb);
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);
266 SkColorSpaceTransferFn fn;
267 SkAssertResult(gamma_to_parametric(&fn, gammas, channel));
268 this->addTransferFn(fn.invert(), channel);
276 void SkColorSpaceXform_A2B::addTransferFns(const SkColorSpaceTransferFn& fn, int channelCount) {
277 for (int i = 0; i < channelCount; ++i) {
278 this->addTransferFn(fn, i);
282 void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex) {
283 switch (channelIndex) {
285 fElementsPipeline.append(SkRasterPipeline::parametric_r, this->copy(fn));
288 fElementsPipeline.append(SkRasterPipeline::parametric_g, this->copy(fn));
291 fElementsPipeline.append(SkRasterPipeline::parametric_b, this->copy(fn));
294 fElementsPipeline.append(SkRasterPipeline::parametric_a, this->copy(fn));
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.
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.
309 bool SkColorSpaceXform_A2B::buildTableFn(SkTableTransferFn* fn) {
310 // Arbitrary, but seems like a reasonable guess.
311 static constexpr int kMinTableSize = 256;
313 if (fn->fSize >= kMinTableSize) {
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);
323 fn->fData = outTable;
324 fn->fSize = kMinTableSize;
328 void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, int channelIndex) {
329 switch (channelIndex) {
331 fElementsPipeline.append(SkRasterPipeline::table_r, this->copy(fn));
334 fElementsPipeline.append(SkRasterPipeline::table_g, this->copy(fn));
337 fElementsPipeline.append(SkRasterPipeline::table_b, this->copy(fn));
340 fElementsPipeline.append(SkRasterPipeline::table_a, this->copy(fn));
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);
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);
359 fElementsPipeline.append(SkRasterPipeline::matrix_3x4, m);
360 fElementsPipeline.append(SkRasterPipeline::clamp_0);
361 fElementsPipeline.append(SkRasterPipeline::clamp_1);