diff options
-rw-r--r-- | dm/DMSrcSink.cpp | 6 | ||||
-rw-r--r-- | gm/labpcsdemo.cpp | 192 | ||||
-rw-r--r-- | gn/gm.gni | 1 | ||||
-rw-r--r-- | src/codec/SkJpegCodec.cpp | 19 | ||||
-rw-r--r-- | src/core/SkColorLookUpTable.cpp | 51 | ||||
-rw-r--r-- | src/core/SkColorLookUpTable.h | 36 | ||||
-rw-r--r-- | src/core/SkColorSpace.cpp | 13 | ||||
-rw-r--r-- | src/core/SkColorSpaceXform_A2B.cpp | 81 | ||||
-rw-r--r-- | src/core/SkColorSpaceXform_A2B.h | 12 | ||||
-rw-r--r-- | src/core/SkColorSpace_A2B.cpp | 7 | ||||
-rw-r--r-- | src/core/SkColorSpace_A2B.h | 55 | ||||
-rw-r--r-- | src/core/SkColorSpace_Base.h | 61 | ||||
-rw-r--r-- | src/core/SkColorSpace_ICC.cpp | 604 | ||||
-rw-r--r-- | src/core/SkColorSpace_XYZ.cpp | 5 | ||||
-rw-r--r-- | src/core/SkRasterPipeline.h | 3 | ||||
-rw-r--r-- | src/opts/SkRasterPipeline_opts.h | 23 | ||||
-rw-r--r-- | tests/ColorSpaceXformTest.cpp | 101 |
17 files changed, 618 insertions, 652 deletions
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index 31a3bc7059..0a54702d08 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -1018,7 +1018,11 @@ 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); + // FIXME (raftias): + // This should be a fatal error. We need to add support for + // A2B images in SkColorSpaceXform. + return Error::Nonfatal(SkStringPrintf("Couldn't getPixels %s. Error code %d", + fPath.c_str(), r)); } switch (fMode) { diff --git a/gm/labpcsdemo.cpp b/gm/labpcsdemo.cpp new file mode 100644 index 0000000000..26e48a8890 --- /dev/null +++ b/gm/labpcsdemo.cpp @@ -0,0 +1,192 @@ +/* + * 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 <cmath> +#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" + +/** + * 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<SkCodec> 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<SkData> iccData = SkData::MakeFromFileName(iccFilename.c_str()); + if (iccData == nullptr) { + return; + } + sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeICC(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<SkColorSpace_A2B*>(colorSpace.get()); + const SkColorLookUpTable* colorLUT = nullptr; + bool printConversions = false; + // We're skipping evaluating the TRCs and the matrix here since they aren't + // in the ICC profile initially used here. + for (int e = 0; e < cs.count(); ++e) { + switch (cs.element(e).type()) { + case SkColorSpace_A2B::Element::Type::kGammaNamed: + SkASSERT(kLinear_SkGammaNamed == cs.element(e).gammaNamed()); + break; + case SkColorSpace_A2B::Element::Type::kGammas: + SkASSERT(false); + break; + case SkColorSpace_A2B::Element::Type::kCLUT: + colorLUT = &cs.element(e).colorLUT(); + break; + case SkColorSpace_A2B::Element::Type::kMatrix: + SkASSERT(cs.element(e).matrix().isIdentity()); + break; + } + } + SkASSERT(colorLUT); + 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 }; + + colorLUT->interp3D(lab, lab); + + // 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<SkScalar>((col+1) * (freeWidth / 3) + col*imageWidth), + static_cast<SkScalar>(freeHeight / 2)); + ++col; + } + +private: + const int fWidth; + const int fHeight; + + typedef skiagm::GM INHERITED; +}; + +DEF_GM( return new LabPCSDemoGM; ) @@ -171,6 +171,7 @@ gm_sources = [ "$_gm/imagetoyuvplanes.cpp", "$_gm/internal_links.cpp", "$_gm/inversepaths.cpp", + "$_gm/labpcsdemo.cpp", "$_gm/largeglyphblur.cpp", "$_gm/lattice.cpp", "$_gm/lcdblendmodes.cpp", diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp index 7a264d5d99..69b80273df 100644 --- a/src/codec/SkJpegCodec.cpp +++ b/src/codec/SkJpegCodec.cpp @@ -11,7 +11,6 @@ #include "SkJpegDecoderMgr.h" #include "SkCodecPriv.h" #include "SkColorPriv.h" -#include "SkColorSpace_Base.h" #include "SkStream.h" #include "SkTemplates.h" #include "SkTypes.h" @@ -230,18 +229,7 @@ bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, sk_sp<SkData> iccData = get_icc_profile(decoderMgr->dinfo()); sk_sp<SkColorSpace> colorSpace = nullptr; if (iccData) { - SkColorSpace_Base::InputColorFormat inputColorFormat = - SkColorSpace_Base::InputColorFormat::kRGB; - switch (decoderMgr->dinfo()->jpeg_color_space) { - case JCS_CMYK: - case JCS_YCCK: - inputColorFormat = SkColorSpace_Base::InputColorFormat::kCMYK; - break; - default: - break; - } - colorSpace = SkColorSpace_Base::MakeICC(iccData->data(), iccData->size(), - inputColorFormat); + colorSpace = SkColorSpace::MakeICC(iccData->data(), iccData->size()); if (!colorSpace) { SkCodecPrintf("Could not create SkColorSpace from ICC data.\n"); } @@ -380,6 +368,9 @@ bool SkJpegCodec::setOutputColorSpace(const SkImageInfo& dstInfo) { // we must do it ourselves. J_COLOR_SPACE encodedColorType = fDecoderMgr->dinfo()->jpeg_color_space; bool isCMYK = (JCS_CMYK == encodedColorType || JCS_YCCK == encodedColorType); + if (isCMYK && this->colorXform()) { + return false; + } // Check for valid color types and set the output color space switch (dstInfo.colorType()) { @@ -578,7 +569,7 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo, SkASSERT(1 == dinfo->rec_outbuf_height); J_COLOR_SPACE colorSpace = dinfo->out_color_space; - if (JCS_CMYK == colorSpace && nullptr == this->colorXform()) { + if (JCS_CMYK == colorSpace) { this->initializeSwizzler(dstInfo, options); } diff --git a/src/core/SkColorLookUpTable.cpp b/src/core/SkColorLookUpTable.cpp index e2da357234..eb832b3214 100644 --- a/src/core/SkColorLookUpTable.cpp +++ b/src/core/SkColorLookUpTable.cpp @@ -8,23 +8,7 @@ #include "SkColorLookUpTable.h" #include "SkFloatingPoint.h" -void SkColorLookUpTable::interp(float* dst, const float* src) const { - if (fInputChannels == 3) { - interp3D(dst, src); - } else { - SkASSERT(dst != src); - // index gets initialized as the algorithm proceeds by interpDimension. - // It's just there to store the choice of low/high so far. - int index[kMaxColorChannels]; - for (uint8_t outputDimension = 0; outputDimension < kOutputChannels; ++outputDimension) { - dst[outputDimension] = interpDimension(src, fInputChannels - 1, outputDimension, - index); - } - } -} - -void SkColorLookUpTable::interp3D(float* dst, const float* src) const { - SkASSERT(3 == kOutputChannels); +void SkColorLookUpTable::interp3D(float dst[3], float src[3]) const { // Call the src components x, y, and z. const uint8_t maxX = fGridPoints[0] - 1; const uint8_t maxY = fGridPoints[1] - 1; @@ -127,36 +111,3 @@ void SkColorLookUpTable::interp3D(float* dst, const float* src) const { ptr++; } } - -float SkColorLookUpTable::interpDimension(const float* src, int inputDimension, - int outputDimension, - int index[kMaxColorChannels]) const { - // Base case. We've already decided whether to use the low or high point for each dimension - // which is stored inside of index[] where index[i] gives the point in the CLUT to use for - // input dimension i. - if (-1 == inputDimension) { - // compute index into CLUT and look up the colour - int outputIndex = outputDimension; - int indexMultiplier = kOutputChannels; - for (int i = fInputChannels - 1; i >= 0; --i) { - outputIndex += index[i] * indexMultiplier; - indexMultiplier *= fGridPoints[i]; - } - return table()[outputIndex]; - } - // for each dimension (input channel), try both the low and high point for it - // and then do the same recursively for the later dimensions. - // Finally, we need to LERP the results. ie LERP X then LERP Y then LERP Z. - const float x = src[inputDimension] * (fGridPoints[inputDimension] - 1); - // try the low point for this dimension - index[inputDimension] = sk_float_floor2int(x); - const float diff = x - index[inputDimension]; - // and recursively LERP all sub-dimensions with the current dimension fixed to the low point - const float lo = interpDimension(src, inputDimension - 1, outputDimension, index); - // now try the high point for this dimension - index[inputDimension] = sk_float_ceil2int(x); - // and recursively LERP all sub-dimensions with the current dimension fixed to the high point - const float hi = interpDimension(src, inputDimension - 1, outputDimension, index); - // then LERP the results based on the current dimension - return (1 - diff) * lo + diff * hi; -} diff --git a/src/core/SkColorLookUpTable.h b/src/core/SkColorLookUpTable.h index e563b2d4a2..020a953700 100644 --- a/src/core/SkColorLookUpTable.h +++ b/src/core/SkColorLookUpTable.h @@ -11,49 +11,25 @@ #include "SkRefCnt.h" #include "SkTemplates.h" -static constexpr uint8_t kMaxColorChannels = 4; - class SkColorLookUpTable : public SkRefCnt { public: static constexpr uint8_t kOutputChannels = 3; - SkColorLookUpTable(uint8_t inputChannels, const uint8_t gridPoints[kMaxColorChannels]) - : fInputChannels(inputChannels) { - SkASSERT(inputChannels >= 1 && inputChannels <= kMaxColorChannels); - memcpy(fGridPoints, gridPoints, fInputChannels * sizeof(uint8_t)); + SkColorLookUpTable(uint8_t inputChannels, const uint8_t gridPoints[3]) { + SkASSERT(3 == inputChannels); + memcpy(fGridPoints, gridPoints, 3 * sizeof(uint8_t)); } - /** - * If fInputChannels == kOutputChannels == 3, performs tetrahedral interpolation, otherwise - * performs multilinear interpolation (ie LERP for n =1, bilinear for n=2, trilinear for n=3) - * with fInputChannels input dimensions and kOutputChannels output dimensions. - * |dst| can be |src| only when fInputChannels == kOutputChannels == 3 - * |dst| is the destination pixel, must have at least kOutputChannels elements. - * |src| is the source pixel, must have at least fInputChannels elements. - */ - void interp(float* dst, const float* src) const; - - int inputChannels() const { return fInputChannels; } - - int outputChannels() const { return kOutputChannels; } + void interp3D(float dst[3], float src[3]) const; private: const float* table() const { return SkTAddOffset<const float>(this, sizeof(SkColorLookUpTable)); } - /** - * Performs tetrahedral interpolation with 3 input and 3 output dimensions. - * |dst| can be |src| - */ - void interp3D(float* dst, const float* src) const; - - // recursively LERPs one dimension at a time. Used by interp() for the general case - float interpDimension(const float* src, int inputDimension, int outputDimension, - int index[kMaxColorChannels]) const; + uint8_t fGridPoints[3]; - uint8_t fInputChannels; - uint8_t fGridPoints[kMaxColorChannels]; + friend class SkColorSpaceXform_A2B; public: // Objects of this type are created in a custom fashion using sk_malloc_throw diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp index e9db37a810..c09387bbad 100644 --- a/src/core/SkColorSpace.cpp +++ b/src/core/SkColorSpace.cpp @@ -178,15 +178,18 @@ sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const SkColorSpaceTransferFn& coeffs, } void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn)); - sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); + sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas()); SkColorSpaceTransferFn* fn = SkTAddOffset<SkColorSpaceTransferFn>(memory, sizeof(SkGammas)); *fn = coeffs; + gammas->fRedType = SkGammas::Type::kParam_Type; + gammas->fGreenType = SkGammas::Type::kParam_Type; + gammas->fBlueType = SkGammas::Type::kParam_Type; + SkGammas::Data data; data.fParamOffset = 0; - for (int channel = 0; channel < 3; ++channel) { - gammas->fType[channel] = SkGammas::Type::kParam_Type; - gammas->fData[channel] = data; - } + gammas->fRedData = data; + gammas->fGreenData = data; + gammas->fBlueData = data; return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed, std::move(gammas), toXYZD50, nullptr)); } diff --git a/src/core/SkColorSpaceXform_A2B.cpp b/src/core/SkColorSpaceXform_A2B.cpp index b0495cfef2..f2f694349f 100644 --- a/src/core/SkColorSpaceXform_A2B.cpp +++ b/src/core/SkColorSpaceXform_A2B.cpp @@ -169,33 +169,16 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, "None", "Named", "Value", "Table", "Param" }; #endif - int currentChannels = -1; - switch (srcSpace->inputColorFormat()) { - case SkColorSpace_Base::InputColorFormat::kRGB: - currentChannels = 3; - break; - case SkColorSpace_Base::InputColorFormat::kCMYK: - currentChannels = 4; - // CMYK images from JPEGs (the only format that supports it) are actually - // inverted CMYK, so we need to invert every channel. - // TransferFn is y = -x + 1 for x < 1.f, otherwise 0x + 0, ie y = 1 - x for x in [0,1] - this->addTransferFns({1.f, 0.f, 0.f, 0.f, 1.f, -1.f, 1.f}, 4); - break; - default: - SkASSERT(false); - } // add in all input color space -> PCS xforms for (int i = 0; i < srcSpace->count(); ++i) { const SkColorSpace_A2B::Element& e = srcSpace->element(i); - SkASSERT(e.inputChannels() == currentChannels); - currentChannels = e.outputChannels(); switch (e.type()) { case SkColorSpace_A2B::Element::Type::kGammaNamed: if (kLinear_SkGammaNamed != e.gammaNamed()) { SkCSXformPrintf("Gamma stage added: %s\n", debugGammaNamed[(int)e.gammaNamed()]); SkColorSpaceTransferFn fn = gammanamed_to_parametric(e.gammaNamed()); - this->addTransferFns(fn, currentChannels); + this->addTransferFn(fn, kRGB_Channels); fElementsPipeline.append(SkRasterPipeline::clamp_0); fElementsPipeline.append(SkRasterPipeline::clamp_1); @@ -204,23 +187,23 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, case SkColorSpace_A2B::Element::Type::kGammas: { const SkGammas& gammas = e.gammas(); SkCSXformPrintf("Gamma stage added:"); - for (int channel = 0; channel < gammas.channels(); ++channel) { + for (int channel = 0; channel < 3; ++channel) { SkCSXformPrintf(" %s", debugGammas[(int)gammas.type(channel)]); } SkCSXformPrintf("\n"); bool gammaNeedsRef = false; - for (int channel = 0; channel < gammas.channels(); ++channel) { + for (int channel = 0; channel < 3; ++channel) { if (SkGammas::Type::kTable_Type == gammas.type(channel)) { SkTableTransferFn table = { gammas.table(channel), gammas.data(channel).fTable.fSize, }; - this->addTableFn(table, channel); + this->addTableFn(table, static_cast<Channels>(channel)); gammaNeedsRef = true; } else { SkColorSpaceTransferFn fn = gamma_to_parametric(gammas, channel); - this->addTransferFn(fn, channel); + this->addTransferFn(fn, static_cast<Channels>(channel)); } } if (gammaNeedsRef) { @@ -232,8 +215,8 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, break; } case SkColorSpace_A2B::Element::Type::kCLUT: - SkCSXformPrintf("CLUT (%d -> %d) stage added\n", e.colorLUT().inputChannels(), - e.colorLUT().outputChannels()); + SkCSXformPrintf("CLUT stage added [%d][%d][%d]\n", e.colorLUT().fGridPoints[0], + e.colorLUT().fGridPoints[1], e.colorLUT().fGridPoints[2]); fCLUTs.push_back(sk_ref_sp(&e.colorLUT())); fElementsPipeline.append(SkRasterPipeline::color_lookup_table, fCLUTs.back().get()); @@ -247,8 +230,6 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, } } - SkASSERT(3 == currentChannels); - // Lab PCS -> XYZ PCS if (SkColorSpace_A2B::PCS::kLAB == srcSpace->pcs()) { SkCSXformPrintf("Lab -> XYZ element added\n"); @@ -264,7 +245,7 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, if (!fLinearDstGamma) { SkColorSpaceTransferFn fn = invert_parametric(gammanamed_to_parametric(dstSpace->gammaNamed())); - this->addTransferFns(fn, 3); + this->addTransferFn(fn, kRGB_Channels); fElementsPipeline.append(SkRasterPipeline::clamp_0); fElementsPipeline.append(SkRasterPipeline::clamp_1); } @@ -280,10 +261,10 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, }; fTableStorage.push_front(std::move(storage)); - this->addTableFn(table, channel); + this->addTableFn(table, static_cast<Channels>(channel)); } else { SkColorSpaceTransferFn fn = invert_parametric(gamma_to_parametric(gammas, channel)); - this->addTransferFn(fn, channel); + this->addTransferFn(fn, static_cast<Channels>(channel)); } } @@ -292,47 +273,45 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, } } -void SkColorSpaceXform_A2B::addTransferFns(const SkColorSpaceTransferFn& fn, int channelCount) { - for (int i = 0; i < channelCount; ++i) { - this->addTransferFn(fn, i); - } -} - -void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex) { +void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, Channels channels) { fTransferFns.push_front(fn); - switch (channelIndex) { - case 0: + switch (channels) { + case kRGB_Channels: + fElementsPipeline.append(SkRasterPipeline::parametric_r, &fTransferFns.front()); + fElementsPipeline.append(SkRasterPipeline::parametric_g, &fTransferFns.front()); + fElementsPipeline.append(SkRasterPipeline::parametric_b, &fTransferFns.front()); + break; + case kR_Channels: fElementsPipeline.append(SkRasterPipeline::parametric_r, &fTransferFns.front()); break; - case 1: + case kG_Channels: fElementsPipeline.append(SkRasterPipeline::parametric_g, &fTransferFns.front()); break; - case 2: + case kB_Channels: fElementsPipeline.append(SkRasterPipeline::parametric_b, &fTransferFns.front()); break; - case 3: - fElementsPipeline.append(SkRasterPipeline::parametric_a, &fTransferFns.front()); - break; default: SkASSERT(false); } } -void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, int channelIndex) { +void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, Channels channels) { fTableTransferFns.push_front(fn); - switch (channelIndex) { - case 0: + switch (channels) { + case kRGB_Channels: + fElementsPipeline.append(SkRasterPipeline::table_r, &fTableTransferFns.front()); + fElementsPipeline.append(SkRasterPipeline::table_g, &fTableTransferFns.front()); + fElementsPipeline.append(SkRasterPipeline::table_b, &fTableTransferFns.front()); + break; + case kR_Channels: fElementsPipeline.append(SkRasterPipeline::table_r, &fTableTransferFns.front()); break; - case 1: + case kG_Channels: fElementsPipeline.append(SkRasterPipeline::table_g, &fTableTransferFns.front()); break; - case 2: + case kB_Channels: fElementsPipeline.append(SkRasterPipeline::table_b, &fTableTransferFns.front()); break; - case 3: - fElementsPipeline.append(SkRasterPipeline::table_a, &fTableTransferFns.front()); - break; default: SkASSERT(false); } diff --git a/src/core/SkColorSpaceXform_A2B.h b/src/core/SkColorSpaceXform_A2B.h index 9376491fa1..6beda285e1 100644 --- a/src/core/SkColorSpaceXform_A2B.h +++ b/src/core/SkColorSpaceXform_A2B.h @@ -32,11 +32,17 @@ public: private: SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, SkColorSpace_XYZ* dstSpace); - void addTransferFns(const SkColorSpaceTransferFn& fn, int channelCount); + enum Channels { + kRGB_Channels = -1, + kR_Channels = 0, + kG_Channels = 1, + kB_Channels = 2 + }; - void addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex); - void addTableFn(const SkTableTransferFn& table, int channelIndex); + + void addTransferFn(const SkColorSpaceTransferFn& fn, Channels channels); + void addTableFn(const SkTableTransferFn& table, Channels channels); void addMatrix(const SkMatrix44& matrix); diff --git a/src/core/SkColorSpace_A2B.cpp b/src/core/SkColorSpace_A2B.cpp index 1fef71a617..9fed82379a 100644 --- a/src/core/SkColorSpace_A2B.cpp +++ b/src/core/SkColorSpace_A2B.cpp @@ -7,10 +7,9 @@ #include "SkColorSpace_A2B.h" -SkColorSpace_A2B::SkColorSpace_A2B(InputColorFormat inputColorFormat, std::vector<Element> elements, - PCS pcs, sk_sp<SkData> profileData) +SkColorSpace_A2B::SkColorSpace_A2B(PCS pcs, sk_sp<SkData> profileData, + std::vector<Element> elements) : INHERITED(std::move(profileData)) - , fInputColorFormat(inputColorFormat) - , fElements(std::move(elements)) , fPCS(pcs) + , fElements(std::move(elements)) {} diff --git a/src/core/SkColorSpace_A2B.h b/src/core/SkColorSpace_A2B.h index b42de77137..2fb7a83cab 100644 --- a/src/core/SkColorSpace_A2B.h +++ b/src/core/SkColorSpace_A2B.h @@ -16,15 +16,14 @@ // 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 -// allows conversion between non-3-channel input color spaces ie CMYK(4) to -// a workable PCS (ie XYZ). +// 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. // -// AtoBType, lut8Type and lut16Type A2B0 tag types are supported. There are -// also MPET (multi-processing-elements) A2B0 tags in the standard which allow -// you to combine these 3 primitives (TRC, CLUT, matrix) in any order/quantity. -// MPET tags are currently unsupported by the MakeICC parser, could be supported -// here by the nature of the design. +// Currently AtoBType A2B0 tag types are supported. There are also lut8Type, +// lut16Type and MPET (multi-processing-elements) A2B0 tags 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 { @@ -46,12 +45,12 @@ public: // 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; @@ -72,37 +71,29 @@ public: class Element { public: - Element(SkGammaNamed gammaNamed, int channelCount) + explicit Element(SkGammaNamed gammaNamed) : fType(Type::kGammaNamed) , fGammaNamed(gammaNamed) , fMatrix(SkMatrix44::kUninitialized_Constructor) - , fInputChannels(channelCount) - , fOutputChannels(channelCount) {} explicit Element(sk_sp<SkGammas> gammas) : fType(Type::kGammas) , fGammas(std::move(gammas)) - , fMatrix(SkMatrix44::kUninitialized_Constructor) - , fInputChannels(fGammas->channels()) - , fOutputChannels(fGammas->channels()) + , fMatrix(SkMatrix44::kUninitialized_Constructor) {} explicit Element(sk_sp<SkColorLookUpTable> colorLUT) : fType(Type::kCLUT) , fCLUT(std::move(colorLUT)) , fMatrix(SkMatrix44::kUninitialized_Constructor) - , fInputChannels(fCLUT->inputChannels()) - , fOutputChannels(fCLUT->outputChannels()) {} explicit Element(const SkMatrix44& matrix) : fType(Type::kMatrix) , fMatrix(matrix) - , fInputChannels(3) - , fOutputChannels(3) {} - + enum class Type { kGammaNamed, kGammas, @@ -132,21 +123,15 @@ public: return fMatrix; } - int inputChannels() const { return fInputChannels; } - - int outputChannels() const { return fOutputChannels; } - private: Type fType; SkGammaNamed fGammaNamed; sk_sp<SkGammas> fGammas; sk_sp<SkColorLookUpTable> fCLUT; SkMatrix44 fMatrix; - int fInputChannels; - int fOutputChannels; }; - const Element& element(int i) const { return fElements[i]; } - + const Element& element(size_t i) const { return fElements[i]; } + int count() const { return (int)fElements.size(); } // the intermediate profile connection space that this color space @@ -155,20 +140,16 @@ public: kLAB, // CIELAB kXYZ // CIEXYZ }; - + PCS pcs() const { return fPCS; } - InputColorFormat inputColorFormat() const { return fInputColorFormat; } - private: - SkColorSpace_A2B(InputColorFormat inputColorFormat, std::vector<Element> elements, PCS pcs, - sk_sp<SkData> profileData); + SkColorSpace_A2B(PCS pcs, sk_sp<SkData> profileData, std::vector<Element> elements); - InputColorFormat fInputColorFormat; - std::vector<Element> fElements; PCS fPCS; + std::vector<Element> fElements; - friend class SkColorSpace_Base; + friend class SkColorSpace; friend class ColorSpaceXformTest; typedef SkColorSpace_Base INHERITED; }; diff --git a/src/core/SkColorSpace_Base.h b/src/core/SkColorSpace_Base.h index 947f725036..480febd7ff 100644 --- a/src/core/SkColorSpace_Base.h +++ b/src/core/SkColorSpace_Base.h @@ -83,8 +83,17 @@ struct SkGammas : SkRefCnt { } const Data& data(int i) const { - SkASSERT(i >= 0 && i < fChannels); - return fData[i]; + switch (i) { + case 0: + return fRedData; + case 1: + return fGreenData; + case 2: + return fBlueData; + default: + SkASSERT(false); + return fRedData; + } } const float* table(int i) const { @@ -98,24 +107,32 @@ struct SkGammas : SkRefCnt { } Type type(int i) const { - SkASSERT(i >= 0 && i < fChannels); - return fType[i]; - } - - uint8_t channels() const { return fChannels; } - - SkGammas(uint8_t channels) - : fChannels(channels) { - SkASSERT(channels <= kMaxColorChannels); - for (uint8_t i = 0; i < kMaxColorChannels; ++i) { - fType[i] = Type::kNone_Type; + switch (i) { + case 0: + return fRedType; + case 1: + return fGreenType; + case 2: + return fBlueType; + default: + SkASSERT(false); + return fRedType; } } + SkGammas() + : fRedType(Type::kNone_Type) + , fGreenType(Type::kNone_Type) + , fBlueType(Type::kNone_Type) + {} + // These fields should only be modified when initializing the struct. - uint8_t fChannels; - Data fData[kMaxColorChannels]; - Type fType[kMaxColorChannels]; + Data fRedData; + Data fGreenData; + Data fBlueData; + Type fRedType; + Type fGreenType; + Type fBlueType; // Objects of this type are sometimes created in a custom fashion using // sk_malloc_throw and therefore must be sk_freed. We overload new to @@ -171,17 +188,9 @@ public: kXYZ, kA2B }; - + virtual Type type() const = 0; - - enum class InputColorFormat { - kRGB, - kCMYK - }; - - static sk_sp<SkColorSpace> MakeICC(const void* input, size_t len, - InputColorFormat inputColorFormat); - + protected: SkColorSpace_Base(sk_sp<SkData> profileData); diff --git a/src/core/SkColorSpace_ICC.cpp b/src/core/SkColorSpace_ICC.cpp index ed65f816e1..5fe066ac11 100644 --- a/src/core/SkColorSpace_ICC.cpp +++ b/src/core/SkColorSpace_ICC.cpp @@ -49,7 +49,6 @@ static constexpr size_t kICCHeaderSize = 132; static constexpr size_t kICCTagTableEntrySize = 12; static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' '); -static constexpr uint32_t kCMYK_ColorSpace = SkSetFourByteTag('C', 'M', 'Y', 'K'); static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r'); static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r'); static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'); @@ -128,19 +127,9 @@ struct ICCProfileHeader { fProfileClass == kColorSpace_Profile, "Unsupported profile"); - switch (fInputColorSpace) { - case kRGB_ColorSpace: - SkColorSpacePrintf("RGB Input Color Space"); - break; - case kCMYK_ColorSpace: - SkColorSpacePrintf("CMYK Input Color Space\n"); - break; - default: - SkColorSpacePrintf("Unsupported Input Color Space: %c%c%c%c\n", - (fInputColorSpace>>24)&0xFF, (fInputColorSpace>>16)&0xFF, - (fInputColorSpace>> 8)&0xFF, (fInputColorSpace>> 0)&0xFF); - return false; - } + // TODO (msarett): + // All the profiles we've tested so far use RGB as the input color space. + return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color space"); switch (fPCS) { case kXYZ_PCSSpace: @@ -151,9 +140,7 @@ struct ICCProfileHeader { break; default: // ICC currently (V4.3) only specifices XYZ and Lab PCS spaces - SkColorSpacePrintf("Unsupported PCS space: %c%c%c%c\n", - (fPCS>>24)&0xFF, (fPCS>>16)&0xFF, - (fPCS>> 8)&0xFF, (fPCS>> 0)&0xFF); + SkColorSpacePrintf("Unsupported PCS space\n"); return false; } @@ -629,6 +616,7 @@ static bool load_color_lut(sk_sp<SkColorLookUpTable>* colorLUT, uint32_t inputCh return false; } + SkASSERT(3 == inputChannels); uint32_t numEntries = SkColorLookUpTable::kOutputChannels; for (uint32_t i = 0; i < inputChannels; i++) { if (0 == gridPoints[i]) { @@ -747,12 +735,14 @@ static bool load_matrix(SkMatrix44* matrix, const uint8_t* src, size_t len, bool } static inline SkGammaNamed is_named(const sk_sp<SkGammas>& gammas) { - for (uint8_t i = 0; i < gammas->channels(); ++i) { - if (!gammas->isNamed(i) || gammas->data(i).fNamed != gammas->data(0).fNamed) { - return kNonStandard_SkGammaNamed; - } + if (gammas->isNamed(0) && gammas->isNamed(1) && gammas->isNamed(2) && + gammas->fRedData.fNamed == gammas->fGreenData.fNamed && + gammas->fRedData.fNamed == gammas->fBlueData.fNamed) + { + return gammas->fRedData.fNamed; } - return gammas->data(0).fNamed; + + return kNonStandard_SkGammaNamed; } /** @@ -762,87 +752,93 @@ static inline SkGammaNamed is_named(const sk_sp<SkGammas>& gammas) { * 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 inputChannels The number of gamma input channels - * @param rTagPtr Pointer to start of the gamma tag. - * @param taglen The size in bytes of the tag + * @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 + * @return false on failure, true on success */ static bool parse_and_load_gamma(SkGammaNamed* gammaNamed, sk_sp<SkGammas>* gammas, - uint8_t inputChannels, const uint8_t* tagSrc, size_t tagLen) { - SkGammas::Data data[kMaxColorChannels]; - SkColorSpaceTransferFn params[kMaxColorChannels]; - SkGammas::Type type[kMaxColorChannels]; - const uint8_t* tagPtr[kMaxColorChannels]; - - tagPtr[0] = tagSrc; + const uint8_t* rTagPtr, size_t tagLen) +{ + SkGammas::Data rData; + SkColorSpaceTransferFn rParams; *gammaNamed = kNonStandard_SkGammaNamed; // 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; - type[0] = parse_gamma(&data[0], ¶ms[0], &tagBytes, tagPtr[0], tagLen); - handle_invalid_gamma(&type[0], &data[0]); + SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, rTagPtr, tagLen); + handle_invalid_gamma(&rType, &rData); size_t alignedTagBytes = SkAlign4(tagBytes); - bool allChannelsSame = false; - if (inputChannels * alignedTagBytes <= tagLen) { - allChannelsSame = true; - for (uint8_t i = 1; i < inputChannels; ++i) { - if (0 != memcmp(tagPtr, tagPtr + i * alignedTagBytes, tagBytes)) { - allChannelsSame = false; - break; - } - } - } - if (allChannelsSame) { - if (SkGammas::Type::kNamed_Type == type[0]) { - *gammaNamed = data[0].fNamed; + 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(type[0], data[0]), &allocSize), + 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<SkGammas>(new (memory) SkGammas(inputChannels)); - load_gammas(memory, 0, type[0], &data[0], params[0], tagPtr[0]); + *gammas = sk_sp<SkGammas>(new (memory) SkGammas()); + load_gammas(memory, 0, rType, &rData, rParams, rTagPtr); - for (uint8_t channel = 0; channel < inputChannels; ++channel) { - (*gammas)->fType[channel] = type[0]; - (*gammas)->fData[channel] = data[0]; - } + (*gammas)->fRedType = rType; + (*gammas)->fGreenType = rType; + (*gammas)->fBlueType = rType; + + (*gammas)->fRedData = rData; + (*gammas)->fGreenData = rData; + (*gammas)->fBlueData = rData; } } else { - for (uint8_t channel = 1; channel < inputChannels; ++channel) { - tagPtr[channel] = tagPtr[channel - 1] + alignedTagBytes; - tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0; - tagBytes = 0; - type[channel] = parse_gamma(&data[channel], ¶ms[channel], &tagBytes, - tagPtr[channel], tagLen); - handle_invalid_gamma(&type[channel], &data[channel]); - alignedTagBytes = SkAlign4(tagBytes); - } + 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); - for (uint8_t channel = 0; channel < inputChannels; ++channel) { - return_if_false(safe_add(allocSize, gamma_alloc_size(type[channel], data[channel]), - &allocSize), - "SkGammas struct is too large to allocate"); - } + 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<SkGammas>(new (memory) SkGammas(inputChannels)); + *gammas = sk_sp<SkGammas>(new (memory) SkGammas()); uint32_t offset = 0; - for (uint8_t channel = 0; channel < inputChannels; ++channel) { - (*gammas)->fType[channel] = type[channel]; - offset += load_gammas(memory,offset, type[channel], &data[channel], params[channel], - tagPtr[channel]); - (*gammas)->fData[channel] = data[channel]; + (*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) { @@ -893,7 +889,7 @@ static bool load_lut_gammas(sk_sp<SkGammas>* gammas, size_t numTables, size_t en "SkGammas struct is too large to allocate"); void* memory = sk_malloc_throw(allocSize); - *gammas = sk_sp<SkGammas>(new (memory) SkGammas(numTables)); + *gammas = sk_sp<SkGammas>(new (memory) SkGammas()); for (size_t tableIndex = 0; tableIndex < numTablesToUse; ++tableIndex) { const uint8_t* ptr = src + readBytesPerChannel * tableIndex; @@ -910,18 +906,24 @@ static bool load_lut_gammas(sk_sp<SkGammas>* gammas, size_t numTables, size_t en } } - SkASSERT(1 == numTablesToUse|| numTables == numTablesToUse); + (*gammas)->fRedType = SkGammas::Type::kTable_Type; + (*gammas)->fGreenType = SkGammas::Type::kTable_Type; + (*gammas)->fBlueType = SkGammas::Type::kTable_Type; - size_t tableOffset = 0; - for (size_t tableIndex = 0; tableIndex < numTables; ++tableIndex) { - (*gammas)->fType[tableIndex] = SkGammas::Type::kTable_Type; - (*gammas)->fData[tableIndex].fTable.fOffset = tableOffset; - (*gammas)->fData[tableIndex].fTable.fSize = entriesPerTable; - if (numTablesToUse > 1) { - tableOffset += writeBytesPerChannel; - } + if (1 == numTablesToUse) { + (*gammas)->fRedData.fTable.fOffset = 0; + (*gammas)->fGreenData.fTable.fOffset = 0; + (*gammas)->fBlueData.fTable.fOffset = 0; + } else { + (*gammas)->fRedData.fTable.fOffset = 0; + (*gammas)->fGreenData.fTable.fOffset = writeBytesPerChannel; + (*gammas)->fBlueData.fTable.fOffset = writeBytesPerChannel * 2; } + (*gammas)->fRedData.fTable.fSize = entriesPerTable; + (*gammas)->fGreenData.fTable.fSize = entriesPerTable; + (*gammas)->fBlueData.fTable.fSize = entriesPerTable; + return true; } @@ -932,23 +934,15 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con // must be zero. const uint8_t inputChannels = src[8]; const uint8_t outputChannels = src[9]; - if (SkColorLookUpTable::kOutputChannels != outputChannels) { - // We only handle RGB outputs. The number of output channels must be 3. - SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels); - return false; - } - if (inputChannels == 0 || inputChannels > 4) { - // And we only support 4 input channels. - // ICC says up to 16 but our decode can only handle 4. - // It could easily be extended to support up to 8, but we only allow CMYK/RGB - // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8. - // We can always change this check when we support bigger input spaces. - SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n", - inputChannels); + if (3 != inputChannels || SkColorLookUpTable::kOutputChannels != outputChannels) { + // We only handle (supposedly) RGB inputs and RGB outputs. The numbers of input + // channels and output channels both must be 3. + // TODO (msarett): + // Support different numbers of input channels. Ex: CMYK (4). + SkColorSpacePrintf("Input and output channels must equal 3 in A to B tag.\n"); return false; } - // It is important that these are loaded in the order of application, as the // order you construct an A2B color space's elements is the order it is applied @@ -958,14 +952,13 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con const size_t tagLen = len - offsetToACurves; SkGammaNamed gammaNamed; sk_sp<SkGammas> gammas; - if (!parse_and_load_gamma(&gammaNamed, &gammas, inputChannels, src + offsetToACurves, - tagLen)) { + if (!parse_and_load_gamma(&gammaNamed, &gammas, src + offsetToACurves, tagLen)) { return false; } if (gammas) { elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); - } else if (kLinear_SkGammaNamed != gammaNamed) { - elements->push_back(SkColorSpace_A2B::Element(gammaNamed, inputChannels)); + } else { + elements->push_back(SkColorSpace_A2B::Element(gammaNamed)); } } @@ -982,8 +975,8 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con return false; } - SkASSERT(inputChannels <= kMaxColorChannels); - uint8_t gridPoints[kMaxColorChannels]; + SkASSERT(3 == inputChannels); + uint8_t gridPoints[3]; for (uint32_t i = 0; i < inputChannels; ++i) { gridPoints[i] = clutSrc[i]; } @@ -1003,14 +996,13 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con const size_t tagLen = len - offsetToMCurves; SkGammaNamed gammaNamed; sk_sp<SkGammas> gammas; - if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToMCurves, - tagLen)) { + if (!parse_and_load_gamma(&gammaNamed, &gammas, src + offsetToMCurves, tagLen)) { return false; } if (gammas) { elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); - } else if (kLinear_SkGammaNamed != gammaNamed) { - elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels)); + } else { + elements->push_back(SkColorSpace_A2B::Element(gammaNamed)); } } @@ -1019,7 +1011,7 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor); if (!load_matrix(&matrix, src + offsetToMatrix, len - offsetToMatrix, true, pcs)) { SkColorSpacePrintf("Failed to read matrix from A to B tag.\n"); - } else if (!matrix.isIdentity()) { + } else { elements->push_back(SkColorSpace_A2B::Element(matrix)); } } @@ -1029,14 +1021,13 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con const size_t tagLen = len - offsetToBCurves; SkGammaNamed gammaNamed; sk_sp<SkGammas> gammas; - if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToBCurves, - tagLen)) { + if (!parse_and_load_gamma(&gammaNamed, &gammas, src + offsetToBCurves, tagLen)) { return false; } if (gammas) { elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); - } else if (kLinear_SkGammaNamed != gammaNamed) { - elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels)); + } else { + elements->push_back(SkColorSpace_A2B::Element(gammaNamed)); } } @@ -1061,19 +1052,12 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const // The four bytes (4-7) that we skipped are reserved and must be zero. const uint8_t inputChannels = src[8]; const uint8_t outputChannels = src[9]; - if (SkColorLookUpTable::kOutputChannels != outputChannels) { - // We only handle RGB outputs. The number of output channels must be 3. - SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels); - return false; - } - if (inputChannels == 0 || inputChannels > 4) { - // And we only support 4 input channels. - // ICC says up to 16 but our decode can only handle 4. - // It could easily be extended to support up to 8, but we only allow CMYK/RGB - // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8. - // We can always change this check when we support bigger input spaces. - SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n", - inputChannels); + if (3 != inputChannels || SkColorLookUpTable::kOutputChannels != outputChannels) { + // We only handle (supposedly) RGB inputs and RGB outputs. The numbers of input + // channels and output channels both must be 3. + // TODO (msarett): + // Support different numbers of input channels. Ex: CMYK (4). + SkColorSpacePrintf("Input and output channels must equal 3 in A to B tag.\n"); return false; } @@ -1082,13 +1066,7 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor); load_matrix(&matrix, &src[12], len - 12, false, pcs); - if (!matrix.isIdentity()) { - // ICC specs (10.8/10.9) say lut8/16Type profiles must have identity matrices - // if the input color space is not PCSXYZ, and we do not support PCSXYZ input color spaces - // so we should never encounter a non-identity matrix here. - SkColorSpacePrintf("non-Identity matrix found in non-XYZ input color space lut profile"); - return false; - } + elements->push_back(SkColorSpace_A2B::Element(matrix)); size_t dataOffset = 48; // # of input table entries @@ -1133,21 +1111,12 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const return false; } SkASSERT(inputGammas); - const SkGammaNamed inputGammaNamed = is_named(inputGammas); - if (kLinear_SkGammaNamed != inputGammaNamed) { - if (kNonStandard_SkGammaNamed != inputGammaNamed) { - elements->push_back(SkColorSpace_A2B::Element(inputGammaNamed, inputChannels)); - } else { - elements->push_back(SkColorSpace_A2B::Element(std::move(inputGammas))); - } - } + elements->push_back(SkColorSpace_A2B::Element(std::move(inputGammas))); const size_t clutOffset = inputOffset + precision*inTableEntries*inputChannels; return_if_false(len >= clutOffset, "A2B0 lutnType tag too small for CLUT"); sk_sp<SkColorLookUpTable> colorLUT; - const uint8_t gridPoints[kMaxColorChannels] = { - clutGridPoints, clutGridPoints, clutGridPoints, clutGridPoints - }; + const uint8_t gridPoints[3] = {clutGridPoints, clutGridPoints, clutGridPoints}; if (!load_color_lut(&colorLUT, inputChannels, precision, gridPoints, src + clutOffset, len - clutOffset)) { SkColorSpacePrintf("Failed to read color LUT from lutnType tag.\n"); @@ -1169,33 +1138,13 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const return false; } SkASSERT(outputGammas); - const SkGammaNamed outputGammaNamed = is_named(outputGammas); - if (kLinear_SkGammaNamed != outputGammaNamed) { - if (kNonStandard_SkGammaNamed != outputGammaNamed) { - elements->push_back(SkColorSpace_A2B::Element(outputGammaNamed, outputChannels)); - } else { - elements->push_back(SkColorSpace_A2B::Element(std::move(outputGammas))); - } - } + elements->push_back(SkColorSpace_A2B::Element(std::move(outputGammas))); return true; } -static inline int icf_channels(SkColorSpace_Base::InputColorFormat inputColorFormat) { - switch (inputColorFormat) { - case SkColorSpace_Base::InputColorFormat::kRGB: - return 3; - case SkColorSpace_Base::InputColorFormat::kCMYK: - return 4; - default: - SkASSERT(false); - return -1; - } -} - static bool load_a2b0(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src, - size_t len, SkColorSpace_A2B::PCS pcs, - SkColorSpace_Base::InputColorFormat inputColorFormat) { + size_t len, SkColorSpace_A2B::PCS pcs) { const uint32_t type = read_big_endian_u32(src); switch (type) { case kTAG_AtoBType: @@ -1204,54 +1153,26 @@ static bool load_a2b0(std::vector<SkColorSpace_A2B::Element>* elements, const ui return false; } SkColorSpacePrintf("A2B0 tag is of type lutAtoBType\n"); - if (!load_a2b0_a_to_b_type(elements, src, len, pcs)) { - return false; - } - break; + return load_a2b0_a_to_b_type(elements, src, len, pcs); case kTAG_lut8Type: if (len < 48) { SkColorSpacePrintf("lut8 tag is too small (%d bytes).", len); return false; } SkColorSpacePrintf("A2B0 tag of type lut8Type\n"); - if (!load_a2b0_lutn_type(elements, src, len, pcs)) { - return false; - } - break; + return load_a2b0_lutn_type(elements, src, len, pcs); case kTAG_lut16Type: if (len < 52) { SkColorSpacePrintf("lut16 tag is too small (%d bytes).", len); return false; } SkColorSpacePrintf("A2B0 tag of type lut16Type\n"); - if (!load_a2b0_lutn_type(elements, src, len, pcs)) { - return false; - } - break; + return load_a2b0_lutn_type(elements, src, len, pcs); default: SkColorSpacePrintf("Unsupported A to B tag type: %c%c%c%c\n", (type>>24)&0xFF, (type>>16)&0xFF, (type>>8)&0xFF, type&0xFF); - return false; - } - // now let's verify that the input/output channels of each A2B element actually match up - SkASSERT(!elements->empty()); - if (icf_channels(inputColorFormat) != elements->front().inputChannels()) { - SkColorSpacePrintf("Input channel count does not match first A2B element's input count"); - return false; - } - for (size_t i = 1; i < elements->size(); ++i) { - if ((*elements)[i - 1].outputChannels() != (*elements)[i].inputChannels()) { - SkColorSpacePrintf("A2B elements don't agree in input/output channel counts"); - return false; - } } - SkASSERT(SkColorSpace_A2B::PCS::kLAB == pcs || SkColorSpace_A2B::PCS::kXYZ == pcs); - static constexpr int kPCSChannels = 3; // must be PCSLAB or PCSXYZ - if (kPCSChannels != elements->back().outputChannels()) { - SkColorSpacePrintf("PCS channel count doesn't match last A2B element's output count"); - return false; - } - return true; + return false; } static bool tag_equals(const ICCTag* a, const ICCTag* b, const uint8_t* base) { @@ -1289,11 +1210,6 @@ static inline bool is_close_to_d50(const SkMatrix44& matrix) { } sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) { - return SkColorSpace_Base::MakeICC(input, len, SkColorSpace_Base::InputColorFormat::kRGB); -} - -sk_sp<SkColorSpace> SkColorSpace_Base::MakeICC(const void* input, size_t len, - InputColorFormat inputColorFormat) { if (!input || len < kICCHeaderSize) { return_null("Data is null or not large enough to contain an ICC profile"); } @@ -1312,22 +1228,6 @@ sk_sp<SkColorSpace> SkColorSpace_Base::MakeICC(const void* input, size_t len, return nullptr; } - switch (inputColorFormat) { - case InputColorFormat::kRGB: - if (header.fInputColorSpace != kRGB_ColorSpace) { - return_null("Provided input color format (RGB) does not match profile.\n"); - } - break; - case InputColorFormat::kCMYK: - if (header.fInputColorSpace != kCMYK_ColorSpace) { - return_null("Provided input color format (CMYK) does not match profile.\n"); - return nullptr; - } - break; - default: - return_null("Provided input color format not supported"); - } - // Adjust ptr and len before reading the tags. if (len < header.fSize) { SkColorSpacePrintf("ICC profile might be truncated.\n"); @@ -1357,84 +1257,102 @@ sk_sp<SkColorSpace> SkColorSpace_Base::MakeICC(const void* input, size_t len, } } - // Recognize color profile specified by A2B0 tag. - // this must be done before XYZ profile checking, as a profile can have both - // in which case we should use the A2B case to be accurate - // (XYZ is there as a fallback / quick preview) - const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0); - if (a2b0) { - const SkColorSpace_A2B::PCS pcs = kXYZ_PCSSpace == header.fPCS - ? SkColorSpace_A2B::PCS::kXYZ - : SkColorSpace_A2B::PCS::kLAB; - std::vector<SkColorSpace_A2B::Element> elements; - if (load_a2b0(&elements, a2b0->addr(base), a2b0->fLength, pcs, inputColorFormat)) { - return sk_sp<SkColorSpace>(new SkColorSpace_A2B(inputColorFormat, std::move(elements), - pcs, std::move(data))); - } - SkColorSpacePrintf("Ignoring malformed A2B0 tag.\n"); - } - - if (kRGB_ColorSpace == header.fInputColorSpace) { - // Recognize the rXYZ, gXYZ, and bXYZ tags. - const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ); - const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); - const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); - // 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 (r && g && b && kXYZ_PCSSpace == header.fPCS) { - float toXYZ[9]; - if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) || - !load_xyz(&toXYZ[3], g->addr(base), g->fLength) || - !load_xyz(&toXYZ[6], b->addr(base), b->fLength)) - { - return_null("Need valid rgb tags for XYZ space"); - } - SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor); - mat.set3x3(toXYZ[0], toXYZ[1], toXYZ[2], - toXYZ[3], toXYZ[4], toXYZ[5], - toXYZ[6], toXYZ[7], toXYZ[8]); - if (!is_close_to_d50(mat)) { - // QCMS treats these profiles as "bogus". I'm not sure if that's - // correct, but we certainly do not handle non-D50 matrices - // correctly. So I'll disable this for now. - SkColorSpacePrintf("Matrix is not close to D50"); - return nullptr; + switch (header.fInputColorSpace) { + case kRGB_ColorSpace: { + // Recognize color profile specified by A2B0 tag. + // this must be done before XYZ profile checking, as a profile can have both + // in which case we should use the A2B case to be accurate + // (XYZ is there as a fallback / quick preview) + const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0); + if (a2b0) { + const SkColorSpace_A2B::PCS pcs = kXYZ_PCSSpace == header.fPCS + ? SkColorSpace_A2B::PCS::kXYZ + : SkColorSpace_A2B::PCS::kLAB; + std::vector<SkColorSpace_A2B::Element> elements; + if (load_a2b0(&elements, a2b0->addr(base), a2b0->fLength, pcs)) { + return sk_sp<SkColorSpace>(new SkColorSpace_A2B(pcs, std::move(data), + std::move(elements))); + } + SkColorSpacePrintf("Ignoring malformed A2B0 tag.\n"); } - r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC); - g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC); - b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC); - - // If some, but not all, of the gamma tags are missing, assume that all - // gammas are meant to be the same. This behavior is an arbitrary guess, - // but it simplifies the code below. - if ((!r || !g || !b) && (r || g || b)) { - if (!r) { - r = g ? g : b; + // Recognize the rXYZ, gXYZ, and bXYZ tags. + const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ); + const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); + const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); + // 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 (r && g && b && kXYZ_PCSSpace == header.fPCS) { + float toXYZ[9]; + if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) || + !load_xyz(&toXYZ[3], g->addr(base), g->fLength) || + !load_xyz(&toXYZ[6], b->addr(base), b->fLength)) + { + return_null("Need valid rgb tags for XYZ space"); } - - if (!g) { - g = r ? r : b; + SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor); + mat.set3x3(toXYZ[0], toXYZ[1], toXYZ[2], + toXYZ[3], toXYZ[4], toXYZ[5], + toXYZ[6], toXYZ[7], toXYZ[8]); + if (!is_close_to_d50(mat)) { + // QCMS treats these profiles as "bogus". I'm not sure if that's + // correct, but we certainly do not handle non-D50 matrices + // correctly. So I'll disable this for now. + SkColorSpacePrintf("Matrix is not close to D50"); + return nullptr; } - if (!b) { - b = r ? r : g; + r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC); + g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC); + b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC); + + // If some, but not all, of the gamma tags are missing, assume that all + // gammas are meant to be the same. This behavior is an arbitrary guess, + // but it simplifies the code below. + if ((!r || !g || !b) && (r || g || b)) { + if (!r) { + r = g ? g : b; + } + + if (!g) { + g = r ? r : b; + } + + if (!b) { + b = r ? r : g; + } } - } - SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed; - sk_sp<SkGammas> gammas = nullptr; - size_t tagBytes; - if (r && g && b) { - if (tag_equals(r, g, base) && tag_equals(g, b, base)) { - SkGammas::Data data; - SkColorSpaceTransferFn params; - SkGammas::Type type = - parse_gamma(&data, ¶ms, &tagBytes, r->addr(base), r->fLength); - handle_invalid_gamma(&type, &data); - - if (SkGammas::Type::kNamed_Type == type) { - gammaNamed = data.fNamed; + SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed; + sk_sp<SkGammas> gammas = nullptr; + size_t tagBytes; + if (r && g && b) { + if (tag_equals(r, g, base) && tag_equals(g, b, base)) { + SkGammas::Data data; + SkColorSpaceTransferFn params; + SkGammas::Type type = + parse_gamma(&data, ¶ms, &tagBytes, r->addr(base), r->fLength); + handle_invalid_gamma(&type, &data); + + if (SkGammas::Type::kNamed_Type == type) { + gammaNamed = data.fNamed; + } else { + size_t allocSize = sizeof(SkGammas); + if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) { + return_null("SkGammas struct is too large to allocate"); + } + void* memory = sk_malloc_throw(allocSize); + gammas = sk_sp<SkGammas>(new (memory) SkGammas()); + load_gammas(memory, 0, type, &data, params, r->addr(base)); + + gammas->fRedType = type; + gammas->fGreenType = type; + gammas->fBlueType = type; + + gammas->fRedData = data; + gammas->fGreenData = data; + gammas->fBlueData = data; + } } else { SkGammas::Data rData; SkColorSpaceTransferFn rParams; @@ -1455,81 +1373,53 @@ sk_sp<SkColorSpace> SkColorSpace_Base::MakeICC(const void* input, size_t len, handle_invalid_gamma(&bType, &bData); size_t allocSize = sizeof(SkGammas); - if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) { + if (!safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize) || + !safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize) || + !safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize)) + { return_null("SkGammas struct is too large to allocate"); } void* memory = sk_malloc_throw(allocSize); - gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); - load_gammas(memory, 0, type, &data, params, r->addr(base)); + gammas = sk_sp<SkGammas>(new (memory) SkGammas()); - for (int i = 0; i < 3; ++i) { - gammas->fType[i] = type; - gammas->fData[i] = data; - } - } - } else { - SkGammas::Data rData; - SkColorSpaceTransferFn rParams; - SkGammas::Type rType = - parse_gamma(&rData, &rParams, &tagBytes, r->addr(base), r->fLength); - handle_invalid_gamma(&rType, &rData); - - SkGammas::Data gData; - SkColorSpaceTransferFn gParams; - SkGammas::Type gType = - parse_gamma(&gData, &gParams, &tagBytes, g->addr(base), g->fLength); - handle_invalid_gamma(&gType, &gData); - - SkGammas::Data bData; - SkColorSpaceTransferFn bParams; - SkGammas::Type bType = - parse_gamma(&bData, &bParams, &tagBytes, b->addr(base), b->fLength); - handle_invalid_gamma(&bType, &bData); - - size_t allocSize = sizeof(SkGammas); - if (!safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize) || - !safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize) || - !safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize)) { - return_null("SkGammas struct is too large to allocate"); - } - void* memory = sk_malloc_throw(allocSize); - gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); + uint32_t offset = 0; + gammas->fRedType = rType; + offset += load_gammas(memory, offset, rType, &rData, rParams, + r->addr(base)); - uint32_t offset = 0; - gammas->fType[0] = rType; - offset += load_gammas(memory, offset, rType, &rData, rParams, - r->addr(base)); + gammas->fGreenType = gType; + offset += load_gammas(memory, offset, gType, &gData, gParams, + g->addr(base)); - gammas->fType[1] = gType; - offset += load_gammas(memory, offset, gType, &gData, gParams, - g->addr(base)); + gammas->fBlueType = bType; + load_gammas(memory, offset, bType, &bData, bParams, b->addr(base)); - gammas->fType[2] = bType; - load_gammas(memory, offset, bType, &bData, bParams, b->addr(base)); + gammas->fRedData = rData; + gammas->fGreenData = gData; + gammas->fBlueData = bData; + } + } else { + // Guess sRGB if the profile is missing transfer functions. + gammaNamed = kSRGB_SkGammaNamed; + } - gammas->fData[0] = rData; - gammas->fData[1] = gData; - gammas->fData[2] = bData; + if (kNonStandard_SkGammaNamed == gammaNamed) { + // 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); } - } else { - // Guess sRGB if the profile is missing transfer functions. - gammaNamed = kSRGB_SkGammaNamed; - } - if (kNonStandard_SkGammaNamed == gammaNamed) { - // 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<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, + std::move(gammas), + mat, std::move(data))); + } - if (kNonStandard_SkGammaNamed == gammaNamed) { - return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, - std::move(gammas), - mat, std::move(data))); + return SkColorSpace_Base::MakeRGB(gammaNamed, mat); } - - return SkColorSpace_Base::MakeRGB(gammaNamed, mat); } + default: + break; } return_null("ICC profile contains unsupported colorspace"); diff --git a/src/core/SkColorSpace_XYZ.cpp b/src/core/SkColorSpace_XYZ.cpp index 4b90cae6a4..f99702d19b 100644 --- a/src/core/SkColorSpace_XYZ.cpp +++ b/src/core/SkColorSpace_XYZ.cpp @@ -31,9 +31,8 @@ SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gamm , fGammas(std::move(gammas)) , fToXYZD50(toXYZD50) , fToXYZD50Hash(SkGoodHash()(toXYZD50)) - , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) { - SkASSERT(!fGammas || 3 == fGammas->channels()); -} + , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) +{} const SkMatrix44* SkColorSpace_XYZ::fromXYZD50() const { fFromXYZOnce([this] { diff --git a/src/core/SkRasterPipeline.h b/src/core/SkRasterPipeline.h index 7338c4d001..9e8f8f7906 100644 --- a/src/core/SkRasterPipeline.h +++ b/src/core/SkRasterPipeline.h @@ -76,8 +76,7 @@ M(matrix_2x3) M(matrix_3x4) M(matrix_4x5) \ M(matrix_perspective) \ M(parametric_r) M(parametric_g) M(parametric_b) \ - M(parametric_a) \ - M(table_r) M(table_g) M(table_b) M(table_a) \ + M(table_r) M(table_g) M(table_b) \ M(color_lookup_table) M(lab_to_xyz) \ M(clamp_x) M(mirror_x) M(repeat_x) \ M(clamp_y) M(mirror_y) M(repeat_y) \ diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h index 66668bd6d9..ee8a9bf029 100644 --- a/src/opts/SkRasterPipeline_opts.h +++ b/src/opts/SkRasterPipeline_opts.h @@ -632,7 +632,6 @@ SI SkNf parametric(const SkNf& v, const SkColorSpaceTransferFn& p) { STAGE(parametric_r) { r = parametric(r, *(const SkColorSpaceTransferFn*)ctx); } STAGE(parametric_g) { g = parametric(g, *(const SkColorSpaceTransferFn*)ctx); } STAGE(parametric_b) { b = parametric(b, *(const SkColorSpaceTransferFn*)ctx); } -STAGE(parametric_a) { a = parametric(a, *(const SkColorSpaceTransferFn*)ctx); } SI SkNf table(const SkNf& v, const SkTableTransferFn& table) { float result[N]; @@ -644,29 +643,23 @@ SI SkNf table(const SkNf& v, const SkTableTransferFn& table) { STAGE(table_r) { r = table(r, *(const SkTableTransferFn*)ctx); } STAGE(table_g) { g = table(g, *(const SkTableTransferFn*)ctx); } STAGE(table_b) { b = table(b, *(const SkTableTransferFn*)ctx); } -STAGE(table_a) { a = table(a, *(const SkTableTransferFn*)ctx); } STAGE(color_lookup_table) { const SkColorLookUpTable* colorLUT = (const SkColorLookUpTable*)ctx; - SkASSERT(3 == colorLUT->inputChannels() || 4 == colorLUT->inputChannels()); - SkASSERT(3 == colorLUT->outputChannels()); + float rgb[3]; float result[3][N]; for (int i = 0; i < N; ++i) { - const float in[4] = { r[i], g[i], b[i], a[i] }; - float out[3]; - colorLUT->interp(out, in); - for (int j = 0; j < colorLUT->outputChannels(); ++j) { - result[j][i] = out[j]; - } + rgb[0] = r[i]; + rgb[1] = g[i]; + rgb[2] = b[i]; + colorLUT->interp3D(rgb, rgb); + result[0][i] = rgb[0]; + result[1][i] = rgb[1]; + result[2][i] = rgb[2]; } r = SkNf::Load(result[0]); g = SkNf::Load(result[1]); b = SkNf::Load(result[2]); - if (4 == colorLUT->inputChannels()) { - // we must set the pixel to opaque, as the alpha channel was used - // as input before this. - a = 1.f; - } } STAGE(lab_to_xyz) { diff --git a/tests/ColorSpaceXformTest.cpp b/tests/ColorSpaceXformTest.cpp index d2bd2a3358..0e67fe497d 100644 --- a/tests/ColorSpaceXformTest.cpp +++ b/tests/ColorSpaceXformTest.cpp @@ -16,8 +16,6 @@ #include "SkColorSpaceXform_Base.h" #include "Test.h" -static constexpr int kChannels = 3; - class ColorSpaceXformTest { public: static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform(const sk_sp<SkGammas>& gammas) { @@ -42,16 +40,13 @@ public: SkMatrix44 arbitraryMatrix{SkMatrix44::kUninitialized_Constructor}; arbitraryMatrix.setRowMajorf(values); if (kNonStandard_SkGammaNamed == gammaNamed) { - SkASSERT(gammas); srcElements.push_back(SkColorSpace_A2B::Element(gammas)); } else { - srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed, kChannels)); + srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed)); } srcElements.push_back(SkColorSpace_A2B::Element(arbitraryMatrix)); - auto srcSpace = - ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ, - SkColorSpace_Base::InputColorFormat::kRGB, - std::move(srcElements)); + auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ, + std::move(srcElements)); sk_sp<SkColorSpace> dstSpace(new SkColorSpace_XYZ(gammaNamed, gammas, arbitraryMatrix, nullptr)); @@ -60,15 +55,13 @@ public: } static sk_sp<SkColorSpace> CreateA2BSpace(SkColorSpace_A2B::PCS pcs, - SkColorSpace_Base::InputColorFormat inputColorFormat, std::vector<SkColorSpace_A2B::Element> elements) { - return sk_sp<SkColorSpace>(new SkColorSpace_A2B(inputColorFormat, std::move(elements), - pcs, nullptr)); + return sk_sp<SkColorSpace>(new SkColorSpace_A2B(pcs, nullptr, std::move(elements))); } }; static bool almost_equal(int x, int y) { - return SkTAbs(x - y) <= 1; + return SkTAbs(x - y) <= 1 ; } static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& gammas, @@ -108,7 +101,7 @@ static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& ga } static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNamed, - const sk_sp<SkGammas>& gammas) { + const sk_sp<SkGammas>& gammas, bool repeat) { // Arbitrary set of 10 pixels constexpr int width = 10; constexpr uint32_t srcPixels[width] = { @@ -135,19 +128,24 @@ static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNam REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF), SkGetPackedA32(dstPixels[i]))); } + + if (repeat) { + // We should cache part of the transform after the run. So it is interesting + // to make sure it still runs correctly the second time. + test_identity_xform_A2B(r, gammaNamed, gammas, false); + } } DEF_TEST(ColorSpaceXform_TableGamma, r) { // Lookup-table based gamma curves constexpr size_t tableSize = 10; void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize); - sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels)); - for (int i = 0; i < kChannels; ++i) { - gammas->fType[i] = SkGammas::Type::kTable_Type; - gammas->fData[i].fTable.fSize = tableSize; - gammas->fData[i].fTable.fOffset = 0; - } - + sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas()); + gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kTable_Type; + gammas->fRedData.fTable.fSize = gammas->fGreenData.fTable.fSize = + gammas->fBlueData.fTable.fSize = tableSize; + gammas->fRedData.fTable.fOffset = gammas->fGreenData.fTable.fOffset = + gammas->fBlueData.fTable.fOffset = 0; float* table = SkTAddOffset<float>(memory, sizeof(SkGammas)); table[0] = 0.00f; @@ -161,18 +159,16 @@ DEF_TEST(ColorSpaceXform_TableGamma, r) { table[8] = 0.75f; table[9] = 1.00f; test_identity_xform(r, gammas, true); - test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas); + test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true); } DEF_TEST(ColorSpaceXform_ParametricGamma, r) { // Parametric gamma curves void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn)); - sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels)); - for (int i = 0; i < kChannels; ++i) { - gammas->fType[i] = SkGammas::Type::kParam_Type; - gammas->fData[i].fParamOffset = 0; - } - + sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas()); + gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kParam_Type; + gammas->fRedData.fParamOffset = gammas->fGreenData.fParamOffset = + gammas->fBlueData.fParamOffset = 0; SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn> (memory, sizeof(SkGammas)); @@ -190,38 +186,36 @@ DEF_TEST(ColorSpaceXform_ParametricGamma, r) { params->fC = 0.0f; params->fG = 2.4f; test_identity_xform(r, gammas, true); - test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas); + test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true); } DEF_TEST(ColorSpaceXform_ExponentialGamma, r) { // Exponential gamma curves - sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels)); - for (int i = 0; i < kChannels; ++i) { - gammas->fType[i] = SkGammas::Type::kValue_Type; - gammas->fData[i].fValue = 1.4f; - } + sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas()); + gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kValue_Type; + gammas->fRedData.fValue = gammas->fGreenData.fValue = gammas->fBlueData.fValue = 1.4f; test_identity_xform(r, gammas, true); - test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas); + test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true); } DEF_TEST(ColorSpaceXform_NamedGamma, r) { - sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels)); - gammas->fType[0] = gammas->fType[1] = gammas->fType[2] = SkGammas::Type::kNamed_Type; - gammas->fData[0].fNamed = kSRGB_SkGammaNamed; - gammas->fData[1].fNamed = k2Dot2Curve_SkGammaNamed; - gammas->fData[2].fNamed = kLinear_SkGammaNamed; + sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas()); + gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kNamed_Type; + gammas->fRedData.fNamed = kSRGB_SkGammaNamed; + gammas->fGreenData.fNamed = k2Dot2Curve_SkGammaNamed; + gammas->fBlueData.fNamed = kLinear_SkGammaNamed; test_identity_xform(r, gammas, true); - test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas); - test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr); - test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr); - test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr); + test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true); + test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr, true); + test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr, true); + test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr, true); } DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) { constexpr size_t tableSize = 10; void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize + sizeof(SkColorSpaceTransferFn)); - sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels)); + sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas()); float* table = SkTAddOffset<float>(memory, sizeof(SkGammas)); table[0] = 0.00f; @@ -245,18 +239,18 @@ DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) { params->fF = 0.0f; params->fG = 2.4f; - gammas->fType[0] = SkGammas::Type::kValue_Type; - gammas->fData[0].fValue = 1.2f; + gammas->fRedType = SkGammas::Type::kValue_Type; + gammas->fRedData.fValue = 1.2f; - gammas->fType[1] = SkGammas::Type::kTable_Type; - gammas->fData[1].fTable.fSize = tableSize; - gammas->fData[1].fTable.fOffset = 0; + gammas->fGreenType = SkGammas::Type::kTable_Type; + gammas->fGreenData.fTable.fSize = tableSize; + gammas->fGreenData.fTable.fOffset = 0; - gammas->fType[2] = SkGammas::Type::kParam_Type; - gammas->fData[2].fParamOffset = sizeof(float) * tableSize; + gammas->fBlueType = SkGammas::Type::kParam_Type; + gammas->fBlueData.fParamOffset = sizeof(float) * tableSize; test_identity_xform(r, gammas, true); - test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas); + test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true); } DEF_TEST(ColorSpaceXform_A2BCLUT, r) { @@ -264,7 +258,7 @@ DEF_TEST(ColorSpaceXform_A2BCLUT, r) { constexpr int gp = 4; // # grid points constexpr int numEntries = gp*gp*gp*3; - const uint8_t gridPoints[3] = {gp, gp, gp}; + uint8_t gridPoints[3] = {gp, gp, gp}; void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries); sk_sp<SkColorLookUpTable> colorLUT(new (memory) SkColorLookUpTable(inputChannels, gridPoints)); // make a CLUT that rotates R, G, and B ie R->G, G->B, B->R @@ -302,7 +296,6 @@ DEF_TEST(ColorSpaceXform_A2BCLUT, r) { std::vector<SkColorSpace_A2B::Element> srcElements; srcElements.push_back(SkColorSpace_A2B::Element(std::move(colorLUT))); auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ, - SkColorSpace_Base::InputColorFormat::kRGB, std::move(srcElements)); // dst space is entirely identity auto dstSpace = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, SkMatrix44::I()); |