Added CMYK support for ICC profiles.

Changed ICC parsing/SkGammas/SkColorLookUpTable to handle non-3-channel
inputs. Parsed CMYK A2B ICC profiles. Integrated this with SkJpegCodec
(the only file that supports CMYK) and SkColorSpaceXform_A2B to allow
parsing and color xforming of ICC CMYK images.

BUG=skia:

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=5197
CQ_INCLUDE_TRYBOTS=skia.primary:Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD


Change-Id: Id6619f63f04071f79cd2d84321857dfa269ad3aa
Reviewed-on: https://skia-review.googlesource.com/5197
Commit-Queue: Mike Klein <mtklein@chromium.org>
Reviewed-by: Matt Sarett <msarett@google.com>
Reviewed-by: Mike Klein <mtklein@chromium.org>
Reviewed-by: Leon Scroggins <scroggo@google.com>
This commit is contained in:
raftias 2016-11-30 11:19:22 -05:00 committed by Skia Commit-Bot
parent 14c8f82334
commit 51c3fcd376
17 changed files with 653 additions and 619 deletions

View File

@ -1018,11 +1018,7 @@ 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) {
// 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));
return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r);
}
switch (fMode) {

View File

@ -1,192 +0,0 @@
/*
* 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; )

View File

@ -171,7 +171,6 @@ gm_sources = [
"$_gm/imagetoyuvplanes.cpp",
"$_gm/internal_links.cpp",
"$_gm/inversepaths.cpp",
"$_gm/labpcsdemo.cpp",
"$_gm/largeglyphblur.cpp",
"$_gm/lattice.cpp",
"$_gm/lcdblendmodes.cpp",

View File

@ -11,6 +11,7 @@
#include "SkJpegDecoderMgr.h"
#include "SkCodecPriv.h"
#include "SkColorPriv.h"
#include "SkColorSpace_Base.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkTypes.h"
@ -229,7 +230,18 @@ bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut,
sk_sp<SkData> iccData = get_icc_profile(decoderMgr->dinfo());
sk_sp<SkColorSpace> colorSpace = nullptr;
if (iccData) {
colorSpace = SkColorSpace::MakeICC(iccData->data(), iccData->size());
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);
if (!colorSpace) {
SkCodecPrintf("Could not create SkColorSpace from ICC data.\n");
}
@ -368,9 +380,6 @@ 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()) {
@ -569,7 +578,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) {
if (JCS_CMYK == colorSpace && nullptr == this->colorXform()) {
this->initializeSwizzler(dstInfo, options);
}

View File

@ -8,7 +8,23 @@
#include "SkColorLookUpTable.h"
#include "SkFloatingPoint.h"
void SkColorLookUpTable::interp3D(float dst[3], float src[3]) const {
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);
// Call the src components x, y, and z.
const uint8_t maxX = fGridPoints[0] - 1;
const uint8_t maxY = fGridPoints[1] - 1;
@ -111,3 +127,36 @@ void SkColorLookUpTable::interp3D(float dst[3], float src[3]) 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;
}

View File

@ -11,25 +11,49 @@
#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[3]) {
SkASSERT(3 == inputChannels);
memcpy(fGridPoints, gridPoints, 3 * sizeof(uint8_t));
SkColorLookUpTable(uint8_t inputChannels, const uint8_t gridPoints[kMaxColorChannels])
: fInputChannels(inputChannels) {
SkASSERT(inputChannels >= 1 && inputChannels <= kMaxColorChannels);
memcpy(fGridPoints, gridPoints, fInputChannels * sizeof(uint8_t));
}
void interp3D(float dst[3], float src[3]) const;
/**
* 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; }
private:
const float* table() const {
return SkTAddOffset<const float>(this, sizeof(SkColorLookUpTable));
}
uint8_t fGridPoints[3];
/**
* Performs tetrahedral interpolation with 3 input and 3 output dimensions.
* |dst| can be |src|
*/
void interp3D(float* dst, const float* src) const;
friend class SkColorSpaceXform_A2B;
// 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 fInputChannels;
uint8_t fGridPoints[kMaxColorChannels];
public:
// Objects of this type are created in a custom fashion using sk_malloc_throw

View File

@ -178,18 +178,15 @@ 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());
sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
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;
gammas->fRedData = data;
gammas->fGreenData = data;
gammas->fBlueData = data;
for (int channel = 0; channel < 3; ++channel) {
gammas->fType[channel] = SkGammas::Type::kParam_Type;
gammas->fData[channel] = data;
}
return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
std::move(gammas), toXYZD50, nullptr));
}

