From: raftias Date: Tue, 18 Oct 2016 17:02:51 +0000 (-0700) Subject: Refactored SkColorSpace and added in a Lab PCS GM X-Git-Tag: submit/tizen/20180928.044319~73^2~545 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=9488833428e83c93a7e6002f4d056084fb57112f;p=platform%2Fupstream%2FlibSkiaSharp.git Refactored SkColorSpace and added in a Lab PCS GM The refactoring breaks off A2B0 tag support into a separate subclass of SkColorSpace_Base, while keeping the current (besides CLUT) functionality in a XYZTRC subclass. ICC profile loading is now aware of this and creates the A2B0 subclass when SkColorSpace::NewICC() is called on a profile in need of the A2B0 functionality. The LabPCSDemo GM loads a .icc profile containing a LAB PCS and then runs a Lab->XYZ conversion on an image using it so we can display it and test out the A2B0 SkColorSpace functionality, sans a/b/m-curves, as well as the Lab->XYZ conversion code. BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2389983002 Review-Url: https://codereview.chromium.org/2389983002 --- diff --git a/bench/ColorCodecBench.cpp b/bench/ColorCodecBench.cpp index 6aa46d609f..fe3cdd24b5 100644 --- a/bench/ColorCodecBench.cpp +++ b/bench/ColorCodecBench.cpp @@ -9,7 +9,7 @@ #include "Resources.h" #include "SkCodec.h" #include "SkCodecPriv.h" -#include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkColorSpaceXform.h" #include "SkCommandLineFlags.h" @@ -170,7 +170,8 @@ void ColorCodecBench::onDelayedSetup() { if (FLAGS_half) { fDstInfo = fDstInfo.makeColorType(kRGBA_F16_SkColorType); - fDstSpace = as_CSB(fDstSpace)->makeLinearGamma(); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(fDstSpace)->type()); + fDstSpace = static_cast(fDstSpace.get())->makeLinearGamma(); } if (FLAGS_nonstd) { diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index e777815baa..8b62864c8f 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -11,7 +11,7 @@ #include "SkCodec.h" #include "SkCodecImageGenerator.h" #include "SkColorSpace.h" -#include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkColorSpaceXform.h" #include "SkCommonFlags.h" #include "SkData.h" @@ -920,7 +920,9 @@ Error ColorCodecSrc::draw(SkCanvas* canvas) const { decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType); } if (kRGBA_F16_SkColorType == fColorType) { - decodeInfo = decodeInfo.makeColorSpace(as_CSB(decodeInfo.colorSpace())->makeLinearGamma()); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(decodeInfo.colorSpace())->type()); + SkColorSpace_XYZ* csXYZ = static_cast(decodeInfo.colorSpace()); + decodeInfo = decodeInfo.makeColorSpace(csXYZ->makeLinearGamma()); } SkImageInfo bitmapInfo = decodeInfo; @@ -939,7 +941,15 @@ Error ColorCodecSrc::draw(SkCanvas* canvas) const { size_t rowBytes = bitmap.rowBytes(); SkCodec::Result r = codec->getPixels(decodeInfo, bitmap.getPixels(), rowBytes); if (SkCodec::kSuccess != r && SkCodec::kIncompleteInput != r) { - return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r); + if (kRGBA_F16_SkColorType == decodeInfo.colorType()) { + // FIXME (raftias): + // Get the codecs to not fail when there is no color xform, + // which currently happens in F16 mode. + return Error::Nonfatal(SkStringPrintf("Couldn't getPixels %s in F16. Error code %d", + fPath.c_str())); + } else { + return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r); + } } switch (fMode) { diff --git a/gm/gamut.cpp b/gm/gamut.cpp index 707da29f64..3c64915561 100644 --- a/gm/gamut.cpp +++ b/gm/gamut.cpp @@ -127,20 +127,25 @@ static void draw_gamut_grid(SkCanvas* canvas, SkTArrayimageInfo(); - auto srgbCS = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); - auto wideCS = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma, - wideGamutRGB_toXYZD50); + sk_sp srgbCS; + sk_sp wideCS; switch (origInfo.colorType()) { case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: + srgbCS = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + wideCS = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma, + wideGamutRGB_toXYZD50); break; case kRGBA_F16_SkColorType: - srgbCS = as_CSB(srgbCS.get())->makeLinearGamma(); - wideCS = as_CSB(wideCS.get())->makeLinearGamma(); + srgbCS = SkColorSpace::NewNamed(SkColorSpace::kSRGBLinear_Named); + wideCS = SkColorSpace::NewRGB(SkColorSpace::kLinear_RenderTargetGamma, + wideGamutRGB_toXYZD50); break; default: return; } + SkASSERT(srgbCS); + SkASSERT(wideCS); // Make our two working surfaces (one sRGB, one Adobe) SkImageInfo srgbGamutInfo = SkImageInfo::Make(gRectSize, gRectSize, origInfo.colorType(), diff --git a/gm/labpcsdemo.cpp b/gm/labpcsdemo.cpp new file mode 100644 index 0000000000..4bd9ed8140 --- /dev/null +++ b/gm/labpcsdemo.cpp @@ -0,0 +1,272 @@ +/* + * 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 +#include "gm.h" +#include "Resources.h" +#include "SkCodec.h" +#include "SkColorSpace_Base.h" +#include "SkColorSpace_A2B.h" +#include "SkColorSpacePriv.h" +#include "SkData.h" +#include "SkFloatingPoint.h" +#include "SkImageInfo.h" +#include "SkScalar.h" +#include "SkSRGB.h" +#include "SkStream.h" +#include "SkSurface.h" +#include "SkTypes.h" + +static inline void interp_3d_clut(float dst[3], float src[3], const SkColorLookUpTable* colorLUT) { + // Call the src components x, y, and z. + uint8_t maxX = colorLUT->fGridPoints[0] - 1; + uint8_t maxY = colorLUT->fGridPoints[1] - 1; + uint8_t maxZ = colorLUT->fGridPoints[2] - 1; + + // An approximate index into each of the three dimensions of the table. + float x = src[0] * maxX; + float y = src[1] * maxY; + float z = src[2] * maxZ; + + // This gives us the low index for our interpolation. + int ix = sk_float_floor2int(x); + int iy = sk_float_floor2int(y); + int iz = sk_float_floor2int(z); + + // Make sure the low index is not also the max index. + ix = (maxX == ix) ? ix - 1 : ix; + iy = (maxY == iy) ? iy - 1 : iy; + iz = (maxZ == iz) ? iz - 1 : iz; + + // Weighting factors for the interpolation. + float diffX = x - ix; + float diffY = y - iy; + float diffZ = z - iz; + + // Constants to help us navigate the 3D table. + // Ex: Assume x = a, y = b, z = c. + // table[a * n001 + b * n010 + c * n100] logically equals table[a][b][c]. + const int n000 = 0; + const int n001 = 3 * colorLUT->fGridPoints[1] * colorLUT->fGridPoints[2]; + const int n010 = 3 * colorLUT->fGridPoints[2]; + const int n011 = n001 + n010; + const int n100 = 3; + const int n101 = n100 + n001; + const int n110 = n100 + n010; + const int n111 = n110 + n001; + + // Base ptr into the table. + const float* ptr = &(colorLUT->table()[ix*n001 + iy*n010 + iz*n100]); + + // The code below performs a tetrahedral interpolation for each of the three + // dst components. Once the tetrahedron containing the interpolation point is + // identified, the interpolation is a weighted sum of grid values at the + // vertices of the tetrahedron. The claim is that tetrahedral interpolation + // provides a more accurate color conversion. + // blogs.mathworks.com/steve/2006/11/24/tetrahedral-interpolation-for-colorspace-conversion/ + // + // I have one test image, and visually I can't tell the difference between + // tetrahedral and trilinear interpolation. In terms of computation, the + // tetrahedral code requires more branches but less computation. The + // SampleICC library provides an option for the client to choose either + // tetrahedral or trilinear. + for (int i = 0; i < 3; i++) { + if (diffZ < diffY) { + if (diffZ < diffX) { + dst[i] = (ptr[n000] + diffZ * (ptr[n110] - ptr[n010]) + + diffY * (ptr[n010] - ptr[n000]) + + diffX * (ptr[n111] - ptr[n110])); + } else if (diffY < diffX) { + dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) + + diffY * (ptr[n011] - ptr[n001]) + + diffX * (ptr[n001] - ptr[n000])); + } else { + dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) + + diffY * (ptr[n010] - ptr[n000]) + + diffX * (ptr[n011] - ptr[n010])); + } + } else { + if (diffZ < diffX) { + dst[i] = (ptr[n000] + diffZ * (ptr[n101] - ptr[n001]) + + diffY * (ptr[n111] - ptr[n101]) + + diffX * (ptr[n001] - ptr[n000])); + } else if (diffY < diffX) { + dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) + + diffY * (ptr[n111] - ptr[n101]) + + diffX * (ptr[n101] - ptr[n100])); + } else { + dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) + + diffY * (ptr[n110] - ptr[n100]) + + diffX * (ptr[n111] - ptr[n110])); + } + } + + // Increment the table ptr in order to handle the next component. + // Note that this is the how table is designed: all of nXXX + // variables are multiples of 3 because there are 3 output + // components. + ptr++; + } +} + + +/** + * This tests decoding from a Lab source image and displays on the left + * the image as raw RGB values, and on the right a Lab PCS. + * It currently does NOT apply a/b/m-curves, as in the .icc profile + * We are testing it on these are all identity transforms. + */ +class LabPCSDemoGM : public skiagm::GM { +public: + LabPCSDemoGM() + : fWidth(1080) + , fHeight(480) + {} + +protected: + + + SkString onShortName() override { + return SkString("labpcsdemo"); + } + + SkISize onISize() override { + return SkISize::Make(fWidth, fHeight); + } + + void onDraw(SkCanvas* canvas) override { + canvas->drawColor(SK_ColorGREEN); + const char* filename = "brickwork-texture.jpg"; + renderImage(canvas, filename, 0, false); + renderImage(canvas, filename, 1, true); + } + + void renderImage(SkCanvas* canvas, const char* filename, int col, bool convertLabToXYZ) { + SkBitmap bitmap; + SkStream* stream(GetResourceAsStream(filename)); + if (stream == nullptr) { + return; + } + std::unique_ptr codec(SkCodec::NewFromStream(stream)); + + + // srgb_lab_pcs.icc is an elaborate way to specify sRGB but uses + // Lab as the PCS, so we can take any arbitrary image that should + // be sRGB and this should show a reasonable image + const SkString iccFilename(GetResourcePath("icc_profiles/srgb_lab_pcs.icc")); + sk_sp iccData = SkData::MakeFromFileName(iccFilename.c_str()); + if (iccData == nullptr) { + return; + } + sk_sp colorSpace = SkColorSpace::NewICC(iccData->bytes(), iccData->size()); + + const int imageWidth = codec->getInfo().width(); + const int imageHeight = codec->getInfo().height(); + // Using nullptr as the color space instructs the codec to decode in legacy mode, + // meaning that we will get the raw encoded bytes without any color correction. + SkImageInfo imageInfo = SkImageInfo::Make(imageWidth, imageHeight, kN32_SkColorType, + kOpaque_SkAlphaType, nullptr); + bitmap.allocPixels(imageInfo); + codec->getPixels(imageInfo, bitmap.getPixels(), bitmap.rowBytes()); + if (convertLabToXYZ) { + SkASSERT(SkColorSpace_Base::Type::kA2B == as_CSB(colorSpace)->type()); + SkColorSpace_A2B& cs = *static_cast(colorSpace.get()); + bool printConversions = false; + SkASSERT(cs.colorLUT()); + // We're skipping evaluating the TRCs and the matrix here since they aren't + // in the ICC profile initially used here. + SkASSERT(kLinear_SkGammaNamed == cs.aCurveNamed()); + SkASSERT(kLinear_SkGammaNamed == cs.mCurveNamed()); + SkASSERT(kLinear_SkGammaNamed == cs.bCurveNamed()); + SkASSERT(cs.matrix().isIdentity()); + for (int y = 0; y < imageHeight; ++y) { + for (int x = 0; x < imageWidth; ++x) { + uint32_t& p = *bitmap.getAddr32(x, y); + const int r = SkColorGetR(p); + const int g = SkColorGetG(p); + const int b = SkColorGetB(p); + if (printConversions) { + SkColorSpacePrintf("\nraw = (%d, %d, %d)\t", r, g, b); + } + + float lab[4] = { r * (1.f/255.f), g * (1.f/255.f), b * (1.f/255.f), 1.f }; + + interp_3d_clut(lab, lab, cs.colorLUT()); + + // Lab has ranges [0,100] for L and [-128,127] for a and b + // but the ICC profile loader stores as [0,1]. The ICC + // specifies an offset of -128 to convert. + // note: formula could be adjusted to remove this conversion, + // but for now let's keep it like this for clarity until + // an optimized version is added. + lab[0] *= 100.f; + lab[1] = 255.f * lab[1] - 128.f; + lab[2] = 255.f * lab[2] - 128.f; + if (printConversions) { + SkColorSpacePrintf("Lab = < %f, %f, %f >\n", lab[0], lab[1], lab[2]); + } + + // convert from Lab to XYZ + float Y = (lab[0] + 16.f) * (1.f/116.f); + float X = lab[1] * (1.f/500.f) + Y; + float Z = Y - (lab[2] * (1.f/200.f)); + float cubed; + cubed = X*X*X; + if (cubed > 0.008856f) + X = cubed; + else + X = (X - (16.f/116.f)) * (1.f/7.787f); + cubed = Y*Y*Y; + if (cubed > 0.008856f) + Y = cubed; + else + Y = (Y - (16.f/116.f)) * (1.f/7.787f); + cubed = Z*Z*Z; + if (cubed > 0.008856f) + Z = cubed; + else + Z = (Z - (16.f/116.f)) * (1.f/7.787f); + + // adjust to D50 illuminant + X *= 0.96422f; + Y *= 1.00000f; + Z *= 0.82521f; + + if (printConversions) { + SkColorSpacePrintf("XYZ = (%4f, %4f, %4f)\t", X, Y, Z); + } + + // convert XYZ -> linear sRGB + Sk4f lRGB( 3.1338561f*X - 1.6168667f*Y - 0.4906146f*Z, + -0.9787684f*X + 1.9161415f*Y + 0.0334540f*Z, + 0.0719453f*X - 0.2289914f*Y + 1.4052427f*Z, + 1.f); + // and apply sRGB gamma + Sk4i sRGB = sk_linear_to_srgb(lRGB); + if (printConversions) { + SkColorSpacePrintf("sRGB = (%d, %d, %d)\n", sRGB[0], sRGB[1], sRGB[2]); + } + p = SkColorSetRGB(sRGB[0], sRGB[1], sRGB[2]); + } + } + } + const int freeWidth = fWidth - 2*imageWidth; + const int freeHeight = fHeight - imageHeight; + canvas->drawBitmap(bitmap, + static_cast((col+1) * (freeWidth / 3) + col*imageWidth), + static_cast(freeHeight / 2)); + ++col; + } + +private: + const int fWidth; + const int fHeight; + + typedef skiagm::GM INHERITED; +}; + +DEF_GM( return new LabPCSDemoGM; ) diff --git a/gyp/core.gypi b/gyp/core.gypi index a435164883..0488436b97 100644 --- a/gyp/core.gypi +++ b/gyp/core.gypi @@ -77,6 +77,10 @@ '<(skia_src_path)/core/SkColorShader.cpp', '<(skia_src_path)/core/SkColorShader.h', '<(skia_src_path)/core/SkColorSpace.cpp', + '<(skia_src_path)/core/SkColorSpace_A2B.cpp', + '<(skia_src_path)/core/SkColorSpace_A2B.h', + '<(skia_src_path)/core/SkColorSpace_XYZ.cpp', + '<(skia_src_path)/core/SkColorSpace_XYZ.h', '<(skia_src_path)/core/SkColorSpace_ICC.cpp', '<(skia_src_path)/core/SkColorSpaceXform.cpp', '<(skia_src_path)/core/SkColorTable.cpp', diff --git a/resources/icc_profiles/srgb_lab_pcs.icc b/resources/icc_profiles/srgb_lab_pcs.icc new file mode 100644 index 0000000000..cfbd03e1f7 Binary files /dev/null and b/resources/icc_profiles/srgb_lab_pcs.icc differ diff --git a/samplecode/SampleApp.cpp b/samplecode/SampleApp.cpp index 4b4621698f..2194895e0d 100644 --- a/samplecode/SampleApp.cpp +++ b/samplecode/SampleApp.cpp @@ -12,7 +12,7 @@ #include "SampleCode.h" #include "SkAnimTimer.h" #include "SkCanvas.h" -#include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkCommandLineFlags.h" #include "SkData.h" #include "SkDocument.h" @@ -1676,7 +1676,9 @@ bool SampleWindow::onEvent(const SkEvent& evt) { } if (kRGBA_F16_SkColorType == gConfig[selected].fColorType) { SkASSERT(colorSpace); - colorSpace = as_CSB(colorSpace)->makeLinearGamma(); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(colorSpace)->type()); + SkColorSpace_XYZ* csXYZ = static_cast(colorSpace.get()); + colorSpace = csXYZ->makeLinearGamma(); } this->setDeviceColorType(gConfig[selected].fColorType, colorSpace); return true; diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp index 92ab2bc182..6863855d3b 100644 --- a/src/codec/SkJpegCodec.cpp +++ b/src/codec/SkJpegCodec.cpp @@ -412,7 +412,9 @@ bool SkJpegCodec::setOutputColorSpace(const SkImageInfo& dstInfo) { fDecoderMgr->dinfo()->out_color_space = JCS_GRAYSCALE; return true; case kRGBA_F16_SkColorType: - SkASSERT(fColorXform); + if (!fColorXform) { + return false; + } if (!dstInfo.colorSpace()->gammaIsLinear()) { return false; } @@ -635,7 +637,6 @@ void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& void SkJpegCodec::initializeColorXform(const SkImageInfo& dstInfo) { if (needs_color_xform(dstInfo, this->getInfo())) { fColorXform = SkColorSpaceXform::New(this->getInfo().colorSpace(), dstInfo.colorSpace()); - SkASSERT(fColorXform); } } diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp index b956c7fe3e..e90254c969 100644 --- a/src/core/SkColorSpace.cpp +++ b/src/core/SkColorSpace.cpp @@ -7,8 +7,8 @@ #include "SkColorSpace.h" #include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkColorSpacePriv.h" -#include "SkColorSpaceXform_Base.h" #include "SkOnce.h" #include "SkPoint3.h" @@ -83,23 +83,8 @@ bool SkColorSpacePrimaries::toXYZD50(SkMatrix44* toXYZ_D50) const { /////////////////////////////////////////////////////////////////////////////////////////////////// -SkColorSpace_Base::SkColorSpace_Base(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50) - : fGammaNamed(gammaNamed) - , fGammas(nullptr) - , fProfileData(nullptr) - , fToXYZD50(toXYZD50) - , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) -{} - -SkColorSpace_Base::SkColorSpace_Base(sk_sp colorLUT, SkGammaNamed gammaNamed, - sk_sp gammas, const SkMatrix44& toXYZD50, - sk_sp profileData) - : fColorLUT(std::move(colorLUT)) - , fGammaNamed(gammaNamed) - , fGammas(std::move(gammas)) - , fProfileData(std::move(profileData)) - , fToXYZD50(toXYZD50) - , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) +SkColorSpace_Base::SkColorSpace_Base(sk_sp profileData) + : fProfileData(std::move(profileData)) {} static constexpr float gSRGB_toXYZD50[] { @@ -163,8 +148,8 @@ sk_sp SkColorSpace::NewRGB(const float values[3], const SkMatrix44 gammas->fRedData.fValue = values[0]; gammas->fGreenData.fValue = values[1]; gammas->fBlueData.fValue = values[2]; - return sk_sp(new SkColorSpace_Base(nullptr, kNonStandard_SkGammaNamed, gammas, - toXYZD50, nullptr)); + return sk_sp(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed, + gammas, toXYZD50, nullptr)); } return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50); @@ -194,7 +179,7 @@ sk_sp SkColorSpace_Base::NewRGB(SkGammaNamed gammaNamed, const SkM break; } - return sk_sp(new SkColorSpace_Base(gammaNamed, toXYZD50)); + return sk_sp(new SkColorSpace_XYZ(gammaNamed, toXYZD50)); } sk_sp SkColorSpace::NewRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) { @@ -235,8 +220,8 @@ sk_sp SkColorSpace::NewRGB(const SkColorSpaceTransferFn& coeffs, gammas->fRedData = data; gammas->fGreenData = data; gammas->fBlueData = data; - return sk_sp(new SkColorSpace_Base(nullptr, kNonStandard_SkGammaNamed, - std::move(gammas), toXYZD50, nullptr)); + return sk_sp(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed, + std::move(gammas), toXYZD50, nullptr)); } static SkColorSpace* gAdobeRGB; @@ -256,7 +241,7 @@ sk_sp SkColorSpace::NewNamed(Named named) { // Force the mutable type mask to be computed. This avoids races. (void)srgbToxyzD50.getType(); - gSRGB = new SkColorSpace_Base(kSRGB_SkGammaNamed, srgbToxyzD50); + gSRGB = new SkColorSpace_XYZ(kSRGB_SkGammaNamed, srgbToxyzD50); }); return sk_ref_sp(gSRGB); } @@ -267,7 +252,7 @@ sk_sp SkColorSpace::NewNamed(Named named) { // Force the mutable type mask to be computed. This avoids races. (void)adobergbToxyzD50.getType(); - gAdobeRGB = new SkColorSpace_Base(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50); + gAdobeRGB = new SkColorSpace_XYZ(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50); }); return sk_ref_sp(gAdobeRGB); } @@ -278,7 +263,7 @@ sk_sp SkColorSpace::NewNamed(Named named) { // Force the mutable type mask to be computed. This avoids races. (void)srgbToxyzD50.getType(); - gSRGBLinear = new SkColorSpace_Base(kLinear_SkGammaNamed, srgbToxyzD50); + gSRGBLinear = new SkColorSpace_XYZ(kLinear_SkGammaNamed, srgbToxyzD50); }); return sk_ref_sp(gSRGBLinear); } @@ -288,53 +273,14 @@ sk_sp SkColorSpace::NewNamed(Named named) { return nullptr; } -sk_sp SkColorSpace_Base::makeLinearGamma() { - if (this->gammaIsLinear()) { - return sk_ref_sp(this); - } - return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, as_CSB(this)->fToXYZD50); -} - /////////////////////////////////////////////////////////////////////////////////////////////////// bool SkColorSpace::gammaCloseToSRGB() const { - return kSRGB_SkGammaNamed == as_CSB(this)->fGammaNamed || - k2Dot2Curve_SkGammaNamed == as_CSB(this)->fGammaNamed; + return as_CSB(this)->onGammaCloseToSRGB(); } bool SkColorSpace::gammaIsLinear() const { - return kLinear_SkGammaNamed == as_CSB(this)->fGammaNamed; -} - -const SkMatrix44& SkColorSpace_Base::fromXYZD50() const { - fFromXYZOnce([this] { - if (!fToXYZD50.invert(&fFromXYZD50)) { - // If a client gives us a dst gamut with a transform that we can't invert, we will - // simply give them back a transform to sRGB gamut. - SkDEBUGFAIL("Non-invertible XYZ matrix, defaulting to sRGB"); - SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor); - srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50); - srgbToxyzD50.invert(&fFromXYZD50); - } - }); - return fFromXYZD50; -} - -void SkColorSpace_Base::toDstGammaTables(const uint8_t* tables[3], sk_sp* storage, - int numTables) const { - fToDstGammaOnce([this, numTables] { - const bool gammasAreMatching = numTables <= 1; - fDstStorage = - SkData::MakeUninitialized(numTables * SkColorSpaceXform_Base::kDstGammaTableSize); - SkColorSpaceXform_Base::BuildDstGammaTables(fToDstGammaTables, - (uint8_t*) fDstStorage->writable_data(), this, - gammasAreMatching); - }); - - *storage = fDstStorage; - tables[0] = fToDstGammaTables[0]; - tables[1] = fToDstGammaTables[1]; - tables[2] = fToDstGammaTables[2]; + return as_CSB(this)->onGammaIsLinear(); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -403,51 +349,52 @@ size_t SkColorSpace::writeToMemory(void* memory) const { // Start by trying the serialization fast path. If we haven't saved ICC profile data, // we must have a profile that we can serialize easily. if (!as_CSB(this)->fProfileData) { + // Profile data is mandatory for A2B0 color spaces. + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(this)->type()); + const SkColorSpace_XYZ* thisXYZ = static_cast(this); // If we have a named profile, only write the enum. + const SkGammaNamed gammaNamed = thisXYZ->gammaNamed(); if (this == gSRGB) { if (memory) { *((ColorSpaceHeader*) memory) = - ColorSpaceHeader::Pack(k0_Version, kSRGB_Named, - as_CSB(this)->fGammaNamed, 0); + ColorSpaceHeader::Pack(k0_Version, kSRGB_Named, gammaNamed, 0); } return sizeof(ColorSpaceHeader); } else if (this == gAdobeRGB) { if (memory) { *((ColorSpaceHeader*) memory) = - ColorSpaceHeader::Pack(k0_Version, kAdobeRGB_Named, - as_CSB(this)->fGammaNamed, 0); + ColorSpaceHeader::Pack(k0_Version, kAdobeRGB_Named, gammaNamed, 0); } return sizeof(ColorSpaceHeader); } else if (this == gSRGBLinear) { if (memory) { - *((ColorSpaceHeader*)memory) = - ColorSpaceHeader::Pack(k0_Version, kSRGBLinear_Named, - as_CSB(this)->fGammaNamed, 0); + *((ColorSpaceHeader*) memory) = + ColorSpaceHeader::Pack(k0_Version, kSRGBLinear_Named, gammaNamed, 0); } return sizeof(ColorSpaceHeader); } // If we have a named gamma, write the enum and the matrix. - switch (as_CSB(this)->fGammaNamed) { + switch (gammaNamed) { case kSRGB_SkGammaNamed: case k2Dot2Curve_SkGammaNamed: case kLinear_SkGammaNamed: { if (memory) { *((ColorSpaceHeader*) memory) = - ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed, + ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed, ColorSpaceHeader::kMatrix_Flag); memory = SkTAddOffset(memory, sizeof(ColorSpaceHeader)); - as_CSB(this)->fToXYZD50.as3x4RowMajorf((float*) memory); + thisXYZ->toXYZD50()->as3x4RowMajorf((float*) memory); } return sizeof(ColorSpaceHeader) + 12 * sizeof(float); } default: - const SkGammas* gammas = as_CSB(this)->gammas(); + const SkGammas* gammas = thisXYZ->gammas(); SkASSERT(gammas); if (gammas->isValue(0) && gammas->isValue(1) && gammas->isValue(2)) { if (memory) { *((ColorSpaceHeader*) memory) = - ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed, + ColorSpaceHeader::Pack(k0_Version, 0, thisXYZ->fGammaNamed, ColorSpaceHeader::kFloatGamma_Flag); memory = SkTAddOffset(memory, sizeof(ColorSpaceHeader)); @@ -456,7 +403,7 @@ size_t SkColorSpace::writeToMemory(void* memory) const { *(((float*) memory) + 2) = gammas->fBlueData.fValue; memory = SkTAddOffset(memory, 3 * sizeof(float)); - as_CSB(this)->fToXYZD50.as3x4RowMajorf((float*) memory); + thisXYZ->fToXYZD50.as3x4RowMajorf((float*) memory); } return sizeof(ColorSpaceHeader) + 15 * sizeof(float); @@ -469,7 +416,7 @@ size_t SkColorSpace::writeToMemory(void* memory) const { if (memory) { *((ColorSpaceHeader*) memory) = - ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed, + ColorSpaceHeader::Pack(k0_Version, 0, thisXYZ->fGammaNamed, ColorSpaceHeader::kTransferFn_Flag); memory = SkTAddOffset(memory, sizeof(ColorSpaceHeader)); @@ -482,7 +429,7 @@ size_t SkColorSpace::writeToMemory(void* memory) const { *(((float*) memory) + 6) = gammas->params(0).fG; memory = SkTAddOffset(memory, 7 * sizeof(float)); - as_CSB(this)->fToXYZD50.as3x4RowMajorf((float*) memory); + thisXYZ->fToXYZD50.as3x4RowMajorf((float*) memory); } return sizeof(ColorSpaceHeader) + 19 * sizeof(float); @@ -624,23 +571,26 @@ bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) { return false; } - // It's important to check fProfileData before named gammas. Some profiles may have named - // gammas, but also include other wacky features that cause us to save the data. - switch (as_CSB(src)->fGammaNamed) { + // profiles are mandatory for A2B0 color spaces + SkASSERT(as_CSB(src)->type() == SkColorSpace_Base::Type::kXYZ); + const SkColorSpace_XYZ* srcXYZ = static_cast(src); + const SkColorSpace_XYZ* dstXYZ = static_cast(dst); + + switch (srcXYZ->gammaNamed()) { case kSRGB_SkGammaNamed: case k2Dot2Curve_SkGammaNamed: case kLinear_SkGammaNamed: - return (as_CSB(src)->fGammaNamed == as_CSB(dst)->fGammaNamed) && - (as_CSB(src)->fToXYZD50 == as_CSB(dst)->fToXYZD50); + return (srcXYZ->gammaNamed() == dstXYZ->gammaNamed()) && + (*srcXYZ->toXYZD50() == *dstXYZ->toXYZD50()); default: - if (as_CSB(src)->fGammaNamed != as_CSB(dst)->fGammaNamed) { + if (srcXYZ->gammaNamed() != dstXYZ->gammaNamed()) { return false; } - // It is unlikely that we will reach this case. - sk_sp srcData = src->serialize(); - sk_sp dstData = dst->serialize(); - return srcData->size() == dstData->size() && - 0 == memcmp(srcData->data(), dstData->data(), srcData->size()); + sk_sp serializedSrcData = src->serialize(); + sk_sp serializedDstData = dst->serialize(); + return serializedSrcData->size() == serializedDstData->size() && + 0 == memcmp(serializedSrcData->data(), serializedDstData->data(), + serializedSrcData->size()); } } diff --git a/src/core/SkColorSpaceXform.cpp b/src/core/SkColorSpaceXform.cpp index 5e362464b4..cacb38878c 100644 --- a/src/core/SkColorSpaceXform.cpp +++ b/src/core/SkColorSpaceXform.cpp @@ -6,7 +6,9 @@ */ #include "SkColorPriv.h" +#include "SkColorSpace_A2B.h" #include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkColorSpacePriv.h" #include "SkColorSpaceXform_Base.h" #include "SkHalf.h" @@ -254,10 +256,10 @@ static const GammaFns kFromLinear { // Build tables to transform src gamma to linear. template static void build_gamma_tables(const T* outGammaTables[3], T* gammaTableStorage, int gammaTableSize, - const SkColorSpace* space, const GammaFns& fns, + const SkColorSpace_XYZ* space, const GammaFns& fns, bool gammasAreMatching) { - switch (as_CSB(space)->gammaNamed()) { + switch (space->gammaNamed()) { case kSRGB_SkGammaNamed: outGammaTables[0] = outGammaTables[1] = outGammaTables[2] = fns.fSRGBTable; break; @@ -268,7 +270,7 @@ static void build_gamma_tables(const T* outGammaTables[3], T* gammaTableStorage, outGammaTables[0] = outGammaTables[1] = outGammaTables[2] = nullptr; break; default: { - const SkGammas* gammas = as_CSB(space)->gammas(); + const SkGammas* gammas = space->gammas(); SkASSERT(gammas); auto build_table = [=](int i) { @@ -326,7 +328,8 @@ static void build_gamma_tables(const T* outGammaTables[3], T* gammaTableStorage, } void SkColorSpaceXform_Base::BuildDstGammaTables(const uint8_t* dstGammaTables[3], - uint8_t* dstStorage, const SkColorSpace* space, + uint8_t* dstStorage, + const SkColorSpace_XYZ* space, bool gammasAreMatching) { build_gamma_tables(dstGammaTables, dstStorage, kDstGammaTableSize, space, kFromLinear, gammasAreMatching); @@ -355,13 +358,30 @@ std::unique_ptr SkColorSpaceXform::New(SkColorSpace* srcSpace return nullptr; } + if (SkColorSpace_Base::Type::kA2B == as_CSB(dstSpace)->type()) { + SkColorSpacePrintf("A2B destinations not supported\n"); + return nullptr; + } + + if (SkColorSpace_Base::Type::kA2B == as_CSB(srcSpace)->type()) { + // TODO (raftias): return an A2B-supporting SkColorSpaceXform here once the xform. + // is implemented. SkColorSpaceXform_Base only supports XYZ+TRC based SkColorSpaces + //SkColorSpace_A2B* src = static_cast(srcSpace); + //SkColorSpace_XYZ* dst = static_cast(dstSpace); + //return std::unique_ptr(new SkColorSpaceXform_A2B(src, dst)); + SkColorSpacePrintf("A2B sources not supported (yet)\n"); + return nullptr; + } + SkColorSpace_XYZ* srcSpaceXYZ = static_cast(srcSpace); + SkColorSpace_XYZ* dstSpaceXYZ = static_cast(dstSpace); + ColorSpaceMatch csm = kNone_ColorSpaceMatch; SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); if (SkColorSpace::Equals(srcSpace, dstSpace)) { srcToDst.setIdentity(); csm = kFull_ColorSpaceMatch; } else { - srcToDst.setConcat(as_CSB(dstSpace)->fromXYZD50(), as_CSB(srcSpace)->toXYZD50()); + srcToDst.setConcat(*dstSpaceXYZ->fromXYZD50(), *srcSpaceXYZ->toXYZD50()); if (is_almost_identity(srcToDst)) { srcToDst.setIdentity(); @@ -371,109 +391,109 @@ std::unique_ptr SkColorSpaceXform::New(SkColorSpace* srcSpace switch (csm) { case kNone_ColorSpaceMatch: - switch (as_CSB(dstSpace)->gammaNamed()) { + switch (dstSpaceXYZ->gammaNamed()) { case kSRGB_SkGammaNamed: - if (srcSpace->gammaIsLinear()) { + if (srcSpaceXYZ->gammaIsLinear()) { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } else { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } case k2Dot2Curve_SkGammaNamed: - if (srcSpace->gammaIsLinear()) { + if (srcSpaceXYZ->gammaIsLinear()) { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } else { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } case kLinear_SkGammaNamed: - if (srcSpace->gammaIsLinear()) { + if (srcSpaceXYZ->gammaIsLinear()) { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } else { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } default: - if (srcSpace->gammaIsLinear()) { + if (srcSpaceXYZ->gammaIsLinear()) { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } else { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } } case kGamut_ColorSpaceMatch: - switch (as_CSB(dstSpace)->gammaNamed()) { + switch (dstSpaceXYZ->gammaNamed()) { case kSRGB_SkGammaNamed: - if (srcSpace->gammaIsLinear()) { + if (srcSpaceXYZ->gammaIsLinear()) { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } else { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } case k2Dot2Curve_SkGammaNamed: - if (srcSpace->gammaIsLinear()) { + if (srcSpaceXYZ->gammaIsLinear()) { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } else { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } case kLinear_SkGammaNamed: - if (srcSpace->gammaIsLinear()) { + if (srcSpaceXYZ->gammaIsLinear()) { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } else { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } default: - if (srcSpace->gammaIsLinear()) { + if (srcSpaceXYZ->gammaIsLinear()) { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } else { return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } } case kFull_ColorSpaceMatch: - switch (as_CSB(dstSpace)->gammaNamed()) { + switch (dstSpaceXYZ->gammaNamed()) { case kSRGB_SkGammaNamed: return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); case k2Dot2Curve_SkGammaNamed: return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); case kLinear_SkGammaNamed: return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); default: return std::unique_ptr(new SkColorSpaceXform_XYZ - (srcSpace, srcToDst, dstSpace)); + (srcSpaceXYZ, srcToDst, dstSpaceXYZ)); } default: SkASSERT(false); @@ -483,138 +503,6 @@ std::unique_ptr SkColorSpaceXform::New(SkColorSpace* srcSpace /////////////////////////////////////////////////////////////////////////////////////////////////// -static float byte_to_float(uint8_t byte) { - return ((float) byte) * (1.0f / 255.0f); -} - -// Clamp to the 0-1 range. -static float clamp_normalized_float(float v) { - if (v > 1.0f) { - return 1.0f; - } else if ((v < 0.0f) || (v != v)) { - return 0.0f; - } else { - return v; - } -} - -static void interp_3d_clut(float dst[3], float src[3], const SkColorLookUpTable* colorLUT) { - // Call the src components x, y, and z. - uint8_t maxX = colorLUT->fGridPoints[0] - 1; - uint8_t maxY = colorLUT->fGridPoints[1] - 1; - uint8_t maxZ = colorLUT->fGridPoints[2] - 1; - - // An approximate index into each of the three dimensions of the table. - float x = src[0] * maxX; - float y = src[1] * maxY; - float z = src[2] * maxZ; - - // This gives us the low index for our interpolation. - int ix = sk_float_floor2int(x); - int iy = sk_float_floor2int(y); - int iz = sk_float_floor2int(z); - - // Make sure the low index is not also the max index. - ix = (maxX == ix) ? ix - 1 : ix; - iy = (maxY == iy) ? iy - 1 : iy; - iz = (maxZ == iz) ? iz - 1 : iz; - - // Weighting factors for the interpolation. - float diffX = x - ix; - float diffY = y - iy; - float diffZ = z - iz; - - // Constants to help us navigate the 3D table. - // Ex: Assume x = a, y = b, z = c. - // table[a * n001 + b * n010 + c * n100] logically equals table[a][b][c]. - const int n000 = 0; - const int n001 = 3 * colorLUT->fGridPoints[1] * colorLUT->fGridPoints[2]; - const int n010 = 3 * colorLUT->fGridPoints[2]; - const int n011 = n001 + n010; - const int n100 = 3; - const int n101 = n100 + n001; - const int n110 = n100 + n010; - const int n111 = n110 + n001; - - // Base ptr into the table. - const float* ptr = &(colorLUT->table()[ix*n001 + iy*n010 + iz*n100]); - - // The code below performs a tetrahedral interpolation for each of the three - // dst components. Once the tetrahedron containing the interpolation point is - // identified, the interpolation is a weighted sum of grid values at the - // vertices of the tetrahedron. The claim is that tetrahedral interpolation - // provides a more accurate color conversion. - // blogs.mathworks.com/steve/2006/11/24/tetrahedral-interpolation-for-colorspace-conversion/ - // - // I have one test image, and visually I can't tell the difference between - // tetrahedral and trilinear interpolation. In terms of computation, the - // tetrahedral code requires more branches but less computation. The - // SampleICC library provides an option for the client to choose either - // tetrahedral or trilinear. - for (int i = 0; i < 3; i++) { - if (diffZ < diffY) { - if (diffZ < diffX) { - dst[i] = (ptr[n000] + diffZ * (ptr[n110] - ptr[n010]) + - diffY * (ptr[n010] - ptr[n000]) + - diffX * (ptr[n111] - ptr[n110])); - } else if (diffY < diffX) { - dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) + - diffY * (ptr[n011] - ptr[n001]) + - diffX * (ptr[n001] - ptr[n000])); - } else { - dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) + - diffY * (ptr[n010] - ptr[n000]) + - diffX * (ptr[n011] - ptr[n010])); - } - } else { - if (diffZ < diffX) { - dst[i] = (ptr[n000] + diffZ * (ptr[n101] - ptr[n001]) + - diffY * (ptr[n111] - ptr[n101]) + - diffX * (ptr[n001] - ptr[n000])); - } else if (diffY < diffX) { - dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) + - diffY * (ptr[n111] - ptr[n101]) + - diffX * (ptr[n101] - ptr[n100])); - } else { - dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) + - diffY * (ptr[n110] - ptr[n100]) + - diffX * (ptr[n111] - ptr[n110])); - } - } - - // Increment the table ptr in order to handle the next component. - // Note that this is the how table is designed: all of nXXX - // variables are multiples of 3 because there are 3 output - // components. - ptr++; - } -} - -static void handle_color_lut(uint32_t* dst, const void* vsrc, int len, - SkColorLookUpTable* colorLUT) { - const uint32_t* src = (const uint32_t*) vsrc; - while (len-- > 0) { - uint8_t r = (*src >> 0) & 0xFF, - g = (*src >> 8) & 0xFF, - b = (*src >> 16) & 0xFF; - - float in[3]; - float out[3]; - in[0] = byte_to_float(r); - in[1] = byte_to_float(g); - in[2] = byte_to_float(b); - interp_3d_clut(out, in, colorLUT); - - r = sk_float_round2int(255.0f * clamp_normalized_float(out[0])); - g = sk_float_round2int(255.0f * clamp_normalized_float(out[1])); - b = sk_float_round2int(255.0f * clamp_normalized_float(out[2])); - *dst = SkPackARGB_as_RGBA(0xFF, r, g, b); - - src++; - dst++; - } -} - static inline void load_matrix(const float matrix[16], Sk4f& rXgXbX, Sk4f& rYgYbY, Sk4f& rZgZbZ, Sk4f& rTgTbT) { rXgXbX = Sk4f::Load(matrix + 0); @@ -1226,14 +1114,14 @@ static void color_xform_RGBA(void* dst, const void* vsrc, int len, /////////////////////////////////////////////////////////////////////////////////////////////////// -static inline int num_tables(SkColorSpace* space) { - switch (as_CSB(space)->gammaNamed()) { +static inline int num_tables(SkColorSpace_XYZ* space) { + switch (space->gammaNamed()) { case kSRGB_SkGammaNamed: case k2Dot2Curve_SkGammaNamed: case kLinear_SkGammaNamed: return 0; default: { - const SkGammas* gammas = as_CSB(space)->gammas(); + const SkGammas* gammas = space->gammas(); SkASSERT(gammas); bool gammasAreMatching = (gammas->type(0) == gammas->type(1)) && @@ -1250,8 +1138,8 @@ static inline int num_tables(SkColorSpace* space) { template SkColorSpaceXform_XYZ -::SkColorSpaceXform_XYZ(SkColorSpace* srcSpace, const SkMatrix44& srcToDst, SkColorSpace* dstSpace) - : fColorLUT(sk_ref_sp((SkColorLookUpTable*) as_CSB(srcSpace)->colorLUT())) +::SkColorSpaceXform_XYZ(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst, + SkColorSpace_XYZ* dstSpace) { srcToDst.asColMajorf(fSrcToDst); @@ -1263,7 +1151,7 @@ SkColorSpaceXform_XYZ srcGammasAreMatching); const int numDstTables = num_tables(dstSpace); - as_CSB(dstSpace)->toDstGammaTables(fDstGammaTables, &fDstStorage, numDstTables); + dstSpace->toDstGammaTables(fDstGammaTables, &fDstStorage, numDstTables); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1348,19 +1236,6 @@ bool SkColorSpaceXform_XYZ } } -#if defined(GOOGLE3) - // Stack frame size is limited in GOOGLE3. - SkAutoSMalloc<256 * sizeof(uint32_t)> storage; -#else - SkAutoSMalloc<1024 * sizeof(uint32_t)> storage; -#endif - if (fColorLUT) { - size_t storageBytes = len * sizeof(uint32_t); - storage.reset(storageBytes); - handle_color_lut((uint32_t*) storage.get(), src, len, fColorLUT.get()); - src = (const uint32_t*) storage.get(); - } - switch (dstColorFormat) { case kRGBA_8888_ColorFormat: switch (kDst) { @@ -1431,7 +1306,7 @@ bool SkColorSpaceXform::apply(ColorFormat dstColorFormat, void* dst, ColorFormat /////////////////////////////////////////////////////////////////////////////////////////////////// -std::unique_ptr SlowIdentityXform(SkColorSpace* space) { +std::unique_ptr SlowIdentityXform(SkColorSpace_XYZ* space) { return std::unique_ptr(new SkColorSpaceXform_XYZ (space, SkMatrix::I(), space)); diff --git a/src/core/SkColorSpaceXform_Base.h b/src/core/SkColorSpaceXform_Base.h index c1b9785424..a648677609 100644 --- a/src/core/SkColorSpaceXform_Base.h +++ b/src/core/SkColorSpaceXform_Base.h @@ -13,6 +13,8 @@ #include "SkColorSpaceXform.h" #include "SkResourceCache.h" +class SkColorSpace_XYZ; + class SkColorSpaceXform_Base : public SkColorSpaceXform { public: static constexpr int kDstGammaTableSize = 1024; @@ -23,10 +25,10 @@ protected: private: static void BuildDstGammaTables(const uint8_t* outGammaTables[3], uint8_t* gammaTableStorage, - const SkColorSpace* space, bool gammasAreMatching); + const SkColorSpace_XYZ* space, bool gammasAreMatching); friend class SkColorSpaceXform; - friend class SkColorSpace_Base; + friend class SkColorSpace_XYZ; }; enum SrcGamma { @@ -54,10 +56,8 @@ protected: int count, SkAlphaType alphaType) const override; private: - SkColorSpaceXform_XYZ(SkColorSpace* srcSpace, const SkMatrix44& srcToDst, - SkColorSpace* dstSpace); - - sk_sp fColorLUT; + SkColorSpaceXform_XYZ(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst, + SkColorSpace_XYZ* dstSpace); // Contain pointers into storage or pointers into precomputed tables. const float* fSrcGammaTables[3]; @@ -68,10 +68,10 @@ private: float fSrcToDst[16]; friend class SkColorSpaceXform; - friend std::unique_ptr SlowIdentityXform(SkColorSpace* space); + friend std::unique_ptr SlowIdentityXform(SkColorSpace_XYZ* space); }; // For testing. Bypasses opts for when src and dst color spaces are equal. -std::unique_ptr SlowIdentityXform(SkColorSpace* space); +std::unique_ptr SlowIdentityXform(SkColorSpace_XYZ* space); #endif diff --git a/src/core/SkColorSpace_A2B.cpp b/src/core/SkColorSpace_A2B.cpp new file mode 100644 index 0000000000..f67da48259 --- /dev/null +++ b/src/core/SkColorSpace_A2B.cpp @@ -0,0 +1,26 @@ +/* + * 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 "SkColorSpace_A2B.h" + +SkColorSpace_A2B::SkColorSpace_A2B(SkGammaNamed aCurveNamed, sk_sp aCurve, + sk_sp colorLUT, + SkGammaNamed mCurveNamed, sk_sp mCurve, + const SkMatrix44& matrix, + SkGammaNamed bCurveNamed, sk_sp bCurve, + PCS pcs, sk_sp profileData) + : INHERITED(std::move(profileData)) + , fACurveNamed(aCurveNamed) + , fACurve(std::move(aCurve)) + , fColorLUT(std::move(colorLUT)) + , fMCurveNamed(mCurveNamed) + , fMCurve(std::move(mCurve)) + , fMatrix(matrix) + , fBCurveNamed(bCurveNamed) + , fBCurve(std::move(bCurve)) + , fPCS(pcs) +{} diff --git a/src/core/SkColorSpace_A2B.h b/src/core/SkColorSpace_A2B.h new file mode 100644 index 0000000000..0fc952bd6c --- /dev/null +++ b/src/core/SkColorSpace_A2B.h @@ -0,0 +1,102 @@ +/* + * 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_A2B_DEFINED +#define SkColorSpace_A2B_DEFINED + +#include "SkColorSpace_Base.h" + +// An alternative SkColorSpace that represents all the color space data that +// is stored in an A2B0 ICC tag. This allows us to use alternative profile +// connection spaces (CIELAB instead of just CIEXYZ), use color-lookup-tables +// to do color space transformations not representable as TRC functions or +// matrix operations, as well as have multiple TRC functions. The CLUT also has +// the potential to allow conversion from input color spaces with a different +// number of channels such as CMYK (4) or GRAY (1), but that is not supported yet. +// +// Evaluation is done: A-curve => CLUT => M-curve => Matrix => B-curve +// +// There are also multi-processing-elements in the A2B0 tag which allow you to +// combine these 3 primitives (TRC, CLUT, matrix) in any order/quantitiy, +// but support for that is not implemented. +class SkColorSpace_A2B : public SkColorSpace_Base { +public: + const SkMatrix44* toXYZD50() const override { + // the matrix specified in A2B0 profiles is not necessarily + // a to-XYZ matrix, as to-Lab is supported as well so returning + // that could be misleading. Additionally, B-curves are applied + // after the matrix is, but a toXYZD50 matrix is the last thing + // applied in order to get into the (XYZ) profile connection space. + return nullptr; + } + + const SkMatrix44* fromXYZD50() const override { + // See toXYZD50()'s comment. Also, A2B0 profiles are not supported + // as destination color spaces, so an inverse matrix is never wanted. + return nullptr; + } + + bool onGammaCloseToSRGB() const override { + // There is no single gamma curve in an A2B0 profile + return false; + } + + bool onGammaIsLinear() const override { + // There is no single gamma curve in an A2B0 profile + return false; + } + + SkGammaNamed aCurveNamed() const { return fACurveNamed; } + + const SkGammas* aCurve() const { return fACurve.get(); } + + const SkColorLookUpTable* colorLUT() const { return fColorLUT.get(); } + + SkGammaNamed mCurveNamed() const { return fMCurveNamed; } + + const SkGammas* mCurve() const { return fMCurve.get(); } + + const SkMatrix44& matrix() const { return fMatrix; } + + SkGammaNamed bCurveNamed() const { return fBCurveNamed; } + + const SkGammas* bCurve() const { return fBCurve.get(); } + + // the intermediate profile connection space that this color space + // represents the transformation to + enum class PCS : uint8_t { + kLAB, // CIELAB + kXYZ // CIEXYZ + }; + + PCS pcs() const { return fPCS; } + + Type type() const override { return Type::kA2B; } + +private: + SkColorSpace_A2B(SkGammaNamed aCurveNamed, sk_sp aCurve, + sk_sp colorLUT, + SkGammaNamed mCurveNamed, sk_sp mCurve, + const SkMatrix44& matrix, + SkGammaNamed bCurveNamed, sk_sp bCurve, + PCS pcs, sk_sp profileData); + + const SkGammaNamed fACurveNamed; + sk_sp fACurve; + sk_sp fColorLUT; + const SkGammaNamed fMCurveNamed; + sk_sp fMCurve; + SkMatrix44 fMatrix; + const SkGammaNamed fBCurveNamed; + sk_sp fBCurve; + PCS fPCS; + + friend class SkColorSpace; + typedef SkColorSpace_Base INHERITED; +}; + +#endif diff --git a/src/core/SkColorSpace_Base.h b/src/core/SkColorSpace_Base.h index a188a11dd1..d8470950c4 100644 --- a/src/core/SkColorSpace_Base.h +++ b/src/core/SkColorSpace_Base.h @@ -169,20 +169,32 @@ struct SkColorLookUpTable : public SkRefCnt { class SkColorSpace_Base : public SkColorSpace { public: - SkGammaNamed gammaNamed() const { return fGammaNamed; } - const SkGammas* gammas() const { return fGammas.get(); } - const SkColorLookUpTable* colorLUT() const { return fColorLUT.get(); } - - const SkMatrix44& toXYZD50() const { return fToXYZD50; } - const SkMatrix44& fromXYZD50() const; - - void toDstGammaTables(const uint8_t* tables[3], sk_sp* storage, int numTables) const; + /** + * Describes color space gamut as a transformation to XYZ D50. + * Returns nullptr if color gamut cannot be described in terms of XYZ D50. + */ + virtual const SkMatrix44* toXYZD50() const = 0; /** - * Create an SkColorSpace with the same gamut as this color space, but with linear gamma. + * Describes color space gamut as a transformation from XYZ D50 + * Returns nullptr if color gamut cannot be described in terms of XYZ D50. */ - sk_sp makeLinearGamma(); + virtual const SkMatrix44* fromXYZD50() const = 0; + + virtual bool onGammaCloseToSRGB() const = 0; + + virtual bool onGammaIsLinear() const = 0; + + enum class Type : uint8_t { + kXYZ, + kA2B + }; + + virtual Type type() const = 0; + +protected: + SkColorSpace_Base(sk_sp profileData); private: @@ -199,23 +211,10 @@ private: SkColorSpace_Base(SkGammaNamed gammaNamed, const SkMatrix44& toXYZ); - SkColorSpace_Base(sk_sp colorLUT, SkGammaNamed gammaNamed, - sk_sp gammas, const SkMatrix44& toXYZ, sk_sp profileData); - - sk_sp fColorLUT; - const SkGammaNamed fGammaNamed; - sk_sp fGammas; - sk_sp fProfileData; - - const SkMatrix44 fToXYZD50; - mutable SkMatrix44 fFromXYZD50; - mutable SkOnce fFromXYZOnce; - - mutable sk_sp fDstStorage; - mutable const uint8_t* fToDstGammaTables[3]; - mutable SkOnce fToDstGammaOnce; + sk_sp fProfileData; friend class SkColorSpace; + friend class SkColorSpace_XYZ; friend class ColorSpaceXformTest; friend class ColorSpaceTest; typedef SkColorSpace INHERITED; diff --git a/src/core/SkColorSpace_ICC.cpp b/src/core/SkColorSpace_ICC.cpp index b33e6b5ba1..df665ecd69 100644 --- a/src/core/SkColorSpace_ICC.cpp +++ b/src/core/SkColorSpace_ICC.cpp @@ -6,7 +6,9 @@ */ #include "SkColorSpace.h" +#include "SkColorSpace_A2B.h" #include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkColorSpacePriv.h" #include "SkEndian.h" #include "SkFixed.h" @@ -52,6 +54,7 @@ static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'); static constexpr uint32_t kColorSpace_Profile = SkSetFourByteTag('s', 'p', 'a', 'c'); static constexpr uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' '); +static constexpr uint32_t kLAB_PCSSpace = SkSetFourByteTag('L', 'a', 'b', ' '); static constexpr uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p'); struct ICCProfileHeader { @@ -130,7 +133,7 @@ struct ICCProfileHeader { // TODO (msarett): // All the profiles we've tested so far use XYZ as the profile connection space. - return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space"); + return_if_false(fPCS == kXYZ_PCSSpace || fPCS == kLAB_PCSSpace, "Unsupported PCS space"); return_if_false(fSignature == kACSP_Signature, "Bad signature"); @@ -668,21 +671,19 @@ static bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) { return false; } - // For this matrix to behave like our "to XYZ D50" matrices, it needs to be scaled. - constexpr float scale = 65535.0 / 32768.0; float array[16]; - array[ 0] = scale * SkFixedToFloat(read_big_endian_i32(src)); - array[ 1] = scale * SkFixedToFloat(read_big_endian_i32(src + 4)); - array[ 2] = scale * SkFixedToFloat(read_big_endian_i32(src + 8)); - array[ 3] = scale * SkFixedToFloat(read_big_endian_i32(src + 36)); // translate R - array[ 4] = scale * SkFixedToFloat(read_big_endian_i32(src + 12)); - array[ 5] = scale * SkFixedToFloat(read_big_endian_i32(src + 16)); - array[ 6] = scale * SkFixedToFloat(read_big_endian_i32(src + 20)); - array[ 7] = scale * SkFixedToFloat(read_big_endian_i32(src + 40)); // translate G - array[ 8] = scale * SkFixedToFloat(read_big_endian_i32(src + 24)); - array[ 9] = scale * SkFixedToFloat(read_big_endian_i32(src + 28)); - array[10] = scale * SkFixedToFloat(read_big_endian_i32(src + 32)); - array[11] = scale * SkFixedToFloat(read_big_endian_i32(src + 44)); // translate B + array[ 0] = SkFixedToFloat(read_big_endian_i32(src)); + array[ 1] = SkFixedToFloat(read_big_endian_i32(src + 4)); + array[ 2] = SkFixedToFloat(read_big_endian_i32(src + 8)); + array[ 3] = SkFixedToFloat(read_big_endian_i32(src + 36)); // translate R + array[ 4] = SkFixedToFloat(read_big_endian_i32(src + 12)); + array[ 5] = SkFixedToFloat(read_big_endian_i32(src + 16)); + array[ 6] = SkFixedToFloat(read_big_endian_i32(src + 20)); + array[ 7] = SkFixedToFloat(read_big_endian_i32(src + 40)); // translate G + array[ 8] = SkFixedToFloat(read_big_endian_i32(src + 24)); + array[ 9] = SkFixedToFloat(read_big_endian_i32(src + 28)); + array[10] = SkFixedToFloat(read_big_endian_i32(src + 32)); + array[11] = SkFixedToFloat(read_big_endian_i32(src + 44)); // translate B array[12] = 0.0f; array[13] = 0.0f; array[14] = 0.0f; @@ -702,9 +703,115 @@ static inline SkGammaNamed is_named(const sk_sp& gammas) { return kNonStandard_SkGammaNamed; } +/** + * Parse and load an entire stored curve. Handles invalid gammas as well. + * + * There's nothing to do for the simple cases, but for table gammas we need to actually + * read the table into heap memory. And for parametric gammas, we need to copy over the + * parameter values. + * + * @param gammaNamed Out-variable. The named gamma curve. + * @param gammas Out-variable. The stored gamma curve information. Can be null if + * gammaNamed is a named curve + * @param rTagPtr Pointer to start of the gamma tag. + * @param taglen The size in bytes of the tag + * + * @return false on failure, true on success + */ +static bool parse_and_load_gamma(SkGammaNamed* gammaNamed, sk_sp* gammas, + const uint8_t* rTagPtr, size_t tagLen) +{ + SkGammas::Data rData; + SkColorSpaceTransferFn rParams; + + // On an invalid first gamma, tagBytes remains set as zero. This causes the two + // subsequent to be treated as identical (which is what we want). + size_t tagBytes = 0; + SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, rTagPtr, tagLen); + handle_invalid_gamma(&rType, &rData); + size_t alignedTagBytes = SkAlign4(tagBytes); + + if ((3 * alignedTagBytes <= tagLen) && + !memcmp(rTagPtr, rTagPtr + 1 * alignedTagBytes, tagBytes) && + !memcmp(rTagPtr, rTagPtr + 2 * alignedTagBytes, tagBytes)) + { + if (SkGammas::Type::kNamed_Type == rType) { + *gammaNamed = rData.fNamed; + } else { + size_t allocSize = sizeof(SkGammas); + return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize), + "SkGammas struct is too large to allocate"); + void* memory = sk_malloc_throw(allocSize); + *gammas = sk_sp(new (memory) SkGammas()); + load_gammas(memory, 0, rType, &rData, rParams, rTagPtr); + + (*gammas)->fRedType = rType; + (*gammas)->fGreenType = rType; + (*gammas)->fBlueType = rType; + + (*gammas)->fRedData = rData; + (*gammas)->fGreenData = rData; + (*gammas)->fBlueData = rData; + } + } else { + const uint8_t* gTagPtr = rTagPtr + alignedTagBytes; + tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0; + SkGammas::Data gData; + SkColorSpaceTransferFn gParams; + tagBytes = 0; + SkGammas::Type gType = parse_gamma(&gData, &gParams, &tagBytes, gTagPtr, + tagLen); + handle_invalid_gamma(&gType, &gData); + + alignedTagBytes = SkAlign4(tagBytes); + const uint8_t* bTagPtr = gTagPtr + alignedTagBytes; + tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0; + SkGammas::Data bData; + SkColorSpaceTransferFn bParams; + SkGammas::Type bType = parse_gamma(&bData, &bParams, &tagBytes, bTagPtr, + tagLen); + handle_invalid_gamma(&bType, &bData); + + size_t allocSize = sizeof(SkGammas); + return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize), + "SkGammas struct is too large to allocate"); + return_if_false(safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize), + "SkGammas struct is too large to allocate"); + return_if_false(safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize), + "SkGammas struct is too large to allocate"); + void* memory = sk_malloc_throw(allocSize); + *gammas = sk_sp(new (memory) SkGammas()); + + uint32_t offset = 0; + (*gammas)->fRedType = rType; + offset += load_gammas(memory, offset, rType, &rData, rParams, rTagPtr); + + (*gammas)->fGreenType = gType; + offset += load_gammas(memory, offset, gType, &gData, gParams, gTagPtr); + + (*gammas)->fBlueType = bType; + load_gammas(memory, offset, bType, &bData, bParams, bTagPtr); + + (*gammas)->fRedData = rData; + (*gammas)->fGreenData = gData; + (*gammas)->fBlueData = bData; + } + + if (kNonStandard_SkGammaNamed == *gammaNamed) { + *gammaNamed = is_named(*gammas); + if (kNonStandard_SkGammaNamed != *gammaNamed) { + // No need to keep the gammas struct, the enum is enough. + *gammas = nullptr; + } + } + return true; +} -static bool load_a2b0(sk_sp* colorLUT, SkGammaNamed* gammaNamed, - sk_sp* gammas, SkMatrix44* toXYZ, const uint8_t* src, size_t len) { +static bool load_a2b0(sk_sp* colorLUT, + SkGammaNamed* aCurveNamed, sk_sp* aCurve, + SkGammaNamed* mCurveNamed, sk_sp* mCurve, + SkGammaNamed* bCurveNamed, sk_sp* bCurve, + SkMatrix44* matrix, const uint8_t* src, size_t len) { if (len < 32) { SkColorSpacePrintf("A to B tag is too small (%d bytes).", len); return false; @@ -730,18 +837,13 @@ static bool load_a2b0(sk_sp* colorLUT, SkGammaNamed* gammaNa return false; } - // Read the offsets of each element in the A to B tag. With the exception of A curves and - // B curves (which we do not yet support), we will handle these elements in the order in - // which they should be applied (rather than the order in which they occur in the tag). // If the offset is non-zero it indicates that the element is present. uint32_t offsetToACurves = read_big_endian_i32(src + 28); - uint32_t offsetToBCurves = read_big_endian_i32(src + 12); - if ((0 != offsetToACurves) || (0 != offsetToBCurves)) { - // FIXME (msarett): Handle A and B curves. - // Note that the A curve is technically required in order to have a color LUT. - // However, all the A curves I have seen so far have are just placeholders that - // don't actually transform the data. - SkColorSpacePrintf("Ignoring A and/or B curve. Output may be wrong.\n"); + if (0 != offsetToACurves && offsetToACurves < len) { + const size_t tagLen = len - offsetToACurves; + if (!parse_and_load_gamma(aCurveNamed, aCurve, src + offsetToACurves, tagLen)) { + return false; + } } uint32_t offsetToColorLUT = read_big_endian_i32(src + 24); @@ -749,107 +851,31 @@ static bool load_a2b0(sk_sp* colorLUT, SkGammaNamed* gammaNa if (!load_color_lut(colorLUT, inputChannels, src + offsetToColorLUT, len - offsetToColorLUT)) { SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n"); + return false; } } uint32_t offsetToMCurves = read_big_endian_i32(src + 20); if (0 != offsetToMCurves && offsetToMCurves < len) { - const uint8_t* rTagPtr = src + offsetToMCurves; - size_t tagLen = len - offsetToMCurves; - - SkGammas::Data rData; - SkColorSpaceTransferFn rParams; - - // On an invalid first gamma, tagBytes remains set as zero. This causes the two - // subsequent to be treated as identical (which is what we want). - size_t tagBytes = 0; - SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, rTagPtr, tagLen); - handle_invalid_gamma(&rType, &rData); - size_t alignedTagBytes = SkAlign4(tagBytes); - - if ((3 * alignedTagBytes <= tagLen) && - !memcmp(rTagPtr, rTagPtr + 1 * alignedTagBytes, tagBytes) && - !memcmp(rTagPtr, rTagPtr + 2 * alignedTagBytes, tagBytes)) - { - if (SkGammas::Type::kNamed_Type == rType) { - *gammaNamed = rData.fNamed; - } else { - size_t allocSize = sizeof(SkGammas); - return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize), - "SkGammas struct is too large to allocate"); - void* memory = sk_malloc_throw(allocSize); - *gammas = sk_sp(new (memory) SkGammas()); - load_gammas(memory, 0, rType, &rData, rParams, rTagPtr); - - (*gammas)->fRedType = rType; - (*gammas)->fGreenType = rType; - (*gammas)->fBlueType = rType; - - (*gammas)->fRedData = rData; - (*gammas)->fGreenData = rData; - (*gammas)->fBlueData = rData; - } - } else { - const uint8_t* gTagPtr = rTagPtr + alignedTagBytes; - tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0; - SkGammas::Data gData; - SkColorSpaceTransferFn gParams; - tagBytes = 0; - SkGammas::Type gType = parse_gamma(&gData, &gParams, &tagBytes, gTagPtr, - tagLen); - handle_invalid_gamma(&gType, &gData); - - alignedTagBytes = SkAlign4(tagBytes); - const uint8_t* bTagPtr = gTagPtr + alignedTagBytes; - tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0; - SkGammas::Data bData; - SkColorSpaceTransferFn bParams; - SkGammas::Type bType = parse_gamma(&bData, &bParams, &tagBytes, bTagPtr, - tagLen); - handle_invalid_gamma(&bType, &bData); - - size_t allocSize = sizeof(SkGammas); - return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize), - "SkGammas struct is too large to allocate"); - return_if_false(safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize), - "SkGammas struct is too large to allocate"); - return_if_false(safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize), - "SkGammas struct is too large to allocate"); - void* memory = sk_malloc_throw(allocSize); - *gammas = sk_sp(new (memory) SkGammas()); - - uint32_t offset = 0; - (*gammas)->fRedType = rType; - offset += load_gammas(memory, offset, rType, &rData, rParams, rTagPtr); - - (*gammas)->fGreenType = gType; - offset += load_gammas(memory, offset, gType, &gData, gParams, gTagPtr); - - (*gammas)->fBlueType = bType; - load_gammas(memory, offset, bType, &bData, bParams, bTagPtr); - - (*gammas)->fRedData = rData; - (*gammas)->fGreenData = gData; - (*gammas)->fBlueData = bData; - } - } else { - // Guess sRGB if the chunk is missing a transfer function. - *gammaNamed = kSRGB_SkGammaNamed; - } - - if (kNonStandard_SkGammaNamed == *gammaNamed) { - *gammaNamed = is_named(*gammas); - if (kNonStandard_SkGammaNamed != *gammaNamed) { - // No need to keep the gammas struct, the enum is enough. - *gammas = nullptr; + const size_t tagLen = len - offsetToMCurves; + if (!parse_and_load_gamma(mCurveNamed, mCurve, src + offsetToMCurves, tagLen)) { + return false; } } uint32_t offsetToMatrix = read_big_endian_i32(src + 16); if (0 != offsetToMatrix && offsetToMatrix < len) { - if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) { + if (!load_matrix(matrix, src + offsetToMatrix, len - offsetToMatrix)) { SkColorSpacePrintf("Failed to read matrix from A to B tag.\n"); - toXYZ->setIdentity(); + matrix->setIdentity(); + } + } + + uint32_t offsetToBCurves = read_big_endian_i32(src + 12); + if (0 != offsetToBCurves && offsetToBCurves < len) { + const size_t tagLen = len - offsetToBCurves; + if (!parse_and_load_gamma(bCurveNamed, bCurve, src + offsetToBCurves, tagLen)) { + return false; } } @@ -927,6 +953,11 @@ sk_sp SkColorSpace::NewICC(const void* input, size_t len) { const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); if (r && g && b) { + // Lab PCS means the profile is required to be an n-component LUT-based + // profile, so 3-component matrix-based profiles can only have an XYZ PCS + if (kXYZ_PCSSpace != header.fPCS) { + return_null("Unsupported PCS space"); + } float toXYZ[9]; if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) || !load_xyz(&toXYZ[3], g->addr(base), g->fLength) || @@ -1044,11 +1075,12 @@ sk_sp SkColorSpace::NewICC(const void* input, size_t len) { // It's possible that we'll initially detect non-matching gammas, only for // them to evaluate to the same named gamma curve. gammaNamed = is_named(gammas); - if (kNonStandard_SkGammaNamed == gammaNamed) { - return sk_sp(new SkColorSpace_Base(nullptr, gammaNamed, - std::move(gammas), mat, - std::move(data))); - } + } + + if (kNonStandard_SkGammaNamed == gammaNamed) { + return sk_sp(new SkColorSpace_XYZ(gammaNamed, + std::move(gammas), + mat, std::move(data))); } return SkColorSpace_Base::NewRGB(gammaNamed, mat); @@ -1057,22 +1089,32 @@ sk_sp SkColorSpace::NewICC(const void* input, size_t len) { // Recognize color profile specified by A2B0 tag. const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0); if (a2b0) { - SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed; - sk_sp gammas = nullptr; + // default to Linear transforms for when the curves are not + // in the profile (which is legal behavior for a profile) + SkGammaNamed aCurveNamed = kLinear_SkGammaNamed; + SkGammaNamed mCurveNamed = kLinear_SkGammaNamed; + SkGammaNamed bCurveNamed = kLinear_SkGammaNamed; + sk_sp aCurve = nullptr; + sk_sp mCurve = nullptr; + sk_sp bCurve = nullptr; sk_sp colorLUT = nullptr; - SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); - if (!load_a2b0(&colorLUT, &gammaNamed, &gammas, &toXYZ, a2b0->addr(base), - a2b0->fLength)) { + SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor); + if (!load_a2b0(&colorLUT, &aCurveNamed, &aCurve, &mCurveNamed, &mCurve, + &bCurveNamed, &bCurve, &matrix, a2b0->addr(base), a2b0->fLength)) { return_null("Failed to parse A2B0 tag"); } - if (colorLUT || kNonStandard_SkGammaNamed == gammaNamed) { - return sk_sp(new SkColorSpace_Base(std::move(colorLUT), - gammaNamed, std::move(gammas), - toXYZ, std::move(data))); + SkColorSpace_A2B::PCS pcs = SkColorSpace_A2B::PCS::kLAB; + if (header.fPCS == kXYZ_PCSSpace) { + pcs = SkColorSpace_A2B::PCS::kXYZ; } - return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ); + return sk_sp(new SkColorSpace_A2B(aCurveNamed, std::move(aCurve), + std::move(colorLUT), + mCurveNamed, std::move(mCurve), + matrix, + bCurveNamed, std::move(bCurve), + pcs, std::move(data))); } } default: @@ -1217,13 +1259,17 @@ sk_sp SkColorSpace_Base::writeToICC() const { if (fProfileData) { return fProfileData; } + // Profile Data is be mandatory for A2B0 Color Spaces + SkASSERT(type() == Type::kXYZ); // The client may create an SkColorSpace using an SkMatrix44, but currently we only // support writing profiles with 3x3 matrices. // TODO (msarett): Fix this! - if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) || - 0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) || - 0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3)) + const SkColorSpace_XYZ* thisXYZ = static_cast(this); + const SkMatrix44& toXYZD50 = *thisXYZ->toXYZD50(); + if (0.0f != toXYZD50.getFloat(3, 0) || 0.0f != toXYZD50.getFloat(3, 1) || + 0.0f != toXYZD50.getFloat(3, 2) || 0.0f != toXYZD50.getFloat(0, 3) || + 0.0f != toXYZD50.getFloat(1, 3) || 0.0f != toXYZD50.getFloat(2, 3)) { return nullptr; } @@ -1244,15 +1290,15 @@ sk_sp SkColorSpace_Base::writeToICC() const { ptr += sizeof(gEmptyTextTag); // Write XYZ tags - write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0); + write_xyz_tag((uint32_t*) ptr, toXYZD50, 0); ptr += kTAG_XYZ_Bytes; - write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1); + write_xyz_tag((uint32_t*) ptr, toXYZD50, 1); ptr += kTAG_XYZ_Bytes; - write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2); + write_xyz_tag((uint32_t*) ptr, toXYZD50, 2); ptr += kTAG_XYZ_Bytes; // Write TRC tags - SkGammaNamed gammaNamed = this->gammaNamed(); + SkGammaNamed gammaNamed = thisXYZ->gammaNamed(); if (kNonStandard_SkGammaNamed == gammaNamed) { // FIXME (msarett): // Write the correct gamma representation rather than 2.2f. diff --git a/src/core/SkColorSpace_XYZ.cpp b/src/core/SkColorSpace_XYZ.cpp new file mode 100644 index 0000000000..1f62245d73 --- /dev/null +++ b/src/core/SkColorSpace_XYZ.cpp @@ -0,0 +1,78 @@ +/* + * 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 "SkColorSpace_XYZ.h" +#include "SkColorSpaceXform_Base.h" + +static constexpr float gSRGB_toXYZD50[] { + 0.4358f, 0.3853f, 0.1430f, // Rx, Gx, Bx + 0.2224f, 0.7170f, 0.0606f, // Ry, Gy, Gz + 0.0139f, 0.0971f, 0.7139f, // Rz, Gz, Bz +}; + +SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50) + : INHERITED(nullptr) + , fGammaNamed(gammaNamed) + , fGammas(nullptr) + , fToXYZD50(toXYZD50) + , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) +{} + +SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp gammas, + const SkMatrix44& toXYZD50, sk_sp profileData) + : INHERITED(std::move(profileData)) + , fGammaNamed(gammaNamed) + , fGammas(std::move(gammas)) + , fToXYZD50(toXYZD50) + , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) +{} + +const SkMatrix44* SkColorSpace_XYZ::fromXYZD50() const { + fFromXYZOnce([this] { + if (!fToXYZD50.invert(&fFromXYZD50)) { + // If a client gives us a dst gamut with a transform that we can't invert, we will + // simply give them back a transform to sRGB gamut. + SkDEBUGFAIL("Non-invertible XYZ matrix, defaulting to sRGB"); + SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor); + srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50); + srgbToxyzD50.invert(&fFromXYZD50); + } + }); + return &fFromXYZD50; +} + +bool SkColorSpace_XYZ::onGammaCloseToSRGB() const { + return kSRGB_SkGammaNamed == fGammaNamed || k2Dot2Curve_SkGammaNamed == fGammaNamed; +} + +bool SkColorSpace_XYZ::onGammaIsLinear() const { + return kLinear_SkGammaNamed == fGammaNamed; +} + +sk_sp SkColorSpace_XYZ::makeLinearGamma() { + if (this->gammaIsLinear()) { + return sk_ref_sp(this); + } + return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, fToXYZD50); +} + +void SkColorSpace_XYZ::toDstGammaTables(const uint8_t* tables[3], sk_sp* storage, + int numTables) const { + fToDstGammaOnce([this, numTables] { + const bool gammasAreMatching = numTables <= 1; + fDstStorage = + SkData::MakeUninitialized(numTables * SkColorSpaceXform_Base::kDstGammaTableSize); + SkColorSpaceXform_Base::BuildDstGammaTables(fToDstGammaTables, + (uint8_t*) fDstStorage->writable_data(), this, + gammasAreMatching); + }); + + *storage = fDstStorage; + tables[0] = fToDstGammaTables[0]; + tables[1] = fToDstGammaTables[1]; + tables[2] = fToDstGammaTables[2]; +} diff --git a/src/core/SkColorSpace_XYZ.h b/src/core/SkColorSpace_XYZ.h new file mode 100644 index 0000000000..72ec3f1b67 --- /dev/null +++ b/src/core/SkColorSpace_XYZ.h @@ -0,0 +1,58 @@ +/* + * 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_XYZ_DEFINED +#define SkColorSpace_XYZ_DEFINED + +#include "SkColorSpace_Base.h" +#include "SkData.h" +#include "SkOnce.h" + +class SkColorSpace_XYZ : public SkColorSpace_Base { +public: + const SkMatrix44* toXYZD50() const override { return &fToXYZD50; } + + const SkMatrix44* fromXYZD50() const override; + + bool onGammaCloseToSRGB() const override; + + bool onGammaIsLinear() const override; + + Type type() const override { return Type::kXYZ; } + + sk_sp makeLinearGamma(); + + SkGammaNamed gammaNamed() const { return fGammaNamed; } + + const SkGammas* gammas() const { return fGammas.get(); } + + void toDstGammaTables(const uint8_t* tables[3], sk_sp* storage, int numTables) const; + +private: + SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZ); + + SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp gammas, + const SkMatrix44& toXYZ, sk_sp profileData); + + const SkGammaNamed fGammaNamed; + sk_sp fGammas; + const SkMatrix44 fToXYZD50; + + mutable SkMatrix44 fFromXYZD50; + mutable SkOnce fFromXYZOnce; + + mutable sk_sp fDstStorage; + mutable const uint8_t* fToDstGammaTables[3]; + mutable SkOnce fToDstGammaOnce; + + friend class SkColorSpace; + friend class SkColorSpace_Base; + friend class ColorSpaceXformTest; + typedef SkColorSpace_Base INHERITED; +}; + +#endif diff --git a/src/effects/gradients/SkGradientShader.cpp b/src/effects/gradients/SkGradientShader.cpp index 03f03b20b1..6c99b37ad7 100644 --- a/src/effects/gradients/SkGradientShader.cpp +++ b/src/effects/gradients/SkGradientShader.cpp @@ -6,7 +6,7 @@ */ #include "Sk4fLinearGradient.h" -#include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkGradientShaderPriv.h" #include "SkHalf.h" #include "SkLinearGradient.h" @@ -1747,7 +1747,8 @@ GrGradientEffect::RandomGradientParams::RandomGradientParams(SkRandom* random) { if (fUseColors4f) { fColorSpace = GrTest::TestColorSpace(random); if (fColorSpace) { - fColorSpace = as_CSB(fColorSpace)->makeLinearGamma(); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(fColorSpace)->type()); + fColorSpace = static_cast(fColorSpace.get())->makeLinearGamma(); } } diff --git a/src/gpu/GrColorSpaceXform.cpp b/src/gpu/GrColorSpaceXform.cpp index d2270fafdd..ef96087147 100644 --- a/src/gpu/GrColorSpaceXform.cpp +++ b/src/gpu/GrColorSpaceXform.cpp @@ -49,8 +49,15 @@ sk_sp GrColorSpaceXform::Make(SkColorSpace* src, SkColorSpace return nullptr; } + + const SkMatrix44* toXYZD50 = as_CSB(src)->toXYZD50(); + const SkMatrix44* fromXYZD50 = as_CSB(dst)->fromXYZD50(); + if (!toXYZD50 || !fromXYZD50) { + // unsupported colour spaces -- cannot specify gamut as a matrix + return nullptr; + } SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); - srcToDst.setConcat(as_CSB(dst)->fromXYZD50(), as_CSB(src)->toXYZD50()); + srcToDst.setConcat(*fromXYZD50, *toXYZD50); if (matrix_is_almost_identity(srcToDst)) { return nullptr; diff --git a/tests/CodecTest.cpp b/tests/CodecTest.cpp index a6058a9c35..45455b4405 100644 --- a/tests/CodecTest.cpp +++ b/tests/CodecTest.cpp @@ -10,7 +10,7 @@ #include "SkBitmap.h" #include "SkCodec.h" #include "SkCodecImageGenerator.h" -#include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkData.h" #include "SkImageEncoder.h" #include "SkFrontBufferedStream.h" @@ -1192,7 +1192,9 @@ static void test_conversion_possible(skiatest::Reporter* r, const char* path, REPORTER_ASSERT(r, SkCodec::kUnimplemented == result); } - infoF16 = infoF16.makeColorSpace(as_CSB(infoF16.colorSpace())->makeLinearGamma()); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(infoF16.colorSpace())->type()); + SkColorSpace_XYZ* csXYZ = static_cast(infoF16.colorSpace()); + infoF16 = infoF16.makeColorSpace(csXYZ->makeLinearGamma()); result = codec->getPixels(infoF16, bm.getPixels(), bm.rowBytes()); REPORTER_ASSERT(r, SkCodec::kSuccess == result); result = codec->startScanlineDecode(infoF16); diff --git a/tests/ColorSpaceTest.cpp b/tests/ColorSpaceTest.cpp index aa0a33b407..04d401012d 100644 --- a/tests/ColorSpaceTest.cpp +++ b/tests/ColorSpaceTest.cpp @@ -9,6 +9,7 @@ #include "SkCodec.h" #include "SkColorSpace.h" #include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "Test.h" #include "png.h" @@ -22,9 +23,11 @@ static void test_space(skiatest::Reporter* r, SkColorSpace* space, const SkGammaNamed expectedGamma) { REPORTER_ASSERT(r, nullptr != space); - REPORTER_ASSERT(r, expectedGamma == as_CSB(space)->gammaNamed()); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(space)->type()); + SkColorSpace_XYZ* csXYZ = static_cast(space); + REPORTER_ASSERT(r, expectedGamma == csXYZ->gammaNamed()); - const SkMatrix44& mat = as_CSB(space)->toXYZD50(); + const SkMatrix44& mat = *csXYZ->toXYZD50(); const float src[] = { 1, 0, 0, 1, 0, 1, 0, 1, @@ -120,8 +123,9 @@ DEF_TEST(ColorSpaceSRGBLinearCompare, r) { sk_sp namedColorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGBLinear_Named); // Create the linear sRGB color space via the sRGB color space's makeLinearGamma() - sk_sp viaSrgbColorSpace = - as_CSB(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named))->makeLinearGamma(); + auto srgb = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + auto srgbXYZ = static_cast(srgb.get()); + sk_sp viaSrgbColorSpace = srgbXYZ->makeLinearGamma(); REPORTER_ASSERT(r, namedColorSpace == viaSrgbColorSpace); // Create a linear sRGB color space by value @@ -166,8 +170,12 @@ DEF_TEST(ColorSpaceWriteICC, r) { sk_sp newMonitorData = ColorSpaceTest::WriteToICC(monitorSpace.get()); sk_sp newMonitorSpace = SkColorSpace::NewICC(newMonitorData->data(), newMonitorData->size()); - REPORTER_ASSERT(r, as_CSB(monitorSpace)->toXYZD50() == as_CSB(newMonitorSpace)->toXYZD50()); - REPORTER_ASSERT(r, as_CSB(monitorSpace)->gammaNamed() == as_CSB(newMonitorSpace)->gammaNamed()); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(monitorSpace)->type()); + SkColorSpace_XYZ* monitorSpaceXYZ = static_cast(monitorSpace.get()); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(newMonitorSpace)->type()); + SkColorSpace_XYZ* newMonitorSpaceXYZ = static_cast(newMonitorSpace.get()); + REPORTER_ASSERT(r, *monitorSpaceXYZ->toXYZD50() == *newMonitorSpaceXYZ->toXYZD50()); + REPORTER_ASSERT(r, monitorSpaceXYZ->gammaNamed() == newMonitorSpaceXYZ->gammaNamed()); } DEF_TEST(ColorSpace_Named, r) { @@ -184,7 +192,9 @@ DEF_TEST(ColorSpace_Named, r) { auto cs = SkColorSpace::NewNamed(rec.fNamed); REPORTER_ASSERT(r, cs); if (cs) { - REPORTER_ASSERT(r, rec.fExpectedGamma == as_CSB(cs)->gammaNamed()); + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(cs)->type()); + SkColorSpace_XYZ* csXYZ = static_cast(cs.get()); + REPORTER_ASSERT(r, rec.fExpectedGamma == csXYZ->gammaNamed()); } } diff --git a/tests/ColorSpaceXformTest.cpp b/tests/ColorSpaceXformTest.cpp index 6cf0dbef21..477e61acdd 100644 --- a/tests/ColorSpaceXformTest.cpp +++ b/tests/ColorSpaceXformTest.cpp @@ -11,6 +11,7 @@ #include "SkColorPriv.h" #include "SkColorSpace.h" #include "SkColorSpace_Base.h" +#include "SkColorSpace_XYZ.h" #include "SkColorSpaceXform_Base.h" #include "Test.h" @@ -18,11 +19,11 @@ class ColorSpaceXformTest { public: static std::unique_ptr CreateIdentityXform(const sk_sp& gammas) { // Logically we can pass any matrix here. For simplicty, pass I(), i.e. D50 XYZ gamut. - sk_sp space(new SkColorSpace_Base( - nullptr, kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr)); + sk_sp space(new SkColorSpace_XYZ( + kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr)); // Use special testing entry point, so we don't skip the xform, even though src == dst. - return SlowIdentityXform(space.get()); + return SlowIdentityXform(static_cast(space.get())); } }; @@ -175,25 +176,3 @@ DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) { test_identity_xform(r, gammas, true); } -DEF_TEST(ColorSpaceXform_applyCLUTMemoryAccess, r) { - // buffers larger than 1024 (or 256 in GOOGLE3) will force ColorSpaceXform_Base::apply() - // to heap-allocate a buffer that is used for CLUT application, and this test is here to - // ensure that it no longer causes potential invalid memory accesses when this happens - const size_t len = 2048; - SkAutoTMalloc src(len); - SkAutoTMalloc dst(len); - for (uint32_t i = 0; i < len; ++i) { - src[i] = i; - } - // this ICC profile has a CLUT in it - const SkString filename(GetResourcePath("icc_profiles/upperRight.icc")); - sk_sp iccData = SkData::MakeFromFileName(filename.c_str()); - REPORTER_ASSERT_MESSAGE(r, iccData, "upperRight.icc profile required for test"); - sk_sp srcSpace = SkColorSpace::NewICC(iccData->bytes(), iccData->size()); - sk_sp dstSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); - auto xform = SkColorSpaceXform::New(srcSpace.get(), dstSpace.get()); - bool result = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, dst.get(), - SkColorSpaceXform::kRGBA_8888_ColorFormat, src.get(), len, - kUnpremul_SkAlphaType); - REPORTER_ASSERT(r, result); -} diff --git a/tests/SurfaceTest.cpp b/tests/SurfaceTest.cpp index 241ddb1dbc..180f2d985b 100644 --- a/tests/SurfaceTest.cpp +++ b/tests/SurfaceTest.cpp @@ -921,9 +921,10 @@ static void test_surface_creation_and_snapshot_with_color_space( auto srgbColorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); auto adobeColorSpace = SkColorSpace::NewNamed(SkColorSpace::kAdobeRGB_Named); - SkMatrix44 srgbMatrix = as_CSB(srgbColorSpace)->toXYZD50(); + const SkMatrix44* srgbMatrix = as_CSB(srgbColorSpace)->toXYZD50(); + SkASSERT(srgbMatrix); const float oddGamma[] = { 2.4f, 2.4f, 2.4f }; - auto oddColorSpace = SkColorSpace::NewRGB(oddGamma, srgbMatrix); + auto oddColorSpace = SkColorSpace::NewRGB(oddGamma, *srgbMatrix); auto linearColorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGBLinear_Named); const struct { diff --git a/tests/TestConfigParsing.cpp b/tests/TestConfigParsing.cpp index 412be73626..ec6d721908 100644 --- a/tests/TestConfigParsing.cpp +++ b/tests/TestConfigParsing.cpp @@ -126,22 +126,28 @@ DEF_TEST(ParseConfigs_DefaultConfigs, reporter) { REPORTER_ASSERT(reporter, configs[25]->asConfigGpu()->getColorType() == kRGBA_F16_SkColorType); REPORTER_ASSERT(reporter, configs[25]->asConfigGpu()->getColorSpace()); REPORTER_ASSERT(reporter, configs[25]->asConfigGpu()->getColorSpace()->gammaIsLinear()); - REPORTER_ASSERT(reporter, as_CSB(configs[25]->asConfigGpu()->getColorSpace())->toXYZD50() == - as_CSB(srgbColorSpace)->toXYZD50()); + const SkMatrix44* srgbXYZ = as_CSB(srgbColorSpace)->toXYZD50(); + SkASSERT(srgbXYZ); + const SkMatrix44* config25XYZ = + as_CSB(configs[25]->asConfigGpu()->getColorSpace())->toXYZD50(); + SkASSERT(config25XYZ); + REPORTER_ASSERT(reporter, *config25XYZ == *srgbXYZ); REPORTER_ASSERT(reporter, configs[26]->asConfigGpu()->getColorType() == kRGBA_8888_SkColorType); REPORTER_ASSERT(reporter, configs[26]->asConfigGpu()->getColorSpace() == srgbColorSpace.get()); REPORTER_ASSERT(reporter, configs[41]->asConfigGpu()->getColorType() == kRGBA_F16_SkColorType); REPORTER_ASSERT(reporter, configs[41]->asConfigGpu()->getColorSpace()); REPORTER_ASSERT(reporter, configs[41]->asConfigGpu()->getColorSpace()->gammaIsLinear()); - REPORTER_ASSERT(reporter, as_CSB(configs[41]->asConfigGpu()->getColorSpace())->toXYZD50() != - as_CSB(srgbColorSpace)->toXYZD50()); + const SkMatrix44* config41XYZ = + as_CSB(configs[41]->asConfigGpu()->getColorSpace())->toXYZD50(); + SkASSERT(config41XYZ); + REPORTER_ASSERT(reporter, *config41XYZ != *srgbXYZ); + REPORTER_ASSERT(reporter, configs[33]->asConfigGpu()->getContextType() == + GrContextFactory::kGL_ContextType); REPORTER_ASSERT(reporter, configs[42]->asConfigGpu()->getColorType() == kRGBA_F16_SkColorType); REPORTER_ASSERT(reporter, configs[42]->asConfigGpu()->getColorSpace()); REPORTER_ASSERT(reporter, configs[42]->asConfigGpu()->getColorSpace()->gammaIsLinear()); - REPORTER_ASSERT(reporter, as_CSB(configs[42]->asConfigGpu()->getColorSpace())->toXYZD50() != - as_CSB(srgbColorSpace)->toXYZD50()); - REPORTER_ASSERT(reporter, configs[33]->asConfigGpu()->getContextType() == - GrContextFactory::kGL_ContextType); + REPORTER_ASSERT(reporter, *as_CSB(configs[42]->asConfigGpu()->getColorSpace())->toXYZD50() != + *as_CSB(srgbColorSpace)->toXYZD50()); REPORTER_ASSERT(reporter, configs[33]->asConfigGpu()->getUseInstanced()); REPORTER_ASSERT(reporter, configs[34]->asConfigGpu()->getContextType() == GrContextFactory::kGL_ContextType); diff --git a/tools/flags/SkCommonFlagsConfig.cpp b/tools/flags/SkCommonFlagsConfig.cpp index 107ab3ad15..9baada89e8 100644 --- a/tools/flags/SkCommonFlagsConfig.cpp +++ b/tools/flags/SkCommonFlagsConfig.cpp @@ -134,7 +134,6 @@ static const char configExtendedHelp[] = "\t Options:\n" "\t\tsrgb\t\t\tsRGB gamut.\n" "\t\twide\t\t\tWide Gamut RGB.\n" - "\t\tnarrow\t\t\tNarrow Gamut RGB.\n" "\tdit\ttype: bool\tdefault: false.\n" "\t Use device independent text.\n" "\tnvpr\ttype: bool\tdefault: false.\n" @@ -284,7 +283,9 @@ static bool parse_option_gpu_color(const SkString& value, } // First, figure out color gamut that we'll work in (default to sRGB) - sk_sp colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + const bool linearGamma = commands[0].equals("f16"); + *outColorSpace = SkColorSpace::NewNamed(linearGamma ? SkColorSpace::kSRGBLinear_Named + : SkColorSpace::kSRGB_Named); if (commands.count() == 2) { if (commands[1].equals("srgb")) { // sRGB gamut (which is our default) @@ -297,8 +298,10 @@ static bool parse_option_gpu_color(const SkString& value, }; SkMatrix44 wideGamutRGBMatrix(SkMatrix44::kUninitialized_Constructor); wideGamutRGBMatrix.set3x3RowMajorf(gWideGamutRGB_toXYZD50); - colorSpace = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma, - wideGamutRGBMatrix); + *outColorSpace = SkColorSpace::NewRGB(linearGamma + ? SkColorSpace::kLinear_RenderTargetGamma + : SkColorSpace::kSRGB_RenderTargetGamma, + wideGamutRGBMatrix); } else if (commands[1].equals("narrow")) { // NarrowGamut RGB (an artifically smaller than sRGB gamut) SkColorSpacePrimaries primaries ={ @@ -309,8 +312,10 @@ static bool parse_option_gpu_color(const SkString& value, }; SkMatrix44 narrowGamutRGBMatrix(SkMatrix44::kUninitialized_Constructor); primaries.toXYZD50(&narrowGamutRGBMatrix); - colorSpace = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma, - narrowGamutRGBMatrix); + *outColorSpace = SkColorSpace::NewRGB(linearGamma + ? SkColorSpace::kLinear_RenderTargetGamma + : SkColorSpace::kSRGB_RenderTargetGamma, + narrowGamutRGBMatrix); } else { // Unknown color gamut return false; @@ -320,12 +325,10 @@ static bool parse_option_gpu_color(const SkString& value, // Now pick a color type if (commands[0].equals("f16")) { *outColorType = kRGBA_F16_SkColorType; - *outColorSpace = as_CSB(colorSpace)->makeLinearGamma(); return true; } if (commands[0].equals("srgb")) { *outColorType = kRGBA_8888_SkColorType; - *outColorSpace = colorSpace; return true; } return false; diff --git a/tools/visualize_color_gamut.cpp b/tools/visualize_color_gamut.cpp index b45996bc8d..6d08ba213b 100644 --- a/tools/visualize_color_gamut.cpp +++ b/tools/visualize_color_gamut.cpp @@ -141,13 +141,17 @@ int main(int argc, char** argv) { // Draw the sRGB gamut if requested. if (FLAGS_sRGB) { sk_sp sRGBSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); - draw_gamut(&canvas, as_CSB(sRGBSpace)->toXYZD50(), "sRGB", 0xFFFF9394, false); + const SkMatrix44* mat = as_CSB(sRGBSpace)->toXYZD50(); + SkASSERT(mat); + draw_gamut(&canvas, *mat, "sRGB", 0xFFFF9394, false); } // Draw the Adobe RGB gamut if requested. if (FLAGS_adobeRGB) { sk_sp adobeRGBSpace = SkColorSpace::NewNamed(SkColorSpace::kAdobeRGB_Named); - draw_gamut(&canvas, as_CSB(adobeRGBSpace)->toXYZD50(), "Adobe RGB", 0xFF31a9e1, false); + const SkMatrix44* mat = as_CSB(adobeRGBSpace)->toXYZD50(); + SkASSERT(mat); + draw_gamut(&canvas, *mat, "Adobe RGB", 0xFF31a9e1, false); } // Draw gamut for the input image. @@ -156,7 +160,9 @@ int main(int argc, char** argv) { SkDebugf("Image had no embedded color space information. Defaulting to sRGB.\n"); colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); } - draw_gamut(&canvas, as_CSB(colorSpace)->toXYZD50(), input, 0xFF000000, true); + const SkMatrix44* mat = as_CSB(colorSpace)->toXYZD50(); + SkASSERT(mat); + draw_gamut(&canvas, *mat, input, 0xFF000000, true); // Finally, encode the result to the output file. sk_sp out(SkImageEncoder::EncodeData(gamut, SkImageEncoder::kPNG_Type, 100));