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 "SkColorSpace_XYZ.h"
9#include "SkColorSpacePriv.h"
10#include "SkColorSpaceXform_Base.h"
11#include "SkOpts.h"
12
13SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
14    : fProfileData(nullptr)
15    , fGammaNamed(gammaNamed)
16    , fGammas(nullptr)
17    , fToXYZD50(toXYZD50)
18    , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
19    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
20{}
21
22SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gammas,
23                                   const SkMatrix44& toXYZD50, sk_sp<SkData> profileData)
24    : fProfileData(std::move(profileData))
25    , fGammaNamed(gammaNamed)
26    , fGammas(std::move(gammas))
27    , fToXYZD50(toXYZD50)
28    , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
29    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) {
30    SkASSERT(!fGammas || 3 == fGammas->channels());
31    if (fGammas) {
32        for (int i = 0; i < fGammas->channels(); ++i) {
33            if (SkGammas::Type::kTable_Type == fGammas->type(i)) {
34                SkASSERT(fGammas->data(i).fTable.fSize >= 2);
35            }
36        }
37    }
38}
39
40const SkMatrix44* SkColorSpace_XYZ::onFromXYZD50() const {
41    fFromXYZOnce([this] {
42        if (!fToXYZD50.invert(&fFromXYZD50)) {
43            // If a client gives us a dst gamut with a transform that we can't invert, we will
44            // simply give them back a transform to sRGB gamut.
45            SkDEBUGFAIL("Non-invertible XYZ matrix, defaulting to sRGB");
46            SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
47            srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
48            srgbToxyzD50.invert(&fFromXYZD50);
49        }
50    });
51    return &fFromXYZD50;
52}
53
54bool SkColorSpace_XYZ::onGammaCloseToSRGB() const {
55    return kSRGB_SkGammaNamed == fGammaNamed || k2Dot2Curve_SkGammaNamed == fGammaNamed;
56}
57
58bool SkColorSpace_XYZ::onGammaIsLinear() const {
59    return kLinear_SkGammaNamed == fGammaNamed;
60}
61
62bool SkColorSpace_XYZ::onIsNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const {
63    if (named_to_parametric(coeffs, fGammaNamed)) {
64        return true;
65    }
66
67    SkASSERT(fGammas);
68    if (!fGammas->allChannelsSame()) {
69        return false;
70    }
71
72    if (fGammas->isValue(0)) {
73        value_to_parametric(coeffs, fGammas->data(0).fValue);
74        return true;
75    }
76
77    if (fGammas->isParametric(0)) {
78        *coeffs = fGammas->params(0);
79        return true;
80    }
81
82    return false;
83}
84
85sk_sp<SkColorSpace> SkColorSpace_XYZ::makeLinearGamma() const {
86    if (this->gammaIsLinear()) {
87        return sk_ref_sp(const_cast<SkColorSpace_XYZ*>(this));
88    }
89    return SkColorSpace::MakeRGB(kLinear_SkGammaNamed, fToXYZD50);
90}
91
92sk_sp<SkColorSpace> SkColorSpace_XYZ::makeSRGBGamma() const {
93    if (this->gammaCloseToSRGB()) {
94        return sk_ref_sp(const_cast<SkColorSpace_XYZ*>(this));
95    }
96    return SkColorSpace::MakeRGB(kSRGB_SkGammaNamed, fToXYZD50);
97}
98
99sk_sp<SkColorSpace> SkColorSpace_XYZ::makeColorSpin() const {
100    SkMatrix44 spin(SkMatrix44::kUninitialized_Constructor);
101    spin.set3x3(0, 1, 0, 0, 0, 1, 1, 0, 0);
102    spin.postConcat(fToXYZD50);
103    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(fGammaNamed, fGammas, spin, fProfileData));
104}
105
106void SkColorSpace_XYZ::toDstGammaTables(const uint8_t* tables[3], sk_sp<SkData>* storage,
107                                         int numTables) const {
108    fToDstGammaOnce([this, numTables] {
109        const bool gammasAreMatching = numTables <= 1;
110        fDstStorage =
111                SkData::MakeUninitialized(numTables * SkColorSpaceXform_Base::kDstGammaTableSize);
112        SkColorSpaceXform_Base::BuildDstGammaTables(fToDstGammaTables,
113                                                    (uint8_t*) fDstStorage->writable_data(), this,
114                                                    gammasAreMatching);
115    });
116
117    *storage = fDstStorage;
118    tables[0] = fToDstGammaTables[0];
119    tables[1] = fToDstGammaTables[1];
120    tables[2] = fToDstGammaTables[2];
121}
122