View File

@ -169,16 +169,33 @@ 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->addTransferFn(fn, kRGB_Channels);
this->addTransferFns(fn, currentChannels);
fElementsPipeline.append(SkRasterPipeline::clamp_0);
fElementsPipeline.append(SkRasterPipeline::clamp_1);
@ -187,23 +204,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 < 3; ++channel) {
for (int channel = 0; channel < gammas.channels(); ++channel) {
SkCSXformPrintf(" %s", debugGammas[(int)gammas.type(channel)]);
}
SkCSXformPrintf("\n");
bool gammaNeedsRef = false;
for (int channel = 0; channel < 3; ++channel) {
for (int channel = 0; channel < gammas.channels(); ++channel) {
if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
SkTableTransferFn table = {
gammas.table(channel),
gammas.data(channel).fTable.fSize,
};
this->addTableFn(table, static_cast<Channels>(channel));
this->addTableFn(table, channel);
gammaNeedsRef = true;
} else {
SkColorSpaceTransferFn fn = gamma_to_parametric(gammas, channel);
this->addTransferFn(fn, static_cast<Channels>(channel));
this->addTransferFn(fn, channel);
}
}
if (gammaNeedsRef) {
@ -215,8 +232,8 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
break;
}
case SkColorSpace_A2B::Element::Type::kCLUT:
SkCSXformPrintf("CLUT stage added [%d][%d][%d]\n", e.colorLUT().fGridPoints[0],
e.colorLUT().fGridPoints[1], e.colorLUT().fGridPoints[2]);
SkCSXformPrintf("CLUT (%d -> %d) stage added\n", e.colorLUT().inputChannels(),
e.colorLUT().outputChannels());
fCLUTs.push_back(sk_ref_sp(&e.colorLUT()));
fElementsPipeline.append(SkRasterPipeline::color_lookup_table,
fCLUTs.back().get());
@ -230,6 +247,8 @@ 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");
@ -245,7 +264,7 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
if (!fLinearDstGamma) {
SkColorSpaceTransferFn fn =
invert_parametric(gammanamed_to_parametric(dstSpace->gammaNamed()));
this->addTransferFn(fn, kRGB_Channels);
this->addTransferFns(fn, 3);
fElementsPipeline.append(SkRasterPipeline::clamp_0);
fElementsPipeline.append(SkRasterPipeline::clamp_1);
}
@ -261,10 +280,10 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
};
fTableStorage.push_front(std::move(storage));
this->addTableFn(table, static_cast<Channels>(channel));
this->addTableFn(table, channel);
} else {
SkColorSpaceTransferFn fn = invert_parametric(gamma_to_parametric(gammas, channel));
this->addTransferFn(fn, static_cast<Channels>(channel));
this->addTransferFn(fn, channel);
}
}
@ -273,45 +292,47 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
}
}
void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, Channels channels) {
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) {
fTransferFns.push_front(fn);
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:
switch (channelIndex) {
case 0:
fElementsPipeline.append(SkRasterPipeline::parametric_r, &fTransferFns.front());
break;
case kG_Channels:
case 1:
fElementsPipeline.append(SkRasterPipeline::parametric_g, &fTransferFns.front());
break;
case kB_Channels:
case 2:
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, Channels channels) {
void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, int channelIndex) {
fTableTransferFns.push_front(fn);
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:
switch (channelIndex) {
case 0:
fElementsPipeline.append(SkRasterPipeline::table_r, &fTableTransferFns.front());
break;
case kG_Channels:
case 1:
fElementsPipeline.append(SkRasterPipeline::table_g, &fTableTransferFns.front());
break;
case kB_Channels:
case 2:
fElementsPipeline.append(SkRasterPipeline::table_b, &fTableTransferFns.front());
break;
case 3:
fElementsPipeline.append(SkRasterPipeline::table_a, &fTableTransferFns.front());
break;
default:
SkASSERT(false);
}

View File

@ -32,17 +32,11 @@ public:
private:
SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, SkColorSpace_XYZ* dstSpace);
enum Channels {
kRGB_Channels = -1,
kR_Channels = 0,
kG_Channels = 1,
kB_Channels = 2
};
void addTransferFns(const SkColorSpaceTransferFn& fn, int channelCount);
void addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex);
void addTransferFn(const SkColorSpaceTransferFn& fn, Channels channels);
void addTableFn(const SkTableTransferFn& table, Channels channels);
void addTableFn(const SkTableTransferFn& table, int channelIndex);
void addMatrix(const SkMatrix44& matrix);

