| Index: src/core/SkColorSpaceXform_A2B.cpp
 | 
| diff --git a/src/core/SkColorSpaceXform_A2B.cpp b/src/core/SkColorSpaceXform_A2B.cpp
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..04ecf7707f9c6bd2aab05a24589a4b3373595273
 | 
| --- /dev/null
 | 
| +++ b/src/core/SkColorSpaceXform_A2B.cpp
 | 
| @@ -0,0 +1,352 @@
 | 
| +/*
 | 
| + * 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 "SkRasterPipeline_opts.h"
 | 
| +
 | 
| +#define AI SK_ALWAYS_INLINE
 | 
| +
 | 
| +namespace {
 | 
| +
 | 
| +class ApplyParametric {
 | 
| +public:
 | 
| +    ApplyParametric(const SkColorSpaceTransferFn& fn)
 | 
| +        : fFn(fn)
 | 
| +    {}
 | 
| +
 | 
| +    float operator()(float x) const {
 | 
| +        float y;
 | 
| +        if (x >= fFn.fD) {
 | 
| +            y = ::powf(fFn.fA * x + fFn.fB, fFn.fG) + fFn.fC;
 | 
| +        } else {
 | 
| +            y = fFn.fE * x + fFn.fF;
 | 
| +        }
 | 
| +        if (y >= 1.f) {
 | 
| +            return 1.f;
 | 
| +        } else if (y >= 0.f) {
 | 
| +            return y;
 | 
| +        }
 | 
| +        return 0.f;
 | 
| +    }
 | 
| +
 | 
| +private:
 | 
| +    SkColorSpaceTransferFn fFn;
 | 
| +};
 | 
| +
 | 
| +class ApplyTable {
 | 
| +public:
 | 
| +    ApplyTable(const float* table, int size)
 | 
| +        : fTable(table)
 | 
| +        , fSize(size)
 | 
| +    {}
 | 
| +
 | 
| +    float operator()(float x) const {
 | 
| +        return interp_lut(x, fTable, fSize);
 | 
| +    }
 | 
| +
 | 
| +private:
 | 
| +    const float* fTable;
 | 
| +    int          fSize;
 | 
| +};
 | 
| +
 | 
| +}
 | 
| +
 | 
| +///////////////////////////////////////////////////////////////////////////////////////////////////
 | 
| +bool SkColorSpaceXform_A2B::onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat,
 | 
| +                                    const void* src, int count, SkAlphaType alphaType) const {
 | 
| +    SkRasterPipeline pipeline;
 | 
| +    switch (srcFormat) {
 | 
| +        case kBGRA_8888_ColorFormat:
 | 
| +            pipeline.append(SkRasterPipeline::load_s_8888, &src);
 | 
| +            pipeline.append(SkRasterPipeline::swap_rb);
 | 
| +            break;
 | 
| +        case kRGBA_8888_ColorFormat:
 | 
| +            pipeline.append(SkRasterPipeline::load_s_8888, &src);
 | 
| +            break;
 | 
| +        default:
 | 
| +            SkCSXformPrintf("F16/F32 source color format not supported\n");
 | 
| +            return false;
 | 
| +    }
 | 
| +
 | 
| +    pipeline.extend(fElementsPipeline);
 | 
| +
 | 
| +    if (kPremul_SkAlphaType == alphaType) {
 | 
| +        pipeline.append(SkRasterPipeline::premul);
 | 
| +    }
 | 
| +
 | 
| +    switch (dstFormat) {
 | 
| +        case kBGRA_8888_ColorFormat:
 | 
| +            pipeline.append(SkRasterPipeline::swap_rb);
 | 
| +            pipeline.append(SkRasterPipeline::store_8888, &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;
 | 
| +    }
 | 
| +
 | 
| +    auto p = pipeline.compile();
 | 
| +
 | 
| +    p(0, count);
 | 
| +
 | 
| +    return true;
 | 
| +}
 | 
| +
 | 
| +static inline SkColorSpaceTransferFn value_to_parametric(float exp) {
 | 
| +    return {exp, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
 | 
| +}
 | 
| +
 | 
| +static inline SkColorSpaceTransferFn gammanamed_to_parametric(SkGammaNamed gammaNamed) {
 | 
| +    switch (gammaNamed) {
 | 
| +        case kLinear_SkGammaNamed:
 | 
| +            return value_to_parametric(1.f);
 | 
| +        case kSRGB_SkGammaNamed:
 | 
| +            return {2.4f, (1.f / 1.055f), (0.055f / 1.055f), 0.f, 0.04045f, (1.f / 12.92f), 0.f};
 | 
| +        case k2Dot2Curve_SkGammaNamed:
 | 
| +            return value_to_parametric(2.2f);
 | 
| +        default:
 | 
| +            SkASSERT(false);
 | 
| +            return {-1.f, -1.f, -1.f, -1.f, -1.f, -1.f, -1.f};
 | 
| +    }
 | 
| +}
 | 
| +
 | 
| +static inline SkColorSpaceTransferFn gamma_to_parametric(const SkGammas& gammas, int channel) {
 | 
| +    switch (gammas.type(channel)) {
 | 
| +        case SkGammas::Type::kNamed_Type:
 | 
| +            return gammanamed_to_parametric(gammas.data(channel).fNamed);
 | 
| +        case SkGammas::Type::kValue_Type:
 | 
| +            return value_to_parametric(gammas.data(channel).fValue);
 | 
| +        case SkGammas::Type::kParam_Type:
 | 
| +            return gammas.params(channel);
 | 
| +        default:
 | 
| +            SkASSERT(false);
 | 
| +            return {-1.f, -1.f, -1.f, -1.f, -1.f, -1.f, -1.f};
 | 
| +    }
 | 
| +}
 | 
| +static inline SkColorSpaceTransferFn invert_parametric(const SkColorSpaceTransferFn& fn) {
 | 
| +    // Original equation is:       y = (ax + b)^g + c   for x >= d
 | 
| +    //                             y = ex + f           otherwise
 | 
| +    //
 | 
| +    // so 1st inverse is:          (y - c)^(1/g) = ax + b
 | 
| +    //                             x = ((y - c)^(1/g) - b) / a
 | 
| +    //
 | 
| +    // which can be re-written as: x = (1/a)(y - c)^(1/g) - b/a
 | 
| +    //                             x = ((1/a)^g)^(1/g) * (y - c)^(1/g) - b/a
 | 
| +    //                             x = ([(1/a)^g]y + [-((1/a)^g)c]) ^ [1/g] + [-b/a]
 | 
| +    //
 | 
| +    // and 2nd inverse is:         x = (y - f) / e
 | 
| +    // which can be re-written as: x = [1/e]y + [-f/e]
 | 
| +    //
 | 
| +    // and now both can be expressed in terms of the same parametric form as the
 | 
| +    // original - parameters are enclosed in square barckets.
 | 
| +
 | 
| +    // find inverse for linear segment (if possible)
 | 
| +    float e, f;
 | 
| +    if (0.f == fn.fE) {
 | 
| +        // otherwise assume it should be 0 as it is the lower segment
 | 
| +        // as y = f is a constant function
 | 
| +        e = 0.f;
 | 
| +        f = 0.f;
 | 
| +    } else {
 | 
| +        e = 1.f / fn.fE;
 | 
| +        f = -fn.fF / fn.fE;
 | 
| +    }
 | 
| +    // find inverse for the other segment (if possible)
 | 
| +    float g, a, b, c;
 | 
| +    if (0.f == fn.fA || 0.f == fn.fG) {
 | 
| +        // otherwise assume it should be 1 as it is the top segment
 | 
| +        // as you can't invert the constant functions y = b^g + c, or y = 1 + c
 | 
| +        g = 1.f;
 | 
| +        a = 0.f;
 | 
| +        b = 0.f;
 | 
| +        c = 1.f;
 | 
| +    } else {
 | 
| +        g = 1.f / fn.fG;
 | 
| +        a = powf(1.f / fn.fA, fn.fG);
 | 
| +        b = -a * fn.fC;
 | 
| +        c = -fn.fB / fn.fA;
 | 
| +    }
 | 
| +    const float d = fn.fE * fn.fD + fn.fF;
 | 
| +    return {g, a, b, c, d, e, f};
 | 
| +}
 | 
| +
 | 
| +static std::vector<float> build_inverse_table(const float* inTable, int inTableSize) {
 | 
| +    static constexpr int kInvTableSize = 256;
 | 
| +    std::vector<float> outTable(kInvTableSize);
 | 
| +    for (int i = 0; i < kInvTableSize; ++i) {
 | 
| +        const float x = ((float) i) * (1.f / ((float) (kInvTableSize - 1)));
 | 
| +        const float y = inverse_interp_lut(x, inTable, inTableSize);
 | 
| +        outTable[i] = y;
 | 
| +    }
 | 
| +    return outTable;
 | 
| +}
 | 
| +
 | 
| +SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
 | 
| +                                             SkColorSpace_XYZ* dstSpace)
 | 
| +    : 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
 | 
| +    // add in all input color space -> PCS xforms
 | 
| +    for (int i = 0; i < srcSpace->count(); ++i) {
 | 
| +        const SkColorSpace_A2B::Element& e = srcSpace->element(i);
 | 
| +        switch (e.type()) {
 | 
| +            case SkColorSpace_A2B::Element::Type::kGammaNamed:
 | 
| +                if (kLinear_SkGammaNamed != e.gammaNamed()) {
 | 
| +                    SkCSXformPrintf("Gamma stage added: %s\n",
 | 
| +                                    debugGammaNamed[(int)e.gammaNamed()]);
 | 
| +                    addGamma(ApplyParametric(gammanamed_to_parametric(e.gammaNamed())),
 | 
| +                             kRGB_Channels);
 | 
| +                }
 | 
| +                break;
 | 
| +            case SkColorSpace_A2B::Element::Type::kGammas: {
 | 
| +                    const SkGammas& gammas = e.gammas();
 | 
| +                    SkCSXformPrintf("Gamma stage added:");
 | 
| +                    for (int channel = 0; channel < 3; ++channel) {
 | 
| +                        SkCSXformPrintf("  %s", debugGammas[(int)gammas.type(channel)]);
 | 
| +                    }
 | 
| +                    SkCSXformPrintf("\n");
 | 
| +                    bool gammaNeedsRef = false;
 | 
| +                    for (int channel = 0; channel < 3; ++channel) {
 | 
| +                        if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
 | 
| +                            addGamma(ApplyTable(gammas.table(channel),
 | 
| +                                                gammas.data(channel).fTable.fSize),
 | 
| +                                                static_cast<Channels>(channel));
 | 
| +                            gammaNeedsRef = true;
 | 
| +                        } else {
 | 
| +                            addGamma(ApplyParametric(gamma_to_parametric(gammas, channel)),
 | 
| +                                     static_cast<Channels>(channel));
 | 
| +                        }
 | 
| +                    }
 | 
| +                    if (gammaNeedsRef) {
 | 
| +                        fGammaRefs.push_back(sk_ref_sp(&gammas));
 | 
| +                    }
 | 
| +                }
 | 
| +                break;
 | 
| +            case SkColorSpace_A2B::Element::Type::kCLUT:
 | 
| +                SkCSXformPrintf("CLUT stage added [%d][%d][%d]\n", e.colorLUT().fGridPoints[0],
 | 
| +                                e.colorLUT().fGridPoints[1], e.colorLUT().fGridPoints[2]);
 | 
| +                fCLUTs.push_back(sk_ref_sp(&e.colorLUT()));
 | 
| +                fElementsPipeline.append(SkRasterPipeline::color_lookup_table,
 | 
| +                                         fCLUTs.back().get());
 | 
| +                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);
 | 
| +    }
 | 
| +
 | 
| +    // and XYZ PCS -> output color space xforms
 | 
| +    if (!dstSpace->fromXYZD50()->isIdentity()) {
 | 
| +        addMatrix(*dstSpace->fromXYZD50());
 | 
| +    }
 | 
| +
 | 
| +    if (kNonStandard_SkGammaNamed != dstSpace->gammaNamed()) {
 | 
| +        if (!fLinearDstGamma) {
 | 
| +            addGamma(ApplyParametric(
 | 
| +                            invert_parametric(gammanamed_to_parametric(dstSpace->gammaNamed()))),
 | 
| +                     kRGB_Channels);
 | 
| +        }
 | 
| +    } else {
 | 
| +        for (int channel = 0; channel < 3; ++channel) {
 | 
| +            const SkGammas& gammas = *dstSpace->gammas();
 | 
| +            if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
 | 
| +                fGammaTables.push_front(build_inverse_table(gammas.table(channel),
 | 
| +                                                            gammas.data(channel).fTable.fSize));
 | 
| +                addGamma(ApplyTable(fGammaTables.front().data(), fGammaTables.front().size()),
 | 
| +                         static_cast<Channels>(channel));
 | 
| +            } else {
 | 
| +                addGamma(ApplyParametric(invert_parametric(gamma_to_parametric(gammas, channel))),
 | 
| +                         static_cast<Channels>(channel));
 | 
| +            }
 | 
| +        }
 | 
| +    }
 | 
| +}
 | 
| +
 | 
| +void SkColorSpaceXform_A2B::addGamma(std::function<float(float)> fn, Channels channels) {
 | 
| +    fGammaFunctions.push_front(std::move(fn));
 | 
| +    switch (channels) {
 | 
| +        case kRGB_Channels:
 | 
| +            fElementsPipeline.append(SkRasterPipeline::fn_1_r, &fGammaFunctions.front());
 | 
| +            fElementsPipeline.append(SkRasterPipeline::fn_1_g, &fGammaFunctions.front());
 | 
| +            fElementsPipeline.append(SkRasterPipeline::fn_1_b, &fGammaFunctions.front());
 | 
| +            break;
 | 
| +        case kR_Channels:
 | 
| +            fElementsPipeline.append(SkRasterPipeline::fn_1_r, &fGammaFunctions.front());
 | 
| +            break;
 | 
| +        case kG_Channels:
 | 
| +            fElementsPipeline.append(SkRasterPipeline::fn_1_g, &fGammaFunctions.front());
 | 
| +            break;
 | 
| +        case kB_Channels:
 | 
| +            fElementsPipeline.append(SkRasterPipeline::fn_1_b, &fGammaFunctions.front());
 | 
| +            break;
 | 
| +        default:
 | 
| +            SkASSERT(false);
 | 
| +    }
 | 
| +}
 | 
| +
 | 
| +void SkColorSpaceXform_A2B::addMatrix(const SkMatrix44& matrix) {
 | 
| +    fMatrices.push_front(std::vector<float>(12));
 | 
| +    auto& m = fMatrices.front();
 | 
| +    m[ 0] = matrix.get(0, 0);
 | 
| +    m[ 1] = matrix.get(1, 0);
 | 
| +    m[ 2] = matrix.get(2, 0);
 | 
| +    m[ 3] = matrix.get(0, 1);
 | 
| +    m[ 4] = matrix.get(1, 1);
 | 
| +    m[ 5] = matrix.get(2, 1);
 | 
| +    m[ 6] = matrix.get(0, 2);
 | 
| +    m[ 7] = matrix.get(1, 2);
 | 
| +    m[ 8] = matrix.get(2, 2);
 | 
| +    m[ 9] = matrix.get(0, 3);
 | 
| +    m[10] = matrix.get(1, 3);
 | 
| +    m[11] = matrix.get(2, 3);
 | 
| +    SkASSERT(matrix.get(3, 0) == 0.f);
 | 
| +    SkASSERT(matrix.get(3, 1) == 0.f);
 | 
| +    SkASSERT(matrix.get(3, 2) == 0.f);
 | 
| +    SkASSERT(matrix.get(3, 3) == 1.f);
 | 
| +    fElementsPipeline.append(SkRasterPipeline::matrix_3x4, m.data());
 | 
| +    fElementsPipeline.append(SkRasterPipeline::clamp_0);
 | 
| +    fElementsPipeline.append(SkRasterPipeline::clamp_a);
 | 
| +}
 | 
| +
 | 
| +
 | 
| 
 |