aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar reed <reed@chromium.org>2016-02-15 14:26:14 -0800
committerGravatar Commit bot <commit-bot@chromium.org>2016-02-15 14:26:14 -0800
commit66a6589478d379ef301130d6134a4f20db6c5e48 (patch)
tree430266895babc837c2008dc559d68bd8149cb7d1 /src
parentb30d6984e6dfc185bf1eebf927343563057a7bb3 (diff)
starter kit for colorspaces
Diffstat (limited to 'src')
-rw-r--r--src/core/SkColorSpace.cpp244
-rw-r--r--src/core/SkColorSpace.h87
2 files changed, 331 insertions, 0 deletions
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<SkColorSpace> cs0(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named));
+ SkAutoTUnref<SkColorSpace> 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