diff options
-rw-r--r-- | include/core/SkColorSpace.h | 21 | ||||
-rw-r--r-- | src/codec/SkPngCodec.cpp | 85 | ||||
-rw-r--r-- | src/core/SkColorSpace.cpp | 76 | ||||
-rw-r--r-- | tests/ColorSpaceTest.cpp | 20 |
4 files changed, 129 insertions, 73 deletions
diff --git a/include/core/SkColorSpace.h b/include/core/SkColorSpace.h index a96f622095..9b990da966 100644 --- a/include/core/SkColorSpace.h +++ b/include/core/SkColorSpace.h @@ -13,6 +13,22 @@ class SkData; +/** + * Describes a color gamut with primaries and a white point. + */ +struct SK_API SkColorSpacePrimaries { + float fRX, fRY; + float fGX, fGY; + float fBX, fBY; + float fWX, fWY; + + /** + * Convert primaries and a white point to a toXYZD50 matrix, the preferred color gamut + * representation of SkColorSpace. + */ + bool toXYZD50(SkMatrix44* toXYZD50) const; +}; + class SK_API SkColorSpace : public SkRefCnt { public: @@ -50,7 +66,10 @@ public: }; /** - * Create an SkColorSpace from a transfer function and a color gamut transform to D50 XYZ. + * Create an SkColorSpace from a transfer function and a color gamut. + * + * Transfer function is specified as linear or sRGB. + * Gamut is specified using the matrix transformation to XYZ D50. */ static sk_sp<SkColorSpace> NewRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50); diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp index dde49d037d..55a29604a9 100644 --- a/src/codec/SkPngCodec.cpp +++ b/src/codec/SkPngCodec.cpp @@ -322,63 +322,6 @@ static constexpr float gSRGB_toXYZD50[] { 0.0139f, 0.0971f, 0.7139f, // Rz, Gz, Bz }; -static bool convert_to_D50(SkMatrix44* toXYZD50, float toXYZ[9], float whitePoint[2]) { - float wX = whitePoint[0]; - float wY = whitePoint[1]; - if (wX < 0.0f || wY < 0.0f || (wX + wY > 1.0f)) { - return false; - } - - // Calculate the XYZ illuminant. Call this the src illuminant. - float wZ = 1.0f - wX - wY; - float scale = 1.0f / wY; - // TODO (msarett): - // What are common src illuminants? I'm guessing we will almost always see D65. Should - // we go ahead and save a precomputed D65->D50 Bradford matrix? Should we exit early if - // if the src illuminant is D50? - SkVector3 srcXYZ = SkVector3::Make(wX * scale, 1.0f, wZ * scale); - - // The D50 illuminant. - SkVector3 dstXYZ = SkVector3::Make(0.96422f, 1.0f, 0.82521f); - - // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus - // the matrices below. The Bradford method is used by Adobe and is widely considered - // to be the best. - // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html - SkMatrix mA, mAInv; - mA.setAll(0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, 0.0367f, 0.0389f, -0.0685f, 1.0296f); - mAInv.setAll(0.9869929f, -0.1470543f, 0.1599627f, 0.4323053f, 0.5183603f, 0.0492912f, - -0.0085287f, 0.0400428f, 0.9684867f); - - // Map illuminant into cone response domain. - SkVector3 srcCone; - srcCone.fX = mA[0] * srcXYZ.fX + mA[1] * srcXYZ.fY + mA[2] * srcXYZ.fZ; - srcCone.fY = mA[3] * srcXYZ.fX + mA[4] * srcXYZ.fY + mA[5] * srcXYZ.fZ; - srcCone.fZ = mA[6] * srcXYZ.fX + mA[7] * srcXYZ.fY + mA[8] * srcXYZ.fZ; - SkVector3 dstCone; - dstCone.fX = mA[0] * dstXYZ.fX + mA[1] * dstXYZ.fY + mA[2] * dstXYZ.fZ; - dstCone.fY = mA[3] * dstXYZ.fX + mA[4] * dstXYZ.fY + mA[5] * dstXYZ.fZ; - dstCone.fZ = mA[6] * dstXYZ.fX + mA[7] * dstXYZ.fY + mA[8] * dstXYZ.fZ; - - SkMatrix DXToD50; - DXToD50.setIdentity(); - DXToD50[0] = dstCone.fX / srcCone.fX; - DXToD50[4] = dstCone.fY / srcCone.fY; - DXToD50[8] = dstCone.fZ / srcCone.fZ; - DXToD50.postConcat(mAInv); - DXToD50.preConcat(mA); - - SkMatrix toXYZ3x3; - toXYZ3x3.setAll(toXYZ[0], toXYZ[3], toXYZ[6], toXYZ[1], toXYZ[4], toXYZ[7], toXYZ[2], toXYZ[5], - toXYZ[8]); - toXYZ3x3.postConcat(DXToD50); - - toXYZD50->set3x3(toXYZ3x3[0], toXYZ3x3[3], toXYZ3x3[6], - toXYZ3x3[1], toXYZ3x3[4], toXYZ3x3[7], - toXYZ3x3[2], toXYZ3x3[5], toXYZ3x3[8]); - return true; -} - #endif // LIBPNG >= 1.6 // Returns a colorSpace object that represents any color space information in @@ -416,26 +359,24 @@ sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) { } // Next, check for chromaticities. - png_fixed_point toXYZFixed[9]; - float toXYZ[9]; - png_fixed_point whitePointFixed[2]; - float whitePoint[2]; + png_fixed_point chrm[8]; png_fixed_point gamma; float gammas[3]; - if (png_get_cHRM_XYZ_fixed(png_ptr, info_ptr, &toXYZFixed[0], &toXYZFixed[1], &toXYZFixed[2], - &toXYZFixed[3], &toXYZFixed[4], &toXYZFixed[5], &toXYZFixed[6], - &toXYZFixed[7], &toXYZFixed[8]) && - png_get_cHRM_fixed(png_ptr, info_ptr, &whitePointFixed[0], &whitePointFixed[1], nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr)) + if (png_get_cHRM_fixed(png_ptr, info_ptr, &chrm[0], &chrm[1], &chrm[2], &chrm[3], &chrm[4], + &chrm[5], &chrm[6], &chrm[7])) { - for (int i = 0; i < 9; i++) { - toXYZ[i] = png_fixed_point_to_float(toXYZFixed[i]); - } - whitePoint[0] = png_fixed_point_to_float(whitePointFixed[0]); - whitePoint[1] = png_fixed_point_to_float(whitePointFixed[1]); + SkColorSpacePrimaries primaries; + primaries.fRX = png_fixed_point_to_float(chrm[2]); + primaries.fRY = png_fixed_point_to_float(chrm[3]); + primaries.fGX = png_fixed_point_to_float(chrm[4]); + primaries.fGY = png_fixed_point_to_float(chrm[5]); + primaries.fBX = png_fixed_point_to_float(chrm[6]); + primaries.fBY = png_fixed_point_to_float(chrm[7]); + primaries.fWX = png_fixed_point_to_float(chrm[0]); + primaries.fWY = png_fixed_point_to_float(chrm[1]); SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor); - if (!convert_to_D50(&toXYZD50, toXYZ, whitePoint)) { + if (!primaries.toXYZD50(&toXYZD50)) { toXYZD50.set3x3RowMajorf(gSRGB_toXYZD50); } diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp index c6bf4b9431..66b980b8f9 100644 --- a/src/core/SkColorSpace.cpp +++ b/src/core/SkColorSpace.cpp @@ -9,6 +9,82 @@ #include "SkColorSpace_Base.h" #include "SkColorSpacePriv.h" #include "SkOnce.h" +#include "SkPoint3.h" + +static inline bool is_zero_to_one(float v) { + return (0.0f <= v) && (v <= 1.0f); +} + +bool SkColorSpacePrimaries::toXYZD50(SkMatrix44* toXYZ_D50) const { + if (!is_zero_to_one(fRX) || !is_zero_to_one(fRY) || + !is_zero_to_one(fGX) || !is_zero_to_one(fGY) || + !is_zero_to_one(fBX) || !is_zero_to_one(fBY) || + !is_zero_to_one(fWX) || !is_zero_to_one(fWY)) + { + return false; + } + + // First, we need to convert xy values (primaries) to XYZ. + SkMatrix primaries; + primaries.setAll( fRX, fGX, fBX, + fRY, fGY, fBY, + 1.0f - fRX - fRY, 1.0f - fGX - fGY, 1.0f - fBX - fBY); + SkMatrix primariesInv; + if (!primaries.invert(&primariesInv)) { + return false; + } + + // Assumes that Y is 1.0f. + SkVector3 wXYZ = SkVector3::Make(fWX / fWY, 1.0f, (1.0f - fWX - fWY) / fWY); + SkVector3 XYZ; + XYZ.fX = primariesInv[0] * wXYZ.fX + primariesInv[1] * wXYZ.fY + primariesInv[2] * wXYZ.fZ; + XYZ.fY = primariesInv[3] * wXYZ.fX + primariesInv[4] * wXYZ.fY + primariesInv[5] * wXYZ.fZ; + XYZ.fZ = primariesInv[6] * wXYZ.fX + primariesInv[7] * wXYZ.fY + primariesInv[8] * wXYZ.fZ; + SkMatrix toXYZ; + toXYZ.setAll(XYZ.fX, 0.0f, 0.0f, + 0.0f, XYZ.fY, 0.0f, + 0.0f, 0.0f, XYZ.fZ); + toXYZ.postConcat(primaries); + + // Now convert toXYZ matrix to toXYZD50. + SkVector3 wXYZD50 = SkVector3::Make(0.96422f, 1.0f, 0.82521f); + + // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus + // the matrices below. The Bradford method is used by Adobe and is widely considered + // to be the best. + SkMatrix mA, mAInv; + mA.setAll(+0.8951f, +0.2664f, -0.1614f, + -0.7502f, +1.7135f, +0.0367f, + +0.0389f, -0.0685f, +1.0296f); + mAInv.setAll(+0.9869929f, -0.1470543f, +0.1599627f, + +0.4323053f, +0.5183603f, +0.0492912f, + -0.0085287f, +0.0400428f, +0.9684867f); + + SkVector3 srcCone; + srcCone.fX = mA[0] * wXYZ.fX + mA[1] * wXYZ.fY + mA[2] * wXYZ.fZ; + srcCone.fY = mA[3] * wXYZ.fX + mA[4] * wXYZ.fY + mA[5] * wXYZ.fZ; + srcCone.fZ = mA[6] * wXYZ.fX + mA[7] * wXYZ.fY + mA[8] * wXYZ.fZ; + SkVector3 dstCone; + dstCone.fX = mA[0] * wXYZD50.fX + mA[1] * wXYZD50.fY + mA[2] * wXYZD50.fZ; + dstCone.fY = mA[3] * wXYZD50.fX + mA[4] * wXYZD50.fY + mA[5] * wXYZD50.fZ; + dstCone.fZ = mA[6] * wXYZD50.fX + mA[7] * wXYZD50.fY + mA[8] * wXYZD50.fZ; + + SkMatrix DXToD50; + DXToD50.setIdentity(); + DXToD50[0] = dstCone.fX / srcCone.fX; + DXToD50[4] = dstCone.fY / srcCone.fY; + DXToD50[8] = dstCone.fZ / srcCone.fZ; + DXToD50.postConcat(mAInv); + DXToD50.preConcat(mA); + + toXYZ.postConcat(DXToD50); + toXYZ_D50->set3x3(toXYZ[0], toXYZ[3], toXYZ[6], + toXYZ[1], toXYZ[4], toXYZ[7], + toXYZ[2], toXYZ[5], toXYZ[8]); + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// SkColorSpace_Base::SkColorSpace_Base(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50) : fGammaNamed(gammaNamed) diff --git a/tests/ColorSpaceTest.cpp b/tests/ColorSpaceTest.cpp index a6ed9e3cf9..a4eab92f5d 100644 --- a/tests/ColorSpaceTest.cpp +++ b/tests/ColorSpaceTest.cpp @@ -271,3 +271,23 @@ DEF_TEST(ColorSpace_Equals, r) { REPORTER_ASSERT(r, !SkColorSpace::Equals(upperRight.get(), adobe.get())); REPORTER_ASSERT(r, !SkColorSpace::Equals(rgb1.get(), rgb2.get())); } + +DEF_TEST(ColorSpace_Primaries, r) { + // sRGB primaries + SkColorSpacePrimaries primaries; + primaries.fRX = 0.64f; + primaries.fRY = 0.33f; + primaries.fGX = 0.30f; + primaries.fGY = 0.60f; + primaries.fBX = 0.15f; + primaries.fBY = 0.06f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + + SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); + bool result = primaries.toXYZD50(&toXYZ); + REPORTER_ASSERT(r, result); + + sk_sp<SkColorSpace> space = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma, toXYZ); + REPORTER_ASSERT(r, SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named) == space); +} |