View File

@ -7,9 +7,10 @@
#include "SkColorSpace_A2B.h"
SkColorSpace_A2B::SkColorSpace_A2B(PCS pcs, sk_sp<SkData> profileData,
std::vector<Element> elements)
SkColorSpace_A2B::SkColorSpace_A2B(InputColorFormat inputColorFormat, std::vector<Element> elements,
PCS pcs, sk_sp<SkData> profileData)
: INHERITED(std::move(profileData))
, fPCS(pcs)
, fInputColorFormat(inputColorFormat)
, fElements(std::move(elements))
, fPCS(pcs)
{}

View File

@ -16,14 +16,15 @@
// 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.
// 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).
//
// 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.
// 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.
class SkColorSpace_A2B : public SkColorSpace_Base {
public:
const SkMatrix44* toXYZD50() const override {
@ -45,12 +46,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;
@ -71,29 +72,37 @@ public:
class Element {
public:
explicit Element(SkGammaNamed gammaNamed)
Element(SkGammaNamed gammaNamed, int channelCount)
: 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)
, fMatrix(SkMatrix44::kUninitialized_Constructor)
, fInputChannels(fGammas->channels())
, fOutputChannels(fGammas->channels())
{}
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,
@ -123,15 +132,21 @@ 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(size_t i) const { return fElements[i]; }
const Element& element(int i) const { return fElements[i]; }
int count() const { return (int)fElements.size(); }
// the intermediate profile connection space that this color space
@ -140,16 +155,20 @@ public:
kLAB, // CIELAB
kXYZ // CIEXYZ
};
PCS pcs() const { return fPCS; }
InputColorFormat inputColorFormat() const { return fInputColorFormat; }
private:
SkColorSpace_A2B(PCS pcs, sk_sp<SkData> profileData, std::vector<Element> elements);
SkColorSpace_A2B(InputColorFormat inputColorFormat, std::vector<Element> elements, PCS pcs,
sk_sp<SkData> profileData);
PCS fPCS;
InputColorFormat fInputColorFormat;
std::vector<Element> fElements;
PCS fPCS;
friend class SkColorSpace;
friend class SkColorSpace_Base;
friend class ColorSpaceXformTest;
typedef SkColorSpace_Base INHERITED;
};

View File

@ -83,17 +83,8 @@ struct SkGammas : SkRefCnt {
}
const Data& data(int i) const {
switch (i) {
case 0:
return fRedData;
case 1:
return fGreenData;
case 2:
return fBlueData;
default:
SkASSERT(false);
return fRedData;
}
SkASSERT(i >= 0 && i < fChannels);
return fData[i];
}
const float* table(int i) const {
@ -107,32 +98,24 @@ struct SkGammas : SkRefCnt {
}
Type type(int i) const {
switch (i) {
case 0:
return fRedType;
case 1:
return fGreenType;
case 2:
return fBlueType;
default:
SkASSERT(false);
return fRedType;
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;
}
}
SkGammas()
: fRedType(Type::kNone_Type)
, fGreenType(Type::kNone_Type)
, fBlueType(Type::kNone_Type)
{}
// These fields should only be modified when initializing the struct.
Data fRedData;
Data fGreenData;
Data fBlueData;
Type fRedType;
Type fGreenType;
Type fBlueType;
uint8_t fChannels;
Data fData[kMaxColorChannels];
Type fType[kMaxColorChannels];
// 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
@ -188,9 +171,17 @@ 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);

