/*
 * 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 "SkColorSpace_XYZ.h"
#include "SkColorSpacePriv.h"
#include "SkColorSpaceXformPriv.h"
#include "SkOpts.h"

SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
    : fProfileData(nullptr)
    , fGammaNamed(gammaNamed)
    , fGammas(nullptr)
    , fToXYZD50(toXYZD50)
    , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
{}

SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gammas,
                                   const SkMatrix44& toXYZD50, sk_sp<SkData> profileData)
    : fProfileData(std::move(profileData))
    , fGammaNamed(gammaNamed)
    , fGammas(std::move(gammas))
    , fToXYZD50(toXYZD50)
    , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) {
    SkASSERT(!fGammas || 3 == fGammas->channels());
    if (fGammas) {
        for (int i = 0; i < fGammas->channels(); ++i) {
            if (SkGammas::Type::kTable_Type == fGammas->type(i)) {
                SkASSERT(fGammas->data(i).fTable.fSize >= 2);
            }
        }
    }
}

const SkMatrix44* SkColorSpace_XYZ::onFromXYZD50() const {
    fFromXYZOnce([this] {
        if (!fToXYZD50.invert(&fFromXYZD50)) {
            // If a client gives us a dst gamut with a transform that we can't invert, we will
            // simply give them back a transform to sRGB gamut.
            SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
            srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
            srgbToxyzD50.invert(&fFromXYZD50);
        }
    });
    return &fFromXYZD50;
}

bool SkColorSpace_XYZ::onGammaCloseToSRGB() const {
    return kSRGB_SkGammaNamed == fGammaNamed || k2Dot2Curve_SkGammaNamed == fGammaNamed;
}

bool SkColorSpace_XYZ::onGammaIsLinear() const {
    return kLinear_SkGammaNamed == fGammaNamed;
}

bool SkColorSpace_XYZ::onIsNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const {
    if (named_to_parametric(coeffs, fGammaNamed)) {
        return true;
    }

    SkASSERT(fGammas);
    if (!fGammas->allChannelsSame()) {
        return false;
    }

    if (fGammas->isValue(0)) {
        value_to_parametric(coeffs, fGammas->data(0).fValue);
        return true;
    }

    if (fGammas->isParametric(0)) {
        *coeffs = fGammas->params(0);
        return true;
    }

    return false;
}

sk_sp<SkColorSpace> SkColorSpace_XYZ::makeLinearGamma() const {
    if (this->gammaIsLinear()) {
        return sk_ref_sp(const_cast<SkColorSpace_XYZ*>(this));
    }
    return SkColorSpace::MakeRGB(kLinear_SkGammaNamed, fToXYZD50);
}

sk_sp<SkColorSpace> SkColorSpace_XYZ::makeSRGBGamma() const {
    if (this->gammaCloseToSRGB()) {
        return sk_ref_sp(const_cast<SkColorSpace_XYZ*>(this));
    }
    return SkColorSpace::MakeRGB(kSRGB_SkGammaNamed, fToXYZD50);
}

sk_sp<SkColorSpace> SkColorSpace_XYZ::makeColorSpin() const {
    SkMatrix44 spin(SkMatrix44::kUninitialized_Constructor);
    spin.set3x3(0, 1, 0, 0, 0, 1, 1, 0, 0);
    spin.postConcat(fToXYZD50);
    (void)spin.getType();  // Pre-cache spin matrix type to avoid races in future getType() calls.
    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(fGammaNamed, fGammas, spin, fProfileData));
}
