blob: 9fdad1158f0eea018989d2a6033575de30146c4f [file] [log] [blame]
/*
* Copyright (C) 2017-2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "ColorTypes.h"
#include <wtf/MathExtras.h>
namespace WebCore {
class DestinationColorSpace;
enum class ColorSpace : uint8_t;
// Conversion function for typed colors.
template<typename Output, typename Input> Output convertColor(const Input& color);
// Conversion functions for raw color components with associated color spaces.
ColorComponents<float, 4> convertAndResolveColorComponents(ColorSpace inputColorSpace, ColorComponents<float, 4> inputColorComponents, ColorSpace outputColorSpace);
ColorComponents<float, 4> convertAndResolveColorComponents(ColorSpace inputColorSpace, ColorComponents<float, 4> inputColorComponents, const DestinationColorSpace& outputColorSpace);
// All color types, other than XYZA or those inheriting from RGBType, must implement
// the following conversions to and from their "Reference" color.
//
// template<> struct ColorConversion<`ColorType`<float>::Reference, `ColorType`<float>> {
// WEBCORE_EXPORT static `ColorType`<float>::Reference convert(const `ColorType`<float>&);
// };
//
// template<> struct ColorConversion<`ColorType`<float>, `ColorType`<float>::Reference> {
// WEBCORE_EXPORT static `ColorType`<float> convert(const `ColorType`<float>::Reference&);
// };
//
template<typename Output, typename Input, typename = void> struct ColorConversion;
template<typename Output, typename Input> inline Output convertColor(const Input& color)
{
return ColorConversion<CanonicalColorType<Output>, CanonicalColorType<Input>>::convert(color);
}
// MARK: Chromatic Adaptation conversions.
// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
template<WhitePoint From, WhitePoint To> struct ChromaticAdapation;
template<> struct ChromaticAdapation<WhitePoint::D65, WhitePoint::D50> {
static constexpr ColorMatrix<3, 3> matrix {
1.0478112f, 0.0228866f, -0.0501270f,
0.0295424f, 0.9904844f, -0.0170491f,
-0.0092345f, 0.0150436f, 0.7521316f
};
};
template<> struct ChromaticAdapation<WhitePoint::D50, WhitePoint::D65> {
static constexpr ColorMatrix<3, 3> matrix {
0.9555766f, -0.0230393f, 0.0631636f,
-0.0282895f, 1.0099416f, 0.0210077f,
0.0122982f, -0.0204830f, 1.3299098f
};
};
// MARK: HSLA
template<> struct ColorConversion<SRGBA<float>, HSLA<float>> {
WEBCORE_EXPORT static SRGBA<float> convert(const HSLA<float>&);
};
template<> struct ColorConversion<HSLA<float>, SRGBA<float>> {
WEBCORE_EXPORT static HSLA<float> convert(const SRGBA<float>&);
};
// MARK: HWBA
template<> struct ColorConversion<SRGBA<float>, HWBA<float>> {
WEBCORE_EXPORT static SRGBA<float> convert(const HWBA<float>&);
};
template<> struct ColorConversion<HWBA<float>, SRGBA<float>> {
WEBCORE_EXPORT static HWBA<float> convert(const SRGBA<float>&);
};
// MARK: LCHA
template<> struct ColorConversion<Lab<float>, LCHA<float>> {
WEBCORE_EXPORT static Lab<float> convert(const LCHA<float>&);
};
template<> struct ColorConversion<LCHA<float>, Lab<float>> {
WEBCORE_EXPORT static LCHA<float> convert(const Lab<float>&);
};
// MARK: Lab
template<> struct ColorConversion<XYZA<float, WhitePoint::D50>, Lab<float>> {
WEBCORE_EXPORT static XYZA<float, WhitePoint::D50> convert(const Lab<float>&);
};
template<> struct ColorConversion<Lab<float>, XYZA<float, WhitePoint::D50>> {
WEBCORE_EXPORT static Lab<float> convert(const XYZA<float, WhitePoint::D50>&);
};
// MARK: OKLCHA
template<> struct ColorConversion<OKLab<float>, OKLCHA<float>> {
WEBCORE_EXPORT static OKLab<float> convert(const OKLCHA<float>&);
};
template<> struct ColorConversion<OKLCHA<float>, OKLab<float>> {
WEBCORE_EXPORT static OKLCHA<float> convert(const OKLab<float>&);
};
// MARK: OKLab
template<> struct ColorConversion<XYZA<float, WhitePoint::D65>, OKLab<float>> {
WEBCORE_EXPORT static XYZA<float, WhitePoint::D65> convert(const OKLab<float>&);
};
template<> struct ColorConversion<OKLab<float>, XYZA<float, WhitePoint::D65>> {
WEBCORE_EXPORT static OKLab<float> convert(const XYZA<float, WhitePoint::D65>&);
};
// Identity conversion.
template<typename ColorType> struct ColorConversion<ColorType, ColorType> {
static ColorType convert(const ColorType& color)
{
return color;
}
};
// MARK: DeltaE color difference algorithms.
template<typename ColorType1, typename ColorType2> inline constexpr float computeDeltaEOK(ColorType1 color1, ColorType2 color2)
{
// https://drafts.csswg.org/css-color/#color-difference-OK
auto [L1, a1, b1, alpha1] = convertColor<OKLab<float>>(color1).resolved();
auto [L2, a2, b2, alpha2] = convertColor<OKLab<float>>(color2).resolved();
auto deltaL = (L1 / 100.0f) - (L2 / 100.0f);
auto deltaA = a1 - a2;
auto deltaB = b1 - b2;
return std::hypot(deltaL, deltaA, deltaB);
}
// MARK: Gamut mapping algorithms.
struct ClipGamutMapping {
template<typename ColorType> static auto mapToBoundedGamut(const ColorType& color) -> typename ColorType::BoundedCounterpart
{
return clipToGamut<typename ColorType::BoundedCounterpart>(color);
}
};
struct CSSGamutMapping {
// This implements the CSS gamut mapping algorithm (https://drafts.csswg.org/css-color/#css-gamut-mapping) for RGB
// colors that are out of gamut for a particular RGB color space. It implements a relative colorimetric intent mapping
// for colors that are outside the destionation gamut and leaves colors inside the destination gamut unchanged.
// A simple optimization over the psuedocode in the specification has been made to avoid unnecessary work in the
// main bisection loop by checking the gamut using the extended linear color space of the RGB family regardless of
// of whether the final type is gamma encoded or not. This avoids unnecessary gamma encoding for non final loops.
// FIXME: This is a naive iterative solution that works for any bounded RGB color space. This can be optimized by
// using an analytical solution that computes the exact intersection.
static constexpr float JND = 0.02f;
template<typename ColorType> static auto mapToBoundedGamut(const ColorType& color) -> typename ColorType::BoundedCounterpart
{
using BoundedColorType = typename ColorType::BoundedCounterpart;
using ExtendedLinearColorType = ExtendedLinearEncoded<typename BoundedColorType::ComponentType, typename BoundedColorType::Descriptor>;
using BoundedLinearColorType = BoundedLinearEncoded<typename BoundedColorType::ComponentType, typename BoundedColorType::Descriptor>;
auto resolvedColor = color.resolved();
if (auto result = colorIfInGamut<BoundedColorType>(resolvedColor))
return *result;
auto colorInOKLCHColorSpace = convertColor<OKLCHA<float>>(resolvedColor).resolved();
if (WTF::areEssentiallyEqual(colorInOKLCHColorSpace.lightness, 100.0f) || colorInOKLCHColorSpace.lightness > 100.0f)
return { 1.0, 1.0, 1.0, resolvedColor.alpha };
else if (WTF::areEssentiallyEqual(colorInOKLCHColorSpace.lightness, 0.0f))
return { 0.0f, 0.0f, 0.0f, resolvedColor.alpha };
float min = 0.0f;
float max = colorInOKLCHColorSpace.chroma;
while (true) {
auto chroma = (min + max) / 2.0f;
auto current = colorInOKLCHColorSpace;
current.chroma = chroma;
auto currentInExtendedLinearColorSpace = convertColor<ExtendedLinearColorType>(current).resolved();
if (inGamut<BoundedLinearColorType>(currentInExtendedLinearColorSpace)) {
min = chroma;
continue;
}
auto currentClippedToBoundedLinearColorSpace = clipToGamut<BoundedLinearColorType>(currentInExtendedLinearColorSpace);
auto deltaE = computeDeltaEOK(currentClippedToBoundedLinearColorSpace, current);
if (deltaE < JND)
return convertColor<BoundedColorType>(currentClippedToBoundedLinearColorSpace);
max = chroma;
}
}
};
// Main conversion.
// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
// Matrix Conversions ┌───────────┐│┌───────────┐
// │ │ XYZ (D50) │││ XYZ (D65) │ │
// └─────▲─────┘│└─────▲─────┘
// │ │ │ │ │
// ┌─────────────────────────┬───────────┘ │ └───────────┬───────────────────────────────┬───────────────────────────────┬───────────────────────────────┬─────────────────────────┐
// │ │ │ │ │ │ │ │ │ │
// │ │ │ │ │ │ │ │
// │ │ │ │ │ │ │ │ │ │
// │ ProPhotoRGB───────────────────┐ │ SRGB──────────────────────────┐ DisplayP3─────────────────────┐ A98RGB────────────────────────┐ Rec2020───────────────────────┐ │
// │ │ │ ┌────────────────┐│ │ │ ┌────────────────┐│ │ ┌────────────────┐│ │ ┌────────────────┐│ │ ┌────────────────┐│ │ │
// │ │ ┌─────▶︎ LinearExtended ││ │ │ ┌─────▶︎ LinearExtended ││ │ ┌─────▶︎ LinearExtended ││ │ ┌─────▶︎ LinearExtended ││ │ ┌─────▶︎ LinearExtended ││ │
// │ │ │ │ └────────▲───────┘│ │ │ │ └────────▲───────┘│ │ │ └────────▲───────┘│ │ │ └────────▲───────┘│ │ │ └────────▲───────┘│ │ │
// │ ─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─ ─│─ ─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─ │
// │ │┌────────┐ │ │ │ │┌────────┐ │ │ │┌────────┐ │ │ │┌────────┐ │ │ │┌────────┐ │ │ │
// │ ││ Linear │ │ │ │ ││ Linear │ │ │ ││ Linear │ │ │ ││ Linear │ │ │ ││ Linear │ │ │ │
// │ │└────▲───┘ │ │ │ │└────▲───┘ │ │ │└────▲───┘ │ │ │└────▲───┘ │ │ │└────▲───┘ │ │ │
// │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
// ┌───────────┐ │┌────────┐ ┌────────────────┐│ │ │┌────────┐ ┌────────────────┐│ │┌────────┐ ┌────────────────┐│ │┌────────┐ ┌────────────────┐│ │┌────────┐ ┌────────────────┐│ ┌───────────┐
// │ Lab │ ││ Gamma │─│ GammaExtended ││ │ ││ Gamma │─│ GammaExtended ││ ││ Gamma │─│ GammaExtended ││ ││ Gamma │─│ GammaExtended ││ ││ Gamma │─│ GammaExtended ││ │ OKLab │
// └─────▲─────┘ │└────────┘ └────────────────┘│ │ │└────▲───┘ └────────────────┘│ │└────────┘ └────────────────┘│ │└────────┘ └────────────────┘│ │└────────┘ └────────────────┘│ └─────▲─────┘
// │ └─────────────────────────────┘ │ └─────┼───────────────────────┘ └─────────────────────────────┘ └─────────────────────────────┘ └─────────────────────────────┘ │
// │ │ ┌──┴──────────┐ │
// │ │ │ │ │
// ┌───────────┐ │┌───────────┐ ┌───────────┐ ┌───────────┐
// │ LCH │ ││ HSL │ │ HWB │ │ OKLCH │
// └───────────┘ │└───────────┘ └───────────┘ └───────────┘
template<typename Output, typename Input, typename> struct ColorConversion {
public:
static constexpr Output convert(const Input& color)
{
// 1. Handle the special case of Input or Output with a uint8_t component type.
if constexpr (std::is_same_v<typename Input::ComponentType, uint8_t>)
return handleToFloatConversion(color);
else if constexpr (std::is_same_v<typename Output::ComponentType, uint8_t>)
return handleToByteConversion(color);
// 2. Handle all color types that are not IsRGBType<T> or IsXYZA<T> for Input and Output. For all
// these other color types, we can uncondtionally convert them to their "reference" color, as
// either they have already been handled by a ColorConversion specialization or this will
// get us closer to the final conversion.
else if constexpr (!IsRGBType<Input> && !IsXYZA<Input>)
return convertColor<Output>(convertColor<typename Input::Reference>(color));
else if constexpr (!IsRGBType<Output> && !IsXYZA<Output>)
return convertColor<Output>(convertColor<typename Output::Reference>(color));
// 3. Handle conversions within a RGBFamily (e.g. all have the same descriptor).
else if constexpr (IsSameRGBTypeFamily<Output, Input>)
return handleRGBFamilyConversion(color);
// 4. Handle any gamma conversions for the Input and Output.
else if constexpr (IsRGBGammaEncodedType<Input>)
return convertColor<Output>(convertColor<typename Input::LinearCounterpart>(color));
else if constexpr (IsRGBGammaEncodedType<Output>)
return convertColor<Output>(convertColor<typename Output::LinearCounterpart>(color));
// 5. Handle any bounds conversions for the Input and Output.
else if constexpr (IsRGBBoundedType<Input>)
return convertColor<Output>(convertColor<typename Input::ExtendedCounterpart>(color));
else if constexpr (IsRGBBoundedType<Output>)
return convertColor<Output>(convertColor<typename Output::ExtendedCounterpart>(color));
// 6. At this point, Input and Output are each either ExtendedLinear-RGB types (of different familes) or XYZA
// and therefore all additional conversion can happen via matrix transformation.
else
return handleMatrixConversion(color);
}
private:
static inline constexpr Output handleToFloatConversion(const Input& color)
{
static_assert(IsRGBBoundedType<Input>, "Only bounded ([0..1]) RGB color types support conversion to/from bytes.");
using InputWithReplacement = ColorTypeWithReplacementComponent<Input, float>;
if constexpr (std::is_same_v<InputWithReplacement, Output>)
return makeFromComponents<InputWithReplacement>(asColorComponents(color.resolved()).map([](uint8_t value) -> float { return value / 255.0f; }));
else
return convertColor<Output>(convertColor<InputWithReplacement>(color));
}
static inline constexpr Output handleToByteConversion(const Input& color)
{
static_assert(IsRGBBoundedType<Output>, "Only bounded ([0..1]) RGB color types support conversion to/from bytes.");
using OutputWithReplacement = ColorTypeWithReplacementComponent<Output, float>;
if constexpr (std::is_same_v<OutputWithReplacement, Input>)
return makeFromComponents<Output>(asColorComponents(color.resolved()).map([](float value) -> uint8_t { return std::clamp(std::lround(value * 255.0f), 0l, 255l); }));
else
return convertColor<Output>(convertColor<OutputWithReplacement>(color));
}
template<typename ColorType> static inline constexpr auto toLinearEncoded(const ColorType& color) -> typename ColorType::LinearCounterpart
{
auto [c1, c2, c3, alpha] = color.resolved();
return { ColorType::TransferFunction::toLinear(c1), ColorType::TransferFunction::toLinear(c2), ColorType::TransferFunction::toLinear(c3), alpha };
}
template<typename ColorType> static inline constexpr auto toGammaEncoded(const ColorType& color) -> typename ColorType::GammaEncodedCounterpart
{
auto [c1, c2, c3, alpha] = color.resolved();
return { ColorType::TransferFunction::toGammaEncoded(c1), ColorType::TransferFunction::toGammaEncoded(c2), ColorType::TransferFunction::toGammaEncoded(c3), alpha };
}
template<typename ColorType> static inline constexpr auto toExtended(const ColorType& color) -> typename ColorType::ExtendedCounterpart
{
return makeFromComponents<typename ColorType::ExtendedCounterpart>(asColorComponents(color.resolved()));
}
template<typename ColorType> static inline constexpr auto toBounded(const ColorType& color) -> typename ColorType::BoundedCounterpart
{
return CSSGamutMapping::mapToBoundedGamut(color);
}
static inline constexpr Output handleRGBFamilyConversion(const Input& color)
{
static_assert(IsSameRGBTypeFamily<Output, Input>);
// RGB Family────────────────────┐
// │ ┌────────────────┐│
// │ ┌─────▶︎ LinearExtended ││
// │ │ └────────▲───────┘│
// │ │ │ │
// │┌────────┐ │ │
// ││ Linear │ │ │
// │└────▲───┘ │ │
// │ │ │ │
// │┌────────┐ ┌────────────────┐│
// ││ Gamma │─│ GammaExtended ││
// │└────────┘ └────────────────┘│
// └─────────────────────────────┘
// This handles conversions between any two of these within the same family, so SRGBLinear -> SRGB, but not
// SRGBLinear -> A98RGB.
auto boundsConversion = [](auto color) {
if constexpr (IsRGBExtendedType<Output> && IsRGBBoundedType<Input>)
return toExtended(color);
else if constexpr (IsRGBBoundedType<Output> && IsRGBExtendedType<Input>)
return toBounded(color);
else
return color;
};
auto gammaConversion = [](auto color) {
if constexpr (IsRGBGammaEncodedType<Output> && IsRGBLinearEncodedType<Input>)
return toGammaEncoded(color);
else if constexpr (IsRGBLinearEncodedType<Output> && IsRGBGammaEncodedType<Input>)
return toLinearEncoded(color);
else
return color;
};
return boundsConversion(gammaConversion(color));
}
static inline constexpr Output handleMatrixConversion(const Input& color)
{
static_assert((IsRGBLinearEncodedType<Input> && IsRGBExtendedType<Input>) || IsXYZA<Input>);
static_assert((IsRGBLinearEncodedType<Output> && IsRGBExtendedType<Output>) || IsXYZA<Output>);
// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
// Matrix Conversions ┌───────────┐│┌───────────┐
// │ │ XYZ (D50) │││ XYZ (D65) │ │
// └─────▲─────┘│└─────▲─────┘
// │ │ │ │ │
// ┌───────────┘ │ └───────────┬───────────────────────────────┬───────────────────────────────┬───────────────────────────────┐
// │ │ │ │ │ │ │ │
// │ │ │ │ │ │
// │ │ │ │ │ │ │ │
// ProPhotoRGB───────────────────┐ │ SRGB──────────────────────────┐ DisplayP3─────────────────────┐ A98RGB────────────────────────┐ Rec2020───────────────────────┐
// │ │ ┌────────────────┐│ │ │ ┌────────────────┐│ │ ┌────────────────┐│ │ ┌────────────────┐│ │ ┌────────────────┐│ │
// │ ┌─────▶︎ LinearExtended ││ │ │ ┌─────▶︎ LinearExtended ││ │ ┌─────▶︎ LinearExtended ││ │ ┌─────▶︎ LinearExtended ││ │ ┌─────▶︎ LinearExtended ││
// │ │ │ └────────▲───────┘│ │ │ │ └────────▲───────┘│ │ │ └────────▲───────┘│ │ │ └────────▲───────┘│ │ │ └────────▲───────┘│ │
// ─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─ ─│─ ─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─│─ ─ ─│─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─│─
// This handles conversions between extended linear color types that can be converted using pre-defined
// 3x3 matrices.
// FIXME: Pre-compute (using constexpr) the concatenation of the matrices prior to applying them
// to reduce number of matrix multiplications to a minimum. This will likely give subtly different
// results (due to floating point effects) so if this optimization is considered we should ensure we
// have sufficient testing coverage to notice any adverse effects.
auto applyMatrices = [](const Input& color, auto... matrices) {
return makeFromComponents<Output>(applyMatricesToColorComponents(asColorComponents(color.resolved()), matrices...));
};
if constexpr (Input::whitePoint == Output::whitePoint) {
if constexpr (IsXYZA<Input>)
return applyMatrices(color, Output::xyzToLinear);
else if constexpr (IsXYZA<Output>)
return applyMatrices(color, Input::linearToXYZ);
else
return applyMatrices(color, Input::linearToXYZ, Output::xyzToLinear);
} else {
if constexpr (IsXYZA<Input> && IsXYZA<Output>)
return applyMatrices(color, ChromaticAdapation<Input::whitePoint, Output::whitePoint>::matrix);
else if constexpr (IsXYZA<Input>)
return applyMatrices(color, ChromaticAdapation<Input::whitePoint, Output::whitePoint>::matrix, Output::xyzToLinear);
else if constexpr (IsXYZA<Output>)
return applyMatrices(color, Input::linearToXYZ, ChromaticAdapation<Input::whitePoint, Output::whitePoint>::matrix);
else
return applyMatrices(color, Input::linearToXYZ, ChromaticAdapation<Input::whitePoint, Output::whitePoint>::matrix, Output::xyzToLinear);
}
}
};
} // namespace WebCore