View File

@ -49,6 +49,7 @@ 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');
@ -127,9 +128,19 @@ struct ICCProfileHeader {
fProfileClass == kColorSpace_Profile,
"Unsupported profile");
// 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 (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;
}
switch (fPCS) {
case kXYZ_PCSSpace:
@ -140,7 +151,9 @@ struct ICCProfileHeader {
break;
default:
// ICC currently (V4.3) only specifices XYZ and Lab PCS spaces
SkColorSpacePrintf("Unsupported PCS space\n");
SkColorSpacePrintf("Unsupported PCS space: %c%c%c%c\n",
(fPCS>>24)&0xFF, (fPCS>>16)&0xFF,
(fPCS>> 8)&0xFF, (fPCS>> 0)&0xFF);
return false;
}
@ -616,7 +629,6 @@ 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]) {
@ -735,14 +747,12 @@ static bool load_matrix(SkMatrix44* matrix, const uint8_t* src, size_t len, bool
}
static inline SkGammaNamed is_named(const sk_sp<SkGammas>& gammas) {
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;
for (uint8_t i = 0; i < gammas->channels(); ++i) {
if (!gammas->isNamed(i) || gammas->data(i).fNamed != gammas->data(0).fNamed) {
return kNonStandard_SkGammaNamed;
}
}
return kNonStandard_SkGammaNamed;
return gammas->data(0).fNamed;
}
/**
@ -752,93 +762,87 @@ 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 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 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
*
* @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,
const uint8_t* rTagPtr, size_t tagLen)
{
SkGammas::Data rData;
SkColorSpaceTransferFn rParams;
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;
*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;
SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, rTagPtr, tagLen);
handle_invalid_gamma(&rType, &rData);
type[0] = parse_gamma(&data[0], &params[0], &tagBytes, tagPtr[0], tagLen);
handle_invalid_gamma(&type[0], &data[0]);
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;
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;
} else {
size_t allocSize = sizeof(SkGammas);
return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize),
return_if_false(safe_add(allocSize, gamma_alloc_size(type[0], data[0]), &allocSize),
"SkGammas struct is too large to allocate");
void* memory = sk_malloc_throw(allocSize);
*gammas = sk_sp<SkGammas>(new (memory) SkGammas());
load_gammas(memory, 0, rType, &rData, rParams, rTagPtr);
*gammas = sk_sp<SkGammas>(new (memory) SkGammas(inputChannels));
load_gammas(memory, 0, type[0], &data[0], params[0], tagPtr[0]);
(*gammas)->fRedType = rType;
(*gammas)->fGreenType = rType;
(*gammas)->fBlueType = rType;
(*gammas)->fRedData = rData;
(*gammas)->fGreenData = rData;
(*gammas)->fBlueData = rData;
for (uint8_t channel = 0; channel < inputChannels; ++channel) {
(*gammas)->fType[channel] = type[0];
(*gammas)->fData[channel] = data[0];
}
}
} 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);
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], &params[channel], &tagBytes,
tagPtr[channel], tagLen);
handle_invalid_gamma(&type[channel], &data[channel]);
alignedTagBytes = SkAlign4(tagBytes);
}
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");
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");
}
void* memory = sk_malloc_throw(allocSize);
*gammas = sk_sp<SkGammas>(new (memory) SkGammas());
*gammas = sk_sp<SkGammas>(new (memory) SkGammas(inputChannels));
uint32_t offset = 0;
(*gammas)->fRedType = rType;
offset += load_gammas(memory, offset, rType, &rData, rParams, rTagPtr);
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)->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) {
@ -889,7 +893,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());
*gammas = sk_sp<SkGammas>(new (memory) SkGammas(numTables));
for (size_t tableIndex = 0; tableIndex < numTablesToUse; ++tableIndex) {
const uint8_t* ptr = src + readBytesPerChannel * tableIndex;
@ -906,24 +910,18 @@ static bool load_lut_gammas(sk_sp<SkGammas>* gammas, size_t numTables, size_t en
}
}
(*gammas)->fRedType = SkGammas::Type::kTable_Type;
(*gammas)->fGreenType = SkGammas::Type::kTable_Type;
(*gammas)->fBlueType = SkGammas::Type::kTable_Type;
SkASSERT(1 == numTablesToUse|| numTables == numTablesToUse);
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;
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;
}
}
(*gammas)->fRedData.fTable.fSize = entriesPerTable;
(*gammas)->fGreenData.fTable.fSize = entriesPerTable;
(*gammas)->fBlueData.fTable.fSize = entriesPerTable;
return true;
}
@ -934,14 +932,22 @@ 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 (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");
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);
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
@ -952,13 +958,14 @@ 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, src + offsetToACurves, tagLen)) {
if (!parse_and_load_gamma(&gammaNamed, &gammas, inputChannels, src + offsetToACurves,
tagLen)) {
return false;
}
if (gammas) {
elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
} else {
elements->push_back(SkColorSpace_A2B::Element(gammaNamed));
} else if (kLinear_SkGammaNamed != gammaNamed) {
elements->push_back(SkColorSpace_A2B::Element(gammaNamed, inputChannels));
}
}
@ -975,8 +982,8 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con
return false;
}
SkASSERT(3 == inputChannels);
uint8_t gridPoints[3];
SkASSERT(inputChannels <= kMaxColorChannels);
uint8_t gridPoints[kMaxColorChannels];
for (uint32_t i = 0; i < inputChannels; ++i) {
gridPoints[i] = clutSrc[i];
}
@ -996,13 +1003,14 @@ 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, src + offsetToMCurves, tagLen)) {
if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToMCurves,
tagLen)) {
return false;
}
if (gammas) {
elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
} else {
elements->push_back(SkColorSpace_A2B::Element(gammaNamed));
} else if (kLinear_SkGammaNamed != gammaNamed) {
elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels));
}
}
@ -1011,7 +1019,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 {
} else if (!matrix.isIdentity()) {
elements->push_back(SkColorSpace_A2B::Element(matrix));
}
}
@ -1021,13 +1029,14 @@ 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, src + offsetToBCurves, tagLen)) {
if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToBCurves,
tagLen)) {
return false;
}
if (gammas) {
elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
} else {
elements->push_back(SkColorSpace_A2B::Element(gammaNamed));
} else if (kLinear_SkGammaNamed != gammaNamed) {
elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels));
}
}
@ -1052,12 +1061,19 @@ 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 (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");
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);
return false;
}
@ -1066,7 +1082,13 @@ 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);
elements->push_back(SkColorSpace_A2B::Element(matrix));
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;
}
size_t dataOffset = 48;
// # of input table entries
@ -1111,12 +1133,21 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const
return false;
}
SkASSERT(inputGammas);
elements->push_back(SkColorSpace_A2B::Element(std::move(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)));
}
}
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[3] = {clutGridPoints, clutGridPoints, clutGridPoints};
const uint8_t gridPoints[kMaxColorChannels] = {
clutGridPoints, 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");
@ -1138,13 +1169,33 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const
return false;
}
SkASSERT(outputGammas);
elements->push_back(SkColorSpace_A2B::Element(std::move(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)));
}
}
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) {
size_t len, SkColorSpace_A2B::PCS pcs,
SkColorSpace_Base::InputColorFormat inputColorFormat) {
const uint32_t type = read_big_endian_u32(src);
switch (type) {
case kTAG_AtoBType:
@ -1153,26 +1204,54 @@ static bool load_a2b0(std::vector<SkColorSpace_A2B::Element>* elements, const ui
return false;
}
SkColorSpacePrintf("A2B0 tag is of type lutAtoBType\n");
return load_a2b0_a_to_b_type(elements, src, len, pcs);
if (!load_a2b0_a_to_b_type(elements, src, len, pcs)) {
return false;
}
break;
case kTAG_lut8Type:
if (len < 48) {
SkColorSpacePrintf("lut8 tag is too small (%d bytes).", len);
return false;
}
SkColorSpacePrintf("A2B0 tag of type lut8Type\n");
return load_a2b0_lutn_type(elements, src, len, pcs);
if (!load_a2b0_lutn_type(elements, src, len, pcs)) {
return false;
}
break;
case kTAG_lut16Type:
if (len < 52) {
SkColorSpacePrintf("lut16 tag is too small (%d bytes).", len);
return false;
}
SkColorSpacePrintf("A2B0 tag of type lut16Type\n");
return load_a2b0_lutn_type(elements, src, len, pcs);
if (!load_a2b0_lutn_type(elements, src, len, pcs)) {
return false;
}
break;
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;
}
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;
}
static bool tag_equals(const ICCTag* a, const ICCTag* b, const uint8_t* base) {
@ -1210,6 +1289,11 @@ 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");
}
@ -1228,6 +1312,22 @@ sk_sp<SkColorSpace> SkColorSpace::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");
@ -1257,102 +1357,84 @@ sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) {
}
}
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");
// 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;
}
// 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;
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;
}
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;
}
if (!g) {
g = r ? r : b;
}
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, &params, &tagBytes, r->addr(base), r->fLength);
handle_invalid_gamma(&type, &data);
if (!b) {
b = r ? r : g;
}
}
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));
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, &params, &tagBytes, r->addr(base), r->fLength);
handle_invalid_gamma(&type, &data);
gammas->fRedType = type;
gammas->fGreenType = type;
gammas->fBlueType = type;
gammas->fRedData = data;
gammas->fGreenData = data;
gammas->fBlueData = data;
}
if (SkGammas::Type::kNamed_Type == type) {
gammaNamed = data.fNamed;
} else {
SkGammas::Data rData;
SkColorSpaceTransferFn rParams;
@ -1373,53 +1455,81 @@ sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) {
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))
{
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());
gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
load_gammas(memory, 0, type, &data, params, r->addr(base));
uint32_t offset = 0;
gammas->fRedType = 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->fBlueType = bType;
load_gammas(memory, offset, bType, &bData, bParams, b->addr(base));
gammas->fRedData = rData;
gammas->fGreenData = gData;
gammas->fBlueData = bData;
for (int i = 0; i < 3; ++i) {
gammas->fType[i] = type;
gammas->fData[i] = data;
}
}
} else {
// Guess sRGB if the profile is missing transfer functions.
gammaNamed = kSRGB_SkGammaNamed;
}
SkGammas::Data rData;
SkColorSpaceTransferFn rParams;
SkGammas::Type rType =
parse_gamma(&rData, &rParams, &tagBytes, r->addr(base), r->fLength);
handle_invalid_gamma(&rType, &rData);
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);
}
SkGammas::Data gData;
SkColorSpaceTransferFn gParams;
SkGammas::Type gType =
parse_gamma(&gData, &gParams, &tagBytes, g->addr(base), g->fLength);
handle_invalid_gamma(&gType, &gData);
if (kNonStandard_SkGammaNamed == gammaNamed) {
return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed,
std::move(gammas),
mat, std::move(data)));
}
SkGammas::Data bData;
SkColorSpaceTransferFn bParams;
SkGammas::Type bType =
parse_gamma(&bData, &bParams, &tagBytes, b->addr(base), b->fLength);
handle_invalid_gamma(&bType, &bData);
return SkColorSpace_Base::MakeRGB(gammaNamed, mat);
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->fType[0] = rType;
offset += load_gammas(memory, offset, rType, &rData, rParams,
r->addr(base));
gammas->fType[1] = gType;
offset += load_gammas(memory, offset, gType, &gData, gParams,
g->addr(base));
gammas->fType[2] = bType;
load_gammas(memory, offset, bType, &bData, bParams, b->addr(base));
gammas->fData[0] = rData;
gammas->fData[1] = gData;
gammas->fData[2] = bData;
}
} 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)));
}
return SkColorSpace_Base::MakeRGB(gammaNamed, mat);
}
default:
break;
}
return_null("ICC profile contains unsupported colorspace");

View File

@ -31,8 +31,9 @@ SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gamm
, fGammas(std::move(gammas))
, fToXYZD50(toXYZD50)
, fToXYZD50Hash(SkGoodHash()(toXYZD50))
, fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
{}
, fFromXYZD50(SkMatrix44::kUninitialized_Constructor) {
SkASSERT(!fGammas || 3 == fGammas->channels());
}
const SkMatrix44* SkColorSpace_XYZ::fromXYZD50() const {
fFromXYZOnce([this] {

View File

@ -76,7 +76,8 @@
M(matrix_2x3) M(matrix_3x4) M(matrix_4x5) \
M(matrix_perspective) \
M(parametric_r) M(parametric_g) M(parametric_b) \
M(table_r) M(table_g) M(table_b) \
M(parametric_a) \
M(table_r) M(table_g) M(table_b) M(table_a) \
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) \

View File

@ -632,6 +632,7 @@ 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];
@ -643,23 +644,29 @@ 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;
float rgb[3];
SkASSERT(3 == colorLUT->inputChannels() || 4 == colorLUT->inputChannels());
SkASSERT(3 == colorLUT->outputChannels());
float result[3][N];
for (int i = 0; i < N; ++i) {
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];
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];
}
}
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) {

View File

@ -16,6 +16,8 @@
#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) {
@ -40,13 +42,16 @@ 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));
srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed, kChannels));
}
srcElements.push_back(SkColorSpace_A2B::Element(arbitraryMatrix));
auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
std::move(srcElements));
auto srcSpace =
ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
SkColorSpace_Base::InputColorFormat::kRGB,
std::move(srcElements));
sk_sp<SkColorSpace> dstSpace(new SkColorSpace_XYZ(gammaNamed, gammas, arbitraryMatrix,
nullptr));
@ -55,13 +60,15 @@ 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(pcs, nullptr, std::move(elements)));
return sk_sp<SkColorSpace>(new SkColorSpace_A2B(inputColorFormat, std::move(elements),
pcs, nullptr));
}
};
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,
@ -101,7 +108,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, bool repeat) {
const sk_sp<SkGammas>& gammas) {
// Arbitrary set of 10 pixels
constexpr int width = 10;
constexpr uint32_t srcPixels[width] = {
@ -128,24 +135,19 @@ 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());
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;
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;
}
float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
table[0] = 0.00f;
@ -159,16 +161,18 @@ 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, true);
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
}
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());
gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kParam_Type;
gammas->fRedData.fParamOffset = gammas->fGreenData.fParamOffset =
gammas->fBlueData.fParamOffset = 0;
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;
}
SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>
(memory, sizeof(SkGammas));
@ -186,36 +190,38 @@ 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, true);
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
}
DEF_TEST(ColorSpaceXform_ExponentialGamma, r) {
// Exponential gamma curves
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;
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;
}
test_identity_xform(r, gammas, true);
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
}
DEF_TEST(ColorSpaceXform_NamedGamma, r) {
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;
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;
test_identity_xform(r, gammas, true);
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);
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);
}
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());
sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
table[0] = 0.00f;
@ -239,18 +245,18 @@ DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) {
params->fF = 0.0f;
params->fG = 2.4f;
gammas->fRedType = SkGammas::Type::kValue_Type;
gammas->fRedData.fValue = 1.2f;
gammas->fType[0] = SkGammas::Type::kValue_Type;
gammas->fData[0].fValue = 1.2f;
gammas->fGreenType = SkGammas::Type::kTable_Type;
gammas->fGreenData.fTable.fSize = tableSize;
gammas->fGreenData.fTable.fOffset = 0;
gammas->fType[1] = SkGammas::Type::kTable_Type;
gammas->fData[1].fTable.fSize = tableSize;
gammas->fData[1].fTable.fOffset = 0;
gammas->fBlueType = SkGammas::Type::kParam_Type;
gammas->fBlueData.fParamOffset = sizeof(float) * tableSize;
gammas->fType[2] = SkGammas::Type::kParam_Type;
gammas->fData[2].fParamOffset = sizeof(float) * tableSize;
test_identity_xform(r, gammas, true);
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
}
DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
@ -258,7 +264,7 @@ DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
constexpr int gp = 4; // # grid points
constexpr int numEntries = gp*gp*gp*3;
uint8_t gridPoints[3] = {gp, gp, gp};
const 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
@ -296,6 +302,7 @@ 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());