From 66a6589478d379ef301130d6134a4f20db6c5e48 Mon Sep 17 00:00:00 2001 From: reed Date: Mon, 15 Feb 2016 14:26:14 -0800 Subject: starter kit for colorspaces BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1692893003 TBR=jvanverth Review URL: https://codereview.chromium.org/1695353002 --- src/core/SkColorSpace.cpp | 244 ++++++++++++++++++++++++++++++++++++++++++++++ src/core/SkColorSpace.h | 87 +++++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 src/core/SkColorSpace.cpp create mode 100644 src/core/SkColorSpace.h (limited to 'src') diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp new file mode 100644 index 0000000000..bbb03c640a --- /dev/null +++ b/src/core/SkColorSpace.cpp @@ -0,0 +1,244 @@ +/* + * 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 "SkAtomics.h" +#include "SkColorSpace.h" + +static inline bool SkFloatIsFinite(float x) { return 0 == x * 0; } + +// +// SkFloat3x3 +// +// In memory order, values are a, b, c, d, e, f, g, h, i +// +// When applied to a color component vector (e.g. [ r, r, r ] or [ g, g, g ] we do +// +// [ r r r ] * [ a b c ] + [ g g g ] * [ d e f ] + [ b b b ] * [ g h i ] +// +// Thus in our point-on-the-right notation, the matrix looks like +// +// [ a d g ] [ r ] +// [ b e h ] * [ g ] +// [ c f i ] [ b ] +// +static SkFloat3x3 concat(const SkFloat3x3& left, const SkFloat3x3& rite) { + SkFloat3x3 result; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + double tmp = 0; + for (int i = 0; i < 3; ++i) { + tmp += (double)left.fMat[row + i * 3] * rite.fMat[i + col * 3]; + } + result.fMat[row + col * 3] = (double)tmp; + } + } + return result; +} + +static double det(const SkFloat3x3& m) { + return (double)m.fMat[0] * m.fMat[4] * m.fMat[8] + + (double)m.fMat[3] * m.fMat[7] * m.fMat[2] + + (double)m.fMat[6] * m.fMat[1] * m.fMat[5] - + (double)m.fMat[0] * m.fMat[7] * m.fMat[5] - + (double)m.fMat[3] * m.fMat[1] * m.fMat[8] - + (double)m.fMat[6] * m.fMat[4] * m.fMat[2]; +} + +static double det2x2(const SkFloat3x3& m, int a, int b, int c, int d) { + return (double)m.fMat[a] * m.fMat[b] - (double)m.fMat[c] * m.fMat[d]; +} + +static SkFloat3x3 invert(const SkFloat3x3& m) { + double d = det(m); + SkASSERT(SkFloatIsFinite((float)d)); + double scale = 1 / d; + SkASSERT(SkFloatIsFinite((float)scale)); + + return {{ + (float)(scale * det2x2(m, 4, 8, 5, 7)), + (float)(scale * det2x2(m, 7, 2, 8, 1)), + (float)(scale * det2x2(m, 1, 5, 2, 4)), + + (float)(scale * det2x2(m, 6, 5, 8, 3)), + (float)(scale * det2x2(m, 0, 8, 2, 6)), + (float)(scale * det2x2(m, 3, 2, 5, 0)), + + (float)(scale * det2x2(m, 3, 7, 4, 6)), + (float)(scale * det2x2(m, 6, 1, 7, 0)), + (float)(scale * det2x2(m, 0, 4, 1, 3)), + }}; +} + +void SkFloat3::dump() const { + SkDebugf("[%7.4f %7.4f %7.4f]\n", fVec[0], fVec[1], fVec[2]); +} + +void SkFloat3x3::dump() const { + SkDebugf("[%7.4f %7.4f %7.4f] [%7.4f %7.4f %7.4f] [%7.4f %7.4f %7.4f]\n", + fMat[0], fMat[1], fMat[2], + fMat[3], fMat[4], fMat[5], + fMat[6], fMat[7], fMat[8]); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +static int32_t gUniqueColorSpaceID; + +SkColorSpace::SkColorSpace(const SkFloat3x3& toXYZD50, const SkFloat3& gamma, Named named) + : fToXYZD50(toXYZD50) + , fGamma(gamma) + , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID)) + , fNamed(named) +{ + for (int i = 0; i < 3; ++i) { + SkASSERT(SkFloatIsFinite(gamma.fVec[i])); + for (int j = 0; j < 3; ++j) { + SkASSERT(SkFloatIsFinite(toXYZD50.fMat[3*i + j])); + } + } +} + +SkColorSpace* SkColorSpace::NewRGB(const SkFloat3x3& toXYZD50, const SkFloat3& gamma) { + for (int i = 0; i < 3; ++i) { + if (!SkFloatIsFinite(gamma.fVec[i]) || gamma.fVec[i] < 0) { + return nullptr; + } + for (int j = 0; j < 3; ++j) { + if (!SkFloatIsFinite(toXYZD50.fMat[3*i + j])) { + return nullptr; + } + } + } + + // check the matrix for invertibility + float d = det(toXYZD50); + if (!SkFloatIsFinite(d) || !SkFloatIsFinite(1 / d)) { + return nullptr; + } + + return new SkColorSpace(toXYZD50, gamma, kUnknown_Named); +} + +void SkColorSpace::dump() const { + fToXYZD50.dump(); + fGamma.dump(); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +const SkFloat3 gDevice_gamma {{ 0, 0, 0 }}; +const SkFloat3x3 gDevice_toXYZD50 {{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 +}}; + +const SkFloat3 gSRGB_gamma {{ 2.2f, 2.2f, 2.2f }}; +const SkFloat3x3 gSRGB_toXYZD50 {{ + 0.4358f, 0.2224f, 0.0139f, // * R + 0.3853f, 0.7170f, 0.0971f, // * G + 0.1430f, 0.0606f, 0.7139f, // * B +}}; + +SkColorSpace* SkColorSpace::NewNamed(Named named) { + switch (named) { + case kDevice_Named: + return new SkColorSpace(gDevice_toXYZD50, gDevice_gamma, kDevice_Named); + case kSRGB_Named: + return new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named); + default: + break; + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColorSpace* dst, + SkFloat3x3* result) { + if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst->named())) { + if (result) { + *result = {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }}; + } + return kIdentity_Result; + } + if (result) { + *result = concat(src->fToXYZD50, invert(dst->fToXYZD50)); + } + return kNormal_Result; +} + +#include "SkColor.h" +#include "SkNx.h" + +void SkApply3x3ToPM4f(const SkFloat3x3& m, const SkPM4f src[], SkPM4f dst[], int count) { + SkASSERT(1 == SkPM4f::G); + SkASSERT(3 == SkPM4f::A); + + Sk4f cr, cg, cb; + cg = Sk4f::Load(m.fMat + 3); + if (0 == SkPM4f::R) { + SkASSERT(2 == SkPM4f::B); + cr = Sk4f::Load(m.fMat + 0); + cb = Sk4f(m.fMat[6], m.fMat[7], m.fMat[8], 0); + } else { + SkASSERT(0 == SkPM4f::B); + SkASSERT(2 == SkPM4f::R); + cb = Sk4f::Load(m.fMat + 0); + cr = Sk4f(m.fMat[6], m.fMat[7], m.fMat[8], 0); + } + cr = cr * Sk4f(1, 1, 1, 0); + cg = cg * Sk4f(1, 1, 1, 0); + cb = cb * Sk4f(1, 1, 1, 0); + + for (int i = 0; i < count; ++i) { + Sk4f r = Sk4f(src[i].fVec[SkPM4f::R]); + Sk4f g = Sk4f(src[i].fVec[SkPM4f::G]); + Sk4f b = Sk4f(src[i].fVec[SkPM4f::B]); + Sk4f a = Sk4f(0, 0, 0, src[i].fVec[SkPM4f::A]); + (cr * r + cg * g + cb * b + a).store(&dst[i]); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkColorSpace::Test() { + SkFloat3x3 mat {{ 2, 0, 0, 0, 3, 0, 0, 0, 4 }}; + SkFloat3x3 inv = invert(mat); + mat.dump(); + inv.dump(); + concat(mat, inv).dump(); + concat(inv, mat).dump(); + SkDebugf("\n"); + + mat = gSRGB_toXYZD50; + inv = invert(mat); + mat.dump(); + inv.dump(); + concat(mat, inv).dump(); + concat(inv, mat).dump(); + SkDebugf("\n"); + + SkAutoTUnref cs0(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named)); + SkAutoTUnref cs1(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named)); + + cs0->dump(); + cs1->dump(); + SkFloat3x3 xform; + (void)SkColorSpace::Concat(cs0, cs1, &xform); + xform.dump(); + SkDebugf("\n"); +} + +// D65 white point of Rec. 709 [8] are: +// +// D65 white-point in unit luminance XYZ = 0.9505, 1.0000, 1.0890 +// +// R G B white +// x 0.640 0.300 0.150 0.3127 +// y 0.330 0.600 0.060 0.3290 +// z 0.030 0.100 0.790 0.3582 diff --git a/src/core/SkColorSpace.h b/src/core/SkColorSpace.h new file mode 100644 index 0000000000..7e81b5f335 --- /dev/null +++ b/src/core/SkColorSpace.h @@ -0,0 +1,87 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorSpace_DEFINED +#define SkColorSpace_DEFINED + +// Some terms +// +// PCS : Profile Connection Space : where color number values have an absolute meaning. +// Part of the work float is to convert colors to and from this space... +// src_linear_unit_floats --> PCS --> PCS' --> dst_linear_unit_floats +// +// Some nice documents +// +// http://www.cambridgeincolour.com/tutorials/color-space-conversion.htm +// https://www.w3.org/Graphics/Color/srgb +// http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html +// + +#include "SkRefCnt.h" + +struct SkFloat3 { + float fVec[3]; + + void dump() const; +}; + +struct SkFloat3x3 { + float fMat[9]; + + void dump() const; +}; + +struct SkPM4f; +void SkApply3x3ToPM4f(const SkFloat3x3&, const SkPM4f src[], SkPM4f dst[], int count); + +class SkColorSpace : public SkRefCnt { +public: + enum Named { + kUnknown_Named, + kDevice_Named, + kSRGB_Named, + }; + + /** + * Return a colorspace instance, given a 3x3 transform from linear_RGB to D50_XYZ + * and the src-gamma, return a ColorSpace + */ + static SkColorSpace* NewRGB(const SkFloat3x3& toXYZD50, const SkFloat3& gamma); + + static SkColorSpace* NewNamed(Named); + static SkColorSpace* NewICC(const void*, size_t); + + SkFloat3 gamma() const { return fGamma; } + Named named() const { return fNamed; } + uint32_t uniqueID() const { return fUniqueID; } + + enum Result { + kFailure_Result, + kIdentity_Result, + kNormal_Result, + }; + + /** + * Given a src and dst colorspace, return the 3x3 matrix that will convert src_linear_RGB + * values into dst_linear_RGB values. + */ + static Result Concat(const SkColorSpace* src, const SkColorSpace* dst, SkFloat3x3* result); + + static void Test(); + void dump() const; + +protected: + SkColorSpace(const SkFloat3x3& toXYZ, const SkFloat3& gamma, Named); + +private: + const SkFloat3x3 fToXYZD50; + const SkFloat3 fGamma; + const uint32_t fUniqueID; + const Named fNamed; +}; + +#endif -- cgit v1.2.3