blob: 4cfb05d8e186a05db33dbd2246a2c2f655f2c35a [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkColorSpaceXform_A2B.h"
#include "SkColorPriv.h"
#include "SkColorSpace_A2B.h"
#include "SkColorSpace_XYZ.h"
#include "SkColorSpacePriv.h"
#include "SkColorSpaceXformPriv.h"
#include "SkMakeUnique.h"
#include "SkNx.h"
#include "SkSRGB.h"
#include "SkTypes.h"
#include "../jumper/SkJumper.h"
bool SkColorSpaceXform_A2B::onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat,
const void* src, int count, SkAlphaType alphaType) const {
SkRasterPipeline_<256> pipeline;
switch (srcFormat) {
case kBGRA_8888_ColorFormat:
pipeline.append(SkRasterPipeline::load_bgra, &src);
break;
case kRGBA_8888_ColorFormat:
pipeline.append(SkRasterPipeline::load_8888, &src);
break;
case kRGBA_U16_BE_ColorFormat:
pipeline.append(SkRasterPipeline::load_u16_be, &src);
break;
case kRGB_U16_BE_ColorFormat:
pipeline.append(SkRasterPipeline::load_rgb_u16_be, &src);
break;
default:
SkCSXformPrintf("F16/F32 sources must be linear.\n");
return false;
}
pipeline.extend(fElementsPipeline);
if (kPremul_SkAlphaType == alphaType) {
pipeline.append(SkRasterPipeline::premul);
}
switch (dstFormat) {
case kBGRA_8888_ColorFormat:
pipeline.append(SkRasterPipeline::store_bgra, &dst);
break;
case kRGBA_8888_ColorFormat:
pipeline.append(SkRasterPipeline::store_8888, &dst);
break;
case kRGBA_F16_ColorFormat:
if (!fLinearDstGamma) {
return false;
}
pipeline.append(SkRasterPipeline::store_f16, &dst);
break;
case kRGBA_F32_ColorFormat:
if (!fLinearDstGamma) {
return false;
}
pipeline.append(SkRasterPipeline::store_f32, &dst);
break;
case kBGR_565_ColorFormat:
if (kOpaque_SkAlphaType != alphaType) {
return false;
}
pipeline.append(SkRasterPipeline::store_565, &dst);
break;
default:
return false;
}
pipeline.run(0,0, count);
return true;
}
static inline bool gamma_to_parametric(SkColorSpaceTransferFn* coeffs, const SkGammas& gammas,
int channel) {
switch (gammas.type(channel)) {
case SkGammas::Type::kNamed_Type:
return named_to_parametric(coeffs, gammas.data(channel).fNamed);
case SkGammas::Type::kValue_Type:
value_to_parametric(coeffs, gammas.data(channel).fValue);
return true;
case SkGammas::Type::kParam_Type:
*coeffs = gammas.params(channel);
return true;
default:
return false;
}
}
SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
SkColorSpace_XYZ* dstSpace)
: fElementsPipeline(&fAlloc)
, fLinearDstGamma(kLinear_SkGammaNamed == dstSpace->gammaNamed()) {
#if (SkCSXformPrintfDefined)
static const char* debugGammaNamed[4] = {
"Linear", "SRGB", "2.2", "NonStandard"
};
static const char* debugGammas[5] = {
"None", "Named", "Value", "Table", "Param"
};
#endif
int currentChannels;
switch (srcSpace->iccType()) {
case SkColorSpace_Base::kRGB_ICCTypeFlag:
currentChannels = 3;
break;
case SkColorSpace_Base::kCMYK_ICCTypeFlag: {
currentChannels = 4;
// CMYK images from JPEGs (the only format that supports it) are actually
// inverted CMYK, so we need to invert every channel.
// TransferFn is y = -x + 1 for x < 1.f, otherwise 0x + 0, ie y = 1 - x for x in [0,1]
SkColorSpaceTransferFn fn = {0,0,0,0,0,0,0};
fn.fG = 1;
fn.fA = 0;
fn.fB = 0;
fn.fC = -1;
fn.fD = 1;
fn.fE = 0;
fn.fF = 1;
this->addTransferFns(fn,4);
break;
}
default:
currentChannels = 0;
SkASSERT(false);
}
// add in all input color space -> PCS xforms
for (int i = 0; i < srcSpace->count(); ++i) {
const SkColorSpace_A2B::Element& e = srcSpace->element(i);
SkASSERT(e.inputChannels() == currentChannels);
currentChannels = e.outputChannels();
switch (e.type()) {
case SkColorSpace_A2B::Element::Type::kGammaNamed:
if (kLinear_SkGammaNamed == e.gammaNamed()) {
break;
}
// Take the fast path for ordinary sRGB.
if (3 == currentChannels && kSRGB_SkGammaNamed == e.gammaNamed()) {
SkCSXformPrintf("fast path from sRGB\n");
// Images should always start the pipeline as unpremul
fElementsPipeline.append_from_srgb(kUnpremul_SkAlphaType);
break;
}
SkCSXformPrintf("Gamma stage added: %s\n", debugGammaNamed[(int)e.gammaNamed()]);
SkColorSpaceTransferFn fn;
SkAssertResult(named_to_parametric(&fn, e.gammaNamed()));
this->addTransferFns(fn, currentChannels);
break;
case SkColorSpace_A2B::Element::Type::kGammas: {
const SkGammas& gammas = e.gammas();
SkCSXformPrintf("Gamma stage added:");
for (int channel = 0; channel < gammas.channels(); ++channel) {
SkCSXformPrintf(" %s", debugGammas[(int)gammas.type(channel)]);
}
SkCSXformPrintf("\n");
bool gammaNeedsRef = false;
for (int channel = 0; channel < gammas.channels(); ++channel) {
if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
SkTableTransferFn table = {
gammas.table(channel),
gammas.data(channel).fTable.fSize,
};
gammaNeedsRef |= !this->buildTableFn(&table);
this->addTableFn(table, channel);
} else {
SkColorSpaceTransferFn fn;
SkAssertResult(gamma_to_parametric(&fn, gammas, channel));
this->addTransferFn(fn, channel);
}
}
if (gammaNeedsRef) {
this->copy(sk_ref_sp(&gammas));
}
break;
}
case SkColorSpace_A2B::Element::Type::kCLUT: {
SkCSXformPrintf("CLUT (%d -> %d) stage added\n", e.colorLUT().inputChannels(),
e.colorLUT().outputChannels());
struct CallbackCtx : SkJumper_CallbackCtx {
sk_sp<const SkColorLookUpTable> clut;
// clut->interp() can't always safely alias its arguments,
// so we allocate a second buffer to hold our results.
float results[4*SkJumper_kMaxStride];
};
auto cb = fAlloc.make<CallbackCtx>();
cb->clut = sk_ref_sp(&e.colorLUT());
cb->read_from = cb->results;
cb->fn = [](SkJumper_CallbackCtx* ctx, int active_pixels) {
auto c = (CallbackCtx*)ctx;
for (int i = 0; i < active_pixels; i++) {
// Look up red, green, and blue for this pixel using 3-4 values from rgba.
c->clut->interp(c->results+4*i, c->rgba+4*i);
// If we used 3 inputs (rgb) preserve the fourth as alpha.
// If we used 4 inputs (cmyk) force alpha to 1.
c->results[4*i+3] = (3 == c->clut->inputChannels()) ? c->rgba[4*i+3] : 1.0f;
}
};
fElementsPipeline.append(SkRasterPipeline::callback, cb);
break;
}
case SkColorSpace_A2B::Element::Type::kMatrix:
if (!e.matrix().isIdentity()) {
SkCSXformPrintf("Matrix stage added\n");
addMatrix(e.matrix());
}
break;
}
}
// Lab PCS -> XYZ PCS
if (SkColorSpace_A2B::PCS::kLAB == srcSpace->pcs()) {
SkCSXformPrintf("Lab -> XYZ element added\n");
fElementsPipeline.append(SkRasterPipeline::lab_to_xyz);
}
// we should now be in XYZ PCS
SkASSERT(3 == currentChannels);
// and XYZ PCS -> output color space xforms
if (!dstSpace->fromXYZD50()->isIdentity()) {
addMatrix(*dstSpace->fromXYZD50());
}
switch (dstSpace->gammaNamed()) {
case kLinear_SkGammaNamed:
// do nothing
break;
case k2Dot2Curve_SkGammaNamed: {
SkColorSpaceTransferFn fn = {0,0,0,0,0,0,0};
fn.fG = 1/2.2f;
fn.fA = 1;
auto to_2dot2 = this->copy(fn);
fElementsPipeline.append(SkRasterPipeline::parametric_r, to_2dot2);
fElementsPipeline.append(SkRasterPipeline::parametric_g, to_2dot2);
fElementsPipeline.append(SkRasterPipeline::parametric_b, to_2dot2);
break;
}
case kSRGB_SkGammaNamed:
fElementsPipeline.append(SkRasterPipeline::to_srgb);
break;
case kNonStandard_SkGammaNamed: {
for (int channel = 0; channel < 3; ++channel) {
const SkGammas& gammas = *dstSpace->gammas();
if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
static constexpr int kInvTableSize = 256;
auto storage = fAlloc.makeArray<float>(kInvTableSize);
invert_table_gamma(storage, nullptr, kInvTableSize,
gammas.table(channel),
gammas.data(channel).fTable.fSize);
SkTableTransferFn table = { storage, kInvTableSize };
this->addTableFn(table, channel);
} else {
SkColorSpaceTransferFn fn;
SkAssertResult(gamma_to_parametric(&fn, gammas, channel));
this->addTransferFn(fn.invert(), channel);
}
}
}
break;
}
}
void SkColorSpaceXform_A2B::addTransferFns(const SkColorSpaceTransferFn& fn, int channelCount) {
for (int i = 0; i < channelCount; ++i) {
this->addTransferFn(fn, i);
}
}
void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex) {
switch (channelIndex) {
case 0:
fElementsPipeline.append(SkRasterPipeline::parametric_r, this->copy(fn));
break;
case 1:
fElementsPipeline.append(SkRasterPipeline::parametric_g, this->copy(fn));
break;
case 2:
fElementsPipeline.append(SkRasterPipeline::parametric_b, this->copy(fn));
break;
case 3:
fElementsPipeline.append(SkRasterPipeline::parametric_a, this->copy(fn));
break;
default:
SkASSERT(false);
}
}
/**
* |fn| is an in-out parameter. If the table is too small to perform reasonable table-lookups
* without interpolation, we will build a bigger table.
*
* This returns false if we use the original table, meaning we do nothing here but need to keep
* a reference to the original table. This returns true if we build a new table and the original
* table can be discarded.
*/
bool SkColorSpaceXform_A2B::buildTableFn(SkTableTransferFn* fn) {
// Arbitrary, but seems like a reasonable guess.
static constexpr int kMinTableSize = 256;
if (fn->fSize >= kMinTableSize) {
return false;
}
float* outTable = fAlloc.makeArray<float>(kMinTableSize);
float step = 1.0f / (kMinTableSize - 1);
for (int i = 0; i < kMinTableSize; i++) {
outTable[i] = interp_lut(i * step, fn->fData, fn->fSize);
}
fn->fData = outTable;
fn->fSize = kMinTableSize;
return true;
}
void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, int channelIndex) {
switch (channelIndex) {
case 0:
fElementsPipeline.append(SkRasterPipeline::table_r, this->copy(fn));
break;
case 1:
fElementsPipeline.append(SkRasterPipeline::table_g, this->copy(fn));
break;
case 2:
fElementsPipeline.append(SkRasterPipeline::table_b, this->copy(fn));
break;
case 3:
fElementsPipeline.append(SkRasterPipeline::table_a, this->copy(fn));
break;
default:
SkASSERT(false);
}
}
void SkColorSpaceXform_A2B::addMatrix(const SkMatrix44& m44) {
auto m = fAlloc.makeArray<float>(12);
m[0] = m44.get(0,0); m[ 1] = m44.get(1,0); m[ 2] = m44.get(2,0);
m[3] = m44.get(0,1); m[ 4] = m44.get(1,1); m[ 5] = m44.get(2,1);
m[6] = m44.get(0,2); m[ 7] = m44.get(1,2); m[ 8] = m44.get(2,2);
m[9] = m44.get(0,3); m[10] = m44.get(1,3); m[11] = m44.get(2,3);
SkASSERT(m44.get(3,0) == 0.0f);
SkASSERT(m44.get(3,1) == 0.0f);
SkASSERT(m44.get(3,2) == 0.0f);
SkASSERT(m44.get(3,3) == 1.0f);
fElementsPipeline.append(SkRasterPipeline::matrix_3x4, m);
fElementsPipeline.append(SkRasterPipeline::clamp_0);
fElementsPipeline.append(SkRasterPipeline::clamp_1);
}