Add support for transfer functions to GrColorSpaceXform
With this change, untagged sources (eg N32) are treated as sRGB data, which causes a huge number of GMs to render more correctly in GPU sRGB/F16/etc... configs. Also, because the sources are treated as having a color space, we actually do gamut conversion for wide or narrow gamut outputs. This change also applies the transfer function math to individual colors in the case of gradient stops and color shaders. (The CPU backend doesn't do this yet, but I think we've decided there's no reason not to support it). Bug: skia: Change-Id: If76e9e4a268f9f74110ff4bbe4fe189ba5d19d9f Reviewed-on: https://skia-review.googlesource.com/64100 Reviewed-by: Brian Salomon <bsalomon@google.com> Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
parent
761f44853f
commit
f06ead925c
@ -54,6 +54,20 @@ struct SK_API SkColorSpaceTransferFn {
|
||||
* this one.
|
||||
*/
|
||||
SkColorSpaceTransferFn invert() const;
|
||||
|
||||
/**
|
||||
* Transform a single float by this transfer function.
|
||||
* For negative inputs, returns sign(x) * f(abs(x)).
|
||||
*/
|
||||
float operator()(float x) {
|
||||
SkScalar s = SkScalarSignAsScalar(x);
|
||||
x = sk_float_abs(x);
|
||||
if (x >= fD) {
|
||||
return s * (powf(fA * x + fB, fG) + fE);
|
||||
} else {
|
||||
return s * (fC * x + fF);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SK_API SkColorSpace : public SkRefCnt {
|
||||
|
@ -368,8 +368,11 @@ sk_sp<SkSpecialImage> SkImageFilter::ImageToColorSpace(SkSpecialImage* src,
|
||||
// object. If that produces something, then both are tagged, and the source is in a different
|
||||
// gamut than the dest. There is some overhead to making the xform, but those are cached, and
|
||||
// if we get one back, that means we're about to use it during the conversion anyway.
|
||||
sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(src->getColorSpace(),
|
||||
outProps.colorSpace());
|
||||
//
|
||||
// TODO: Fix this check, to handle wider support of transfer functions, config mismatch, etc.
|
||||
// For now, continue to just check if gamut is different, which may not be sufficient.
|
||||
auto colorSpaceXform = GrColorSpaceXform::MakeGamutXform(src->getColorSpace(),
|
||||
outProps.colorSpace());
|
||||
|
||||
if (!colorSpaceXform) {
|
||||
// No xform needed, just return the original image
|
||||
|
@ -172,9 +172,10 @@ sk_sp<SkSpecialImage> SkAlphaThresholdFilterImpl::onFilterImage(SkSpecialImage*
|
||||
}
|
||||
|
||||
const OutputProperties& outProps = ctx.outputProperties();
|
||||
GrPixelConfig inputConfig = inputProxy->config();
|
||||
auto textureFP = GrSimpleTextureEffect::Make(std::move(inputProxy), SkMatrix::I());
|
||||
textureFP = GrColorSpaceXformEffect::Make(std::move(textureFP), input->getColorSpace(),
|
||||
outProps.colorSpace());
|
||||
inputConfig, outProps.colorSpace());
|
||||
if (!textureFP) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -306,12 +306,13 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU(
|
||||
if (backgroundProxy) {
|
||||
SkMatrix backgroundMatrix = SkMatrix::MakeTrans(-SkIntToScalar(backgroundOffset.fX),
|
||||
-SkIntToScalar(backgroundOffset.fY));
|
||||
GrPixelConfig bgConfig = backgroundProxy->config();
|
||||
bgFP = GrTextureDomainEffect::Make(
|
||||
std::move(backgroundProxy), backgroundMatrix,
|
||||
GrTextureDomain::MakeTexelDomain(background->subset()),
|
||||
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
|
||||
bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
|
||||
outputProperties.colorSpace());
|
||||
bgConfig, outputProperties.colorSpace());
|
||||
} else {
|
||||
bgFP = GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
|
||||
GrConstColorProcessor::kIgnore_InputMode);
|
||||
@ -320,12 +321,13 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU(
|
||||
if (foregroundProxy) {
|
||||
SkMatrix foregroundMatrix = SkMatrix::MakeTrans(-SkIntToScalar(foregroundOffset.fX),
|
||||
-SkIntToScalar(foregroundOffset.fY));
|
||||
GrPixelConfig fgConfig = foregroundProxy->config();
|
||||
auto foregroundFP = GrTextureDomainEffect::Make(
|
||||
std::move(foregroundProxy), foregroundMatrix,
|
||||
GrTextureDomain::MakeTexelDomain(foreground->subset()),
|
||||
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
|
||||
foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
|
||||
foreground->getColorSpace(),
|
||||
foreground->getColorSpace(), fgConfig,
|
||||
outputProperties.colorSpace());
|
||||
paint.addColorFragmentProcessor(std::move(foregroundFP));
|
||||
|
||||
|
@ -286,6 +286,7 @@ sk_sp<SkSpecialImage> SkDisplacementMapEffect::onFilterImage(SkSpecialImage* sou
|
||||
SkIntToScalar(colorOffset.fY - displOffset.fY));
|
||||
SkColorSpace* colorSpace = ctx.outputProperties().colorSpace();
|
||||
|
||||
GrPixelConfig colorConfig = colorProxy->config();
|
||||
std::unique_ptr<GrFragmentProcessor> fp =
|
||||
GrDisplacementMapEffect::Make(fXChannelSelector,
|
||||
fYChannelSelector,
|
||||
@ -294,7 +295,8 @@ sk_sp<SkSpecialImage> SkDisplacementMapEffect::onFilterImage(SkSpecialImage* sou
|
||||
offsetMatrix,
|
||||
std::move(colorProxy),
|
||||
SkISize::Make(color->width(), color->height()));
|
||||
fp = GrColorSpaceXformEffect::Make(std::move(fp), color->getColorSpace(), colorSpace);
|
||||
fp = GrColorSpaceXformEffect::Make(std::move(fp), color->getColorSpace(), colorConfig,
|
||||
colorSpace);
|
||||
|
||||
GrPaint paint;
|
||||
paint.addColorFragmentProcessor(std::move(fp));
|
||||
|
@ -355,6 +355,7 @@ sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* sour
|
||||
offset->fY = bounds.top();
|
||||
bounds.offset(-inputOffset);
|
||||
|
||||
GrPixelConfig inputConfig = inputProxy->config();
|
||||
auto fp = GrMagnifierEffect::Make(std::move(inputProxy),
|
||||
bounds,
|
||||
fSrcRect,
|
||||
@ -363,7 +364,7 @@ sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* sour
|
||||
bounds.width() * invInset,
|
||||
bounds.height() * invInset);
|
||||
fp = GrColorSpaceXformEffect::Make(std::move(fp), input->getColorSpace(),
|
||||
ctx.outputProperties().colorSpace());
|
||||
inputConfig, ctx.outputProperties().colorSpace());
|
||||
if (!fp) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -295,12 +295,13 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::filterImageGPU(
|
||||
if (backgroundProxy) {
|
||||
SkMatrix bgMatrix = SkMatrix::MakeTrans(-SkIntToScalar(backgroundOffset.fX),
|
||||
-SkIntToScalar(backgroundOffset.fY));
|
||||
GrPixelConfig bgConfig = backgroundProxy->config();
|
||||
bgFP = GrTextureDomainEffect::Make(std::move(backgroundProxy), bgMatrix,
|
||||
GrTextureDomain::MakeTexelDomain(background->subset()),
|
||||
GrTextureDomain::kDecal_Mode,
|
||||
GrSamplerState::Filter::kNearest);
|
||||
bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
|
||||
outputProperties.colorSpace());
|
||||
bgConfig, outputProperties.colorSpace());
|
||||
} else {
|
||||
bgFP = GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
|
||||
GrConstColorProcessor::kIgnore_InputMode);
|
||||
@ -309,12 +310,13 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::filterImageGPU(
|
||||
if (foregroundProxy) {
|
||||
SkMatrix fgMatrix = SkMatrix::MakeTrans(-SkIntToScalar(foregroundOffset.fX),
|
||||
-SkIntToScalar(foregroundOffset.fY));
|
||||
GrPixelConfig fgConfig = foregroundProxy->config();
|
||||
auto foregroundFP = GrTextureDomainEffect::Make(
|
||||
std::move(foregroundProxy), fgMatrix,
|
||||
GrTextureDomain::MakeTexelDomain(foreground->subset()),
|
||||
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
|
||||
foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
|
||||
foreground->getColorSpace(),
|
||||
foreground->getColorSpace(), fgConfig,
|
||||
outputProperties.colorSpace());
|
||||
paint.addColorFragmentProcessor(std::move(foregroundFP));
|
||||
|
||||
|
@ -17,7 +17,8 @@ GrColorSpaceXform* GrColorSpaceInfo::colorSpaceXformFromSRGB() const {
|
||||
if (!fInitializedColorSpaceXformFromSRGB) {
|
||||
// sRGB sources are very common (SkColor, etc...), so we cache that gamut transformation
|
||||
auto srgbColorSpace = SkColorSpace::MakeSRGB();
|
||||
fColorXformFromSRGB = GrColorSpaceXform::Make(srgbColorSpace.get(), fColorSpace.get());
|
||||
fColorXformFromSRGB = GrColorSpaceXform::MakeGamutXform(srgbColorSpace.get(),
|
||||
fColorSpace.get());
|
||||
fInitializedColorSpaceXformFromSRGB = true;
|
||||
}
|
||||
// You can't be color-space aware in legacy mode
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "GrColorSpaceXform.h"
|
||||
#include "SkColorSpace.h"
|
||||
#include "SkColorSpacePriv.h"
|
||||
#include "SkColorSpace_Base.h"
|
||||
#include "SkMatrix44.h"
|
||||
#include "SkSpinlock.h"
|
||||
@ -56,56 +57,114 @@ private:
|
||||
uint64_t fSequence;
|
||||
};
|
||||
|
||||
GrColorSpaceXform::GrColorSpaceXform(const SkMatrix44& srcToDst)
|
||||
: fSrcToDst(srcToDst) {}
|
||||
GrColorSpaceXform::GrColorSpaceXform(const SkColorSpaceTransferFn& srcTransferFn,
|
||||
const SkMatrix44& gamutXform, uint32_t flags)
|
||||
: fSrcTransferFn(srcTransferFn), fGamutXform(gamutXform), fFlags(flags) {}
|
||||
|
||||
static SkSpinlock gColorSpaceXformCacheSpinlock;
|
||||
|
||||
sk_sp<GrColorSpaceXform> GrColorSpaceXform::Make(const SkColorSpace* src, const SkColorSpace* dst) {
|
||||
if (!src || !dst) {
|
||||
// Invalid
|
||||
sk_sp<GrColorSpaceXform> GrColorSpaceXform::Make(const SkColorSpace* src,
|
||||
GrPixelConfig srcConfig,
|
||||
const SkColorSpace* dst) {
|
||||
if (!dst) {
|
||||
// No transformation is performed in legacy mode
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (src == dst) {
|
||||
// Quick equality check - no conversion needed in this case
|
||||
// Treat null sources as sRGB
|
||||
if (!src) {
|
||||
if (GrPixelConfigIsFloatingPoint(srcConfig)) {
|
||||
src = SkColorSpace::MakeSRGBLinear().get();
|
||||
} else {
|
||||
src = SkColorSpace::MakeSRGB().get();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t flags = 0;
|
||||
SkColorSpaceTransferFn srcTransferFn;
|
||||
|
||||
// kUnknown_GrPixelConfig is a sentinel that means we don't care about transfer functions,
|
||||
// just the gamut xform.
|
||||
if (kUnknown_GrPixelConfig != srcConfig) {
|
||||
// Determine if src transfer function is needed, based on src config and color space
|
||||
if (GrPixelConfigIsSRGB(srcConfig)) {
|
||||
// Source texture is sRGB, will be converted to linear when we sample
|
||||
if (src->gammaCloseToSRGB()) {
|
||||
// Hardware linearize does the right thing
|
||||
} else if (src->gammaIsLinear()) {
|
||||
// Oops, need to undo the (extra) linearize
|
||||
flags |= kApplyInverseSRGB_Flag;
|
||||
} else if (src->isNumericalTransferFn(&srcTransferFn)) {
|
||||
// Need to undo the (extra) linearize, then apply the correct transfer function
|
||||
flags |= (kApplyInverseSRGB_Flag | kApplyTransferFn_Flag);
|
||||
} else {
|
||||
// We don't (yet) support more complex transfer functions
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// Source texture is some non-sRGB format, we consider it linearly encoded
|
||||
if (src->gammaIsLinear()) {
|
||||
// Linear sampling does the right thing
|
||||
} else if (src->isNumericalTransferFn(&srcTransferFn)) {
|
||||
// Need to manually apply some transfer function (including sRGB)
|
||||
flags |= kApplyTransferFn_Flag;
|
||||
} else {
|
||||
// We don't (yet) support more complex transfer functions
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (src == dst && (0 == flags)) {
|
||||
// Quick equality check - no conversion (or transfer function) needed in this case
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SkMatrix44* toXYZD50 = as_CSB(src)->toXYZD50();
|
||||
const SkMatrix44* fromXYZD50 = as_CSB(dst)->fromXYZD50();
|
||||
if (!toXYZD50 || !fromXYZD50) {
|
||||
// unsupported colour spaces -- cannot specify gamut as a matrix
|
||||
// Unsupported colour spaces -- cannot specify gamut as a matrix
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Determine if a gamut xform is needed
|
||||
uint32_t srcHash = as_CSB(src)->toXYZD50Hash();
|
||||
uint32_t dstHash = as_CSB(dst)->toXYZD50Hash();
|
||||
if (srcHash == dstHash) {
|
||||
// Identical gamut - no conversion needed in this case
|
||||
if (srcHash != dstHash) {
|
||||
flags |= kApplyGamutXform_Flag;
|
||||
} else {
|
||||
SkASSERT(*toXYZD50 == *as_CSB(dst)->toXYZD50() && "Hash collision");
|
||||
}
|
||||
|
||||
if (0 == flags) {
|
||||
// Identical gamut and no transfer function - no conversion needed in this case
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto deferredResult = [fromXYZD50, toXYZD50]() {
|
||||
auto makeXform = [srcTransferFn, fromXYZD50, toXYZD50, flags]() {
|
||||
SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor);
|
||||
srcToDst.setConcat(*fromXYZD50, *toXYZD50);
|
||||
return sk_make_sp<GrColorSpaceXform>(srcToDst);
|
||||
if (SkToBool(flags & kApplyGamutXform_Flag)) {
|
||||
srcToDst.setConcat(*fromXYZD50, *toXYZD50);
|
||||
} else {
|
||||
srcToDst.setIdentity();
|
||||
}
|
||||
return sk_make_sp<GrColorSpaceXform>(srcTransferFn, srcToDst, flags);
|
||||
};
|
||||
|
||||
if (gColorSpaceXformCacheSpinlock.tryAcquire()) {
|
||||
// For now, we only cache pure gamut xforms (no transfer functions)
|
||||
// TODO: Fold a hash of the transfer function into the cache key
|
||||
if ((kApplyGamutXform_Flag == flags) && gColorSpaceXformCacheSpinlock.tryAcquire()) {
|
||||
static GrColorSpaceXformCache* gCache;
|
||||
if (nullptr == gCache) {
|
||||
gCache = new GrColorSpaceXformCache();
|
||||
}
|
||||
|
||||
uint64_t key = static_cast<uint64_t>(srcHash) << 32 | static_cast<uint64_t>(dstHash);
|
||||
sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, deferredResult);
|
||||
sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, makeXform);
|
||||
gColorSpaceXformCacheSpinlock.release();
|
||||
return xform;
|
||||
} else {
|
||||
// Rather than wait for the spin lock, just bypass the cache
|
||||
return deferredResult();
|
||||
// If our xform has non-gamut components, or we can't get the spin lock, just build it
|
||||
return makeXform();
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,16 +173,36 @@ bool GrColorSpaceXform::Equals(const GrColorSpaceXform* a, const GrColorSpaceXfo
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!a || !b) {
|
||||
if (!a || !b || a->fFlags != b->fFlags) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a->fSrcToDst == b->fSrcToDst;
|
||||
if (SkToBool(a->fFlags & kApplyTransferFn_Flag) &&
|
||||
0 != memcmp(&a->fSrcTransferFn, &b->fSrcTransferFn, sizeof(SkColorSpaceTransferFn))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SkToBool(a->fFlags && kApplyGamutXform_Flag) && a->fGamutXform != b->fGamutXform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GrColor4f GrColorSpaceXform::unclampedXform(const GrColor4f& srcColor) {
|
||||
GrColor4f result;
|
||||
fSrcToDst.mapScalars(srcColor.fRGBA, result.fRGBA);
|
||||
// This transform step should only happen with textures (not CPU xform of individual values)
|
||||
SkASSERT(!SkToBool(fFlags & kApplyInverseSRGB_Flag));
|
||||
|
||||
GrColor4f result = srcColor;
|
||||
if (fFlags & kApplyTransferFn_Flag) {
|
||||
// Only transform RGB (not alpha)
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
result.fRGBA[i] = fSrcTransferFn(result.fRGBA[i]);
|
||||
}
|
||||
}
|
||||
if (fFlags & kApplyGamutXform_Flag) {
|
||||
fGamutXform.mapScalars(result.fRGBA, result.fRGBA);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -214,12 +293,13 @@ GrFragmentProcessor::OptimizationFlags GrColorSpaceXformEffect::OptFlags(
|
||||
std::unique_ptr<GrFragmentProcessor> GrColorSpaceXformEffect::Make(
|
||||
std::unique_ptr<GrFragmentProcessor> child,
|
||||
const SkColorSpace* src,
|
||||
GrPixelConfig srcConfig,
|
||||
const SkColorSpace* dst) {
|
||||
if (!child) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto colorXform = GrColorSpaceXform::Make(src, dst);
|
||||
auto colorXform = GrColorSpaceXform::Make(src, srcConfig, dst);
|
||||
if (colorXform) {
|
||||
return std::unique_ptr<GrFragmentProcessor>(
|
||||
new GrColorSpaceXformEffect(std::move(child), std::move(colorXform)));
|
||||
|
@ -10,29 +10,42 @@
|
||||
|
||||
#include "GrColor.h"
|
||||
#include "GrFragmentProcessor.h"
|
||||
#include "SkColorSpace.h"
|
||||
#include "SkMatrix44.h"
|
||||
#include "SkRefCnt.h"
|
||||
|
||||
class SkColorSpace;
|
||||
|
||||
/**
|
||||
* Represents a color gamut transformation (as a 4x4 color matrix)
|
||||
* Represents a color space transformation
|
||||
*/
|
||||
class GrColorSpaceXform : public SkRefCnt {
|
||||
public:
|
||||
GrColorSpaceXform(const SkMatrix44& srcToDst);
|
||||
GrColorSpaceXform(const SkColorSpaceTransferFn&, const SkMatrix44&, uint32_t);
|
||||
|
||||
static sk_sp<GrColorSpaceXform> Make(const SkColorSpace* src, const SkColorSpace* dst);
|
||||
static sk_sp<GrColorSpaceXform> Make(const SkColorSpace* src,
|
||||
GrPixelConfig srcConfig,
|
||||
const SkColorSpace* dst);
|
||||
static sk_sp<GrColorSpaceXform> MakeGamutXform(const SkColorSpace* src,
|
||||
const SkColorSpace* dst) {
|
||||
auto result = Make(src, kUnknown_GrPixelConfig, dst);
|
||||
SkASSERT(!result || 0 == (result->fFlags & ~kApplyGamutXform_Flag));
|
||||
return result;
|
||||
}
|
||||
|
||||
const SkMatrix44& srcToDst() const { return fSrcToDst; }
|
||||
const SkColorSpaceTransferFn& transferFn() const { return fSrcTransferFn; }
|
||||
const float* transferFnCoeffs() const {
|
||||
static_assert(0 == offsetof(SkColorSpaceTransferFn, fG), "TransferFn layout");
|
||||
return &fSrcTransferFn.fG;
|
||||
}
|
||||
|
||||
const SkMatrix44& gamutXform() const { return fGamutXform; }
|
||||
|
||||
/**
|
||||
* GrGLSLFragmentProcessor::GenKey() must call this and include the returned value in its
|
||||
* computed key.
|
||||
*/
|
||||
static uint32_t XformKey(const GrColorSpaceXform* xform) {
|
||||
// Code generation changes if there is an xform, but it otherwise constant
|
||||
return SkToBool(xform) ? 1 : 0;
|
||||
// Code generation depends on which steps we apply (as encoded by fFlags)
|
||||
return SkToBool(xform) ? xform->fFlags : 0;
|
||||
}
|
||||
|
||||
static bool Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b);
|
||||
@ -41,7 +54,21 @@ public:
|
||||
GrColor4f clampedXform(const GrColor4f& srcColor);
|
||||
|
||||
private:
|
||||
SkMatrix44 fSrcToDst;
|
||||
friend class GrGLSLColorSpaceXformHelper;
|
||||
|
||||
enum Flags {
|
||||
kApplyTransferFn_Flag = 0x1,
|
||||
kApplyGamutXform_Flag = 0x2,
|
||||
|
||||
// Almost never used. This handles the case where the src data is sRGB pixel config,
|
||||
// but the color space has a different transfer function. In that case, we first undo
|
||||
// the HW sRGB -> Linear conversion, before applying any other steps.
|
||||
kApplyInverseSRGB_Flag = 0x4,
|
||||
};
|
||||
|
||||
SkColorSpaceTransferFn fSrcTransferFn;
|
||||
SkMatrix44 fGamutXform;
|
||||
uint32_t fFlags;
|
||||
};
|
||||
|
||||
class GrColorSpaceXformEffect : public GrFragmentProcessor {
|
||||
@ -52,6 +79,7 @@ public:
|
||||
*/
|
||||
static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> child,
|
||||
const SkColorSpace* src,
|
||||
GrPixelConfig srcConfig,
|
||||
const SkColorSpace* dst);
|
||||
|
||||
const char* name() const override { return "ColorSpaceXform"; }
|
||||
|
@ -325,10 +325,10 @@ sk_sp<GrColorSpaceXform> TestColorXform(SkRandom* random) {
|
||||
sk_sp<SkColorSpace> adobe = SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kAdobeRGB_Named);
|
||||
// No gamut change
|
||||
gXforms[0] = nullptr;
|
||||
// To larger gamut
|
||||
gXforms[1] = GrColorSpaceXform::Make(srgb.get(), adobe.get());
|
||||
// To smaller gamut
|
||||
gXforms[2] = GrColorSpaceXform::Make(adobe.get(), srgb.get());
|
||||
// To larger gamut (with automatic transfer function)
|
||||
gXforms[1] = GrColorSpaceXform::Make(srgb.get(), kSRGBA_8888_GrPixelConfig, adobe.get());
|
||||
// To smaller gamut (with manual transfer function)
|
||||
gXforms[2] = GrColorSpaceXform::Make(adobe.get(), kRGBA_8888_GrPixelConfig, srgb.get());
|
||||
}
|
||||
return gXforms[random->nextULessThan(static_cast<uint32_t>(SK_ARRAY_COUNT(gXforms)))];
|
||||
}
|
||||
|
@ -156,7 +156,8 @@ std::unique_ptr<GrFragmentProcessor> GrTextureAdjuster::createFragmentProcessor(
|
||||
}
|
||||
SkASSERT(kNoDomain_DomainMode == domainMode ||
|
||||
(domain.fLeft <= domain.fRight && domain.fTop <= domain.fBottom));
|
||||
GrPixelConfig config = proxy->config();
|
||||
auto fp = CreateFragmentProcessorForDomainAndFilter(std::move(proxy), textureMatrix,
|
||||
domainMode, domain, filterOrNullForBicubic);
|
||||
return GrColorSpaceXformEffect::Make(std::move(fp), fColorSpace, dstColorSpace);
|
||||
return GrColorSpaceXformEffect::Make(std::move(fp), fColorSpace, config, dstColorSpace);
|
||||
}
|
||||
|
@ -118,9 +118,10 @@ std::unique_ptr<GrFragmentProcessor> GrTextureMaker::createFragmentProcessor(
|
||||
proxy.get(),
|
||||
nullptr, fmForDetermineDomain, &domain);
|
||||
SkASSERT(kTightCopy_DomainMode != domainMode);
|
||||
GrPixelConfig config = proxy->config();
|
||||
auto fp = CreateFragmentProcessorForDomainAndFilter(std::move(proxy), adjustedMatrix,
|
||||
domainMode, domain, filterOrNullForBicubic);
|
||||
return GrColorSpaceXformEffect::Make(std::move(fp), texColorSpace.get(), dstColorSpace);
|
||||
return GrColorSpaceXformEffect::Make(std::move(fp), texColorSpace.get(), config, dstColorSpace);
|
||||
}
|
||||
|
||||
sk_sp<GrTextureProxy> GrTextureMaker::generateTextureProxyForParams(const CopyParams& copyParams,
|
||||
|
@ -992,6 +992,7 @@ void SkGpuDevice::drawBitmapTile(const SkBitmap& bitmap,
|
||||
// Construct a GrPaint by setting the bitmap texture as the first effect and then configuring
|
||||
// the rest from the SkPaint.
|
||||
std::unique_ptr<GrFragmentProcessor> fp;
|
||||
GrPixelConfig config = proxy->config();
|
||||
|
||||
if (needsTextureDomain && (SkCanvas::kStrict_SrcRectConstraint == constraint)) {
|
||||
// Use a constrained texture domain to avoid color bleeding
|
||||
@ -1022,7 +1023,7 @@ void SkGpuDevice::drawBitmapTile(const SkBitmap& bitmap,
|
||||
fp = GrSimpleTextureEffect::Make(std::move(proxy), texMatrix, samplerState);
|
||||
}
|
||||
|
||||
fp = GrColorSpaceXformEffect::Make(std::move(fp), bitmap.colorSpace(),
|
||||
fp = GrColorSpaceXformEffect::Make(std::move(fp), bitmap.colorSpace(), config,
|
||||
fRenderTargetContext->colorSpaceInfo().colorSpace());
|
||||
GrPaint grPaint;
|
||||
if (!SkPaintToGrPaintWithTexture(this->context(), fRenderTargetContext->colorSpaceInfo(), paint,
|
||||
@ -1088,7 +1089,7 @@ void SkGpuDevice::drawSpecial(SkSpecialImage* special1, int left, int top, const
|
||||
tmpUnfiltered.setImageFilter(nullptr);
|
||||
|
||||
auto fp = GrSimpleTextureEffect::Make(std::move(proxy), SkMatrix::I());
|
||||
fp = GrColorSpaceXformEffect::Make(std::move(fp), result->getColorSpace(),
|
||||
fp = GrColorSpaceXformEffect::Make(std::move(fp), result->getColorSpace(), config,
|
||||
fRenderTargetContext->colorSpaceInfo().colorSpace());
|
||||
if (GrPixelConfigIsAlphaOnly(config)) {
|
||||
fp = GrFragmentProcessor::MakeInputPremulAndMulByOutput(std::move(fp));
|
||||
|
@ -114,7 +114,8 @@ static void draw_texture_affine(const SkPaint& paint, const SkMatrix& ctm, const
|
||||
SkAssertResult(srcRect.intersect(SkRect::MakeIWH(proxy->width(), proxy->height())));
|
||||
srcToDst.mapRect(&dstRect, srcRect);
|
||||
}
|
||||
auto csxf = GrColorSpaceXform::Make(colorSpace, rtc->colorSpaceInfo().colorSpace());
|
||||
auto csxf = GrColorSpaceXform::Make(colorSpace, proxy->config(),
|
||||
rtc->colorSpaceInfo().colorSpace());
|
||||
GrSamplerState::Filter filter;
|
||||
switch (paint.getFilterQuality()) {
|
||||
case kNone_SkFilterQuality:
|
||||
|
@ -229,11 +229,11 @@ std::unique_ptr<GrFragmentProcessor> GrNonlinearColorSpaceXformEffect::Make(
|
||||
uint32_t ops = 0;
|
||||
|
||||
// We rely on GrColorSpaceXform to build the gamut xform matrix for us (to get caching)
|
||||
auto gamutXform = GrColorSpaceXform::Make(src, dst);
|
||||
auto gamutXform = GrColorSpaceXform::MakeGamutXform(src, dst);
|
||||
SkMatrix44 srcToDstMtx(SkMatrix44::kUninitialized_Constructor);
|
||||
if (gamutXform) {
|
||||
ops |= kGamutXform_Op;
|
||||
srcToDstMtx = gamutXform->srcToDst();
|
||||
srcToDstMtx = gamutXform->gamutXform();
|
||||
}
|
||||
|
||||
SkColorSpaceTransferFn srcTransferFn;
|
||||
|
@ -18,28 +18,56 @@
|
||||
*/
|
||||
class GrGLSLColorSpaceXformHelper : public SkNoncopyable {
|
||||
public:
|
||||
GrGLSLColorSpaceXformHelper() : fValid(false) {}
|
||||
GrGLSLColorSpaceXformHelper() : fFlags(0) {}
|
||||
|
||||
void emitCode(GrGLSLUniformHandler* uniformHandler, const GrColorSpaceXform* colorSpaceXform,
|
||||
uint32_t visibility = kFragment_GrShaderFlag) {
|
||||
SkASSERT(uniformHandler);
|
||||
if (colorSpaceXform) {
|
||||
fGamutXformVar = uniformHandler->addUniform(visibility, kHalf4x4_GrSLType,
|
||||
"ColorXform");
|
||||
fValid = true;
|
||||
fFlags = colorSpaceXform->fFlags;
|
||||
if (this->applyGamutXform()) {
|
||||
fGamutXformVar = uniformHandler->addUniform(visibility,
|
||||
kHalf4x4_GrSLType,
|
||||
"ColorXform");
|
||||
}
|
||||
if (this->applyTransferFn()) {
|
||||
fTransferFnVar = uniformHandler->addUniformArray(visibility,
|
||||
kHalf_GrSLType,
|
||||
"TransferFn",
|
||||
kNumTransferFnCoeffs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setData(const GrGLSLProgramDataManager& pdman, const GrColorSpaceXform* colorSpaceXform) {
|
||||
pdman.setSkMatrix44(fGamutXformVar, colorSpaceXform->srcToDst());
|
||||
if (this->applyGamutXform()) {
|
||||
pdman.setSkMatrix44(fGamutXformVar, colorSpaceXform->gamutXform());
|
||||
}
|
||||
if (this->applyTransferFn()) {
|
||||
pdman.set1fv(fTransferFnVar, kNumTransferFnCoeffs, colorSpaceXform->transferFnCoeffs());
|
||||
}
|
||||
}
|
||||
|
||||
bool isValid() const { return fValid; }
|
||||
GrGLSLProgramDataManager::UniformHandle const gamutXformUniform() { return fGamutXformVar; }
|
||||
bool isValid() const { return (0 != fFlags); }
|
||||
bool applyInverseSRGB() const {
|
||||
return SkToBool(fFlags & GrColorSpaceXform::kApplyInverseSRGB_Flag);
|
||||
}
|
||||
bool applyTransferFn() const {
|
||||
return SkToBool(fFlags & GrColorSpaceXform::kApplyTransferFn_Flag);
|
||||
}
|
||||
bool applyGamutXform() const {
|
||||
return SkToBool(fFlags & GrColorSpaceXform::kApplyGamutXform_Flag);
|
||||
}
|
||||
|
||||
GrGLSLProgramDataManager::UniformHandle gamutXformUniform() const { return fGamutXformVar; }
|
||||
GrGLSLProgramDataManager::UniformHandle transferFnUniform() const { return fTransferFnVar; }
|
||||
|
||||
private:
|
||||
static const int kNumTransferFnCoeffs = 7;
|
||||
|
||||
GrGLSLProgramDataManager::UniformHandle fGamutXformVar;
|
||||
bool fValid;
|
||||
GrGLSLProgramDataManager::UniformHandle fTransferFnVar;
|
||||
uint32_t fFlags;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -118,29 +118,78 @@ void GrGLSLShaderBuilder::appendTextureLookupAndModulate(
|
||||
void GrGLSLShaderBuilder::appendColorGamutXform(SkString* out,
|
||||
const char* srcColor,
|
||||
GrGLSLColorSpaceXformHelper* colorXformHelper) {
|
||||
// Our color is (r, g, b, a), but we want to multiply (r, g, b, 1) by our matrix, then
|
||||
// re-insert the original alpha. The supplied srcColor is likely to be of the form
|
||||
// "texture(...)", and we don't want to evaluate that twice, so wrap everything in a function.
|
||||
static const GrShaderVar gColorGamutXformArgs[] = {
|
||||
GrShaderVar("color", kHalf4_GrSLType),
|
||||
GrShaderVar("xform", kHalf4x4_GrSLType),
|
||||
};
|
||||
SkString functionBody;
|
||||
// Gamut xform, clamp to destination gamut. We only support/have premultiplied textures, so we
|
||||
// always just clamp to alpha.
|
||||
functionBody.append("\tcolor.rgb = clamp((xform * half4(color.rgb, 1.0)).rgb, 0.0, color.a);\n");
|
||||
functionBody.append("\treturn color;");
|
||||
SkString colorGamutXformFuncName;
|
||||
this->emitFunction(kHalf4_GrSLType,
|
||||
"colorGamutXform",
|
||||
SK_ARRAY_COUNT(gColorGamutXformArgs),
|
||||
gColorGamutXformArgs,
|
||||
functionBody.c_str(),
|
||||
&colorGamutXformFuncName);
|
||||
|
||||
GrGLSLUniformHandler* uniformHandler = fProgramBuilder->uniformHandler();
|
||||
out->appendf("%s(%s, %s)", colorGamutXformFuncName.c_str(), srcColor,
|
||||
uniformHandler->getUniformCStr(colorXformHelper->gamutXformUniform()));
|
||||
|
||||
// We define up to three helper functions, to keep things clearer. One does inverse sRGB,
|
||||
// one does an arbitrary transfer function, and the last does gamut xform. Any combination of
|
||||
// these may be present, although some configurations are much more likely.
|
||||
|
||||
SkString inverseSrgbFuncName;
|
||||
if (colorXformHelper->applyInverseSRGB()) {
|
||||
static const GrShaderVar gInverseSRGBArgs[] = { GrShaderVar("x", kHalf_GrSLType) };
|
||||
SkString body;
|
||||
body.append("return (x <= 0.0031308) ? (x * 12.92) : (1.055 * pow(x, 0.4166667) - 0.055);");
|
||||
this->emitFunction(kHalf_GrSLType, "inverse_srgb", SK_ARRAY_COUNT(gInverseSRGBArgs),
|
||||
gInverseSRGBArgs, body.c_str(), &inverseSrgbFuncName);
|
||||
|
||||
}
|
||||
|
||||
SkString transferFnFuncName;
|
||||
if (colorXformHelper->applyTransferFn()) {
|
||||
static const GrShaderVar gTransferFnArgs[] = { GrShaderVar("x", kHalf_GrSLType) };
|
||||
const char* coeffs = uniformHandler->getUniformCStr(colorXformHelper->transferFnUniform());
|
||||
SkString body;
|
||||
// Temporaries to make evaluation line readable
|
||||
body.appendf("half G = %s[0];", coeffs);
|
||||
body.appendf("half A = %s[1];", coeffs);
|
||||
body.appendf("half B = %s[2];", coeffs);
|
||||
body.appendf("half C = %s[3];", coeffs);
|
||||
body.appendf("half D = %s[4];", coeffs);
|
||||
body.appendf("half E = %s[5];", coeffs);
|
||||
body.appendf("half F = %s[6];", coeffs);
|
||||
body.append("half s = sign(x);");
|
||||
body.append("x = abs(x);");
|
||||
body.appendf("return s * ((x < D) ? (C * x) + F : pow(A * x + B, G) + E);");
|
||||
this->emitFunction(kHalf_GrSLType, "transfer_fn", SK_ARRAY_COUNT(gTransferFnArgs),
|
||||
gTransferFnArgs, body.c_str(), &transferFnFuncName);
|
||||
}
|
||||
|
||||
SkString gamutXformFuncName;
|
||||
if (colorXformHelper->applyGamutXform()) {
|
||||
// Our color is (r, g, b, a), but we want to multiply (r, g, b, 1) by our matrix, then
|
||||
// re-insert the original alpha.
|
||||
static const GrShaderVar gGamutXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) };
|
||||
const char* xform = uniformHandler->getUniformCStr(colorXformHelper->gamutXformUniform());
|
||||
SkString body;
|
||||
body.appendf("color.rgb = clamp((%s * half4(color.rgb, 1.0)).rgb, 0.0, color.a);", xform);
|
||||
body.append("return color;");
|
||||
this->emitFunction(kHalf4_GrSLType, "gamut_xform", SK_ARRAY_COUNT(gGamutXformArgs),
|
||||
gGamutXformArgs, body.c_str(), &gamutXformFuncName);
|
||||
}
|
||||
|
||||
// Now define a wrapper function that applies all the intermediate steps
|
||||
{
|
||||
static const GrShaderVar gColorXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) };
|
||||
SkString body;
|
||||
if (colorXformHelper->applyInverseSRGB()) {
|
||||
body.appendf("color.r = %s(color.r);", inverseSrgbFuncName.c_str());
|
||||
body.appendf("color.g = %s(color.g);", inverseSrgbFuncName.c_str());
|
||||
body.appendf("color.b = %s(color.b);", inverseSrgbFuncName.c_str());
|
||||
}
|
||||
if (colorXformHelper->applyTransferFn()) {
|
||||
body.appendf("color.r = %s(color.r);", transferFnFuncName.c_str());
|
||||
body.appendf("color.g = %s(color.g);", transferFnFuncName.c_str());
|
||||
body.appendf("color.b = %s(color.b);", transferFnFuncName.c_str());
|
||||
}
|
||||
if (colorXformHelper->applyGamutXform()) {
|
||||
body.appendf("color = %s(color);", gamutXformFuncName.c_str());
|
||||
}
|
||||
body.append("return color;");
|
||||
SkString colorXformFuncName;
|
||||
this->emitFunction(kHalf4_GrSLType, "color_xform", SK_ARRAY_COUNT(gColorXformArgs),
|
||||
gColorXformArgs, body.c_str(), &colorXformFuncName);
|
||||
out->appendf("%s(%s)", colorXformFuncName.c_str(), srcColor);
|
||||
}
|
||||
}
|
||||
|
||||
void GrGLSLShaderBuilder::appendColorGamutXform(const char* srcColor,
|
||||
|
@ -211,8 +211,10 @@ SkShader::GradientType SkColor4Shader::asAGradient(GradientInfo* info) const {
|
||||
|
||||
std::unique_ptr<GrFragmentProcessor> SkColor4Shader::asFragmentProcessor(
|
||||
const AsFPArgs& args) const {
|
||||
sk_sp<GrColorSpaceXform> colorSpaceXform =
|
||||
GrColorSpaceXform::Make(fColorSpace.get(), args.fDstColorSpaceInfo->colorSpace());
|
||||
// Construct an xform assuming float inputs. The color space can have a transfer function on
|
||||
// it, which will be applied below.
|
||||
auto colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), kRGBA_float_GrPixelConfig,
|
||||
args.fDstColorSpaceInfo->colorSpace());
|
||||
GrColor4f color = GrColor4f::FromSkColor4f(fColor4);
|
||||
if (colorSpaceXform) {
|
||||
color = colorSpaceXform->clampedXform(color);
|
||||
|
@ -218,7 +218,8 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool isAlphaOnly = GrPixelConfigIsAlphaOnly(proxy->config());
|
||||
GrPixelConfig config = proxy->config();
|
||||
bool isAlphaOnly = GrPixelConfigIsAlphaOnly(config);
|
||||
|
||||
lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]);
|
||||
|
||||
@ -228,7 +229,7 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
|
||||
} else {
|
||||
inner = GrSimpleTextureEffect::Make(std::move(proxy), lmInverse, samplerState);
|
||||
}
|
||||
inner = GrColorSpaceXformEffect::Make(std::move(inner), texColorSpace.get(),
|
||||
inner = GrColorSpaceXformEffect::Make(std::move(inner), texColorSpace.get(), config,
|
||||
args.fDstColorSpaceInfo->colorSpace());
|
||||
if (isAlphaOnly) {
|
||||
return inner;
|
||||
|
@ -184,6 +184,7 @@ SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatri
|
||||
fColorSpace = SkColorSpace::MakeSRGBLinear();
|
||||
} else {
|
||||
// The color space refers to the float colors, so it must be linear gamma
|
||||
// TODO: GPU code no longer requires this (see GrGradientEffect). Remove this restriction?
|
||||
SkASSERT(desc.fColorSpace->gammaIsLinear());
|
||||
fColorSpace = desc.fColorSpace;
|
||||
}
|
||||
@ -1260,8 +1261,11 @@ GrGradientEffect::GrGradientEffect(ClassID classID, const CreateArgs& args, bool
|
||||
fPremulType = kAfterInterp_PremulType;
|
||||
}
|
||||
|
||||
// Convert input colors to GrColor4f, possibly premul, and apply color space xform
|
||||
// Convert input colors to GrColor4f, possibly premul, and apply color space xform.
|
||||
// The xform is constructed assuming floats as input, but the color space can have a
|
||||
// transfer function on it, which will be applied below.
|
||||
auto colorSpaceXform = GrColorSpaceXform::Make(shader.fColorSpace.get(),
|
||||
kRGBA_float_GrPixelConfig,
|
||||
args.fDstColorSpace);
|
||||
SkASSERT(shader.fOrigColors && shader.fOrigColors4f);
|
||||
fColors4f.setCount(shader.fColorCount);
|
||||
|
@ -277,8 +277,13 @@ protected:
|
||||
// xform is needed. With texture-based gradients, we leave the data in the source color
|
||||
// space (to avoid clamping if we can't use F16)... Add an extra FP to do the xform.
|
||||
if (kTexture_ColorType == gradientFP->getColorType()) {
|
||||
// Our texture is always either F16 or sRGB, so the data is "linear" in the shader.
|
||||
// Create our xform assuming float inputs, which will suppress any extra sRGB work.
|
||||
// We do support having a transfer function on the color space of the stops, so
|
||||
// this FP may include that transformation.
|
||||
fp = GrColorSpaceXformEffect::Make(std::move(gradientFP),
|
||||
args.fShader->fColorSpace.get(),
|
||||
kRGBA_float_GrPixelConfig,
|
||||
args.fDstColorSpace);
|
||||
} else {
|
||||
fp = std::move(gradientFP);
|
||||
|
Loading…
Reference in New Issue
Block a user