Add color space support to 4f gradients

Similar to the raster pipeline stage, transform the stops into the dest
color space before interpolation.

Change-Id: I626b6ef18606fd2308d7da166ce70d05f3951e21
Reviewed-on: https://skia-review.googlesource.com/18767
Reviewed-by: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2017-06-05 23:33:45 -04:00 committed by Skia Commit-Bot
parent 6b3542aeb0
commit 0e36b3f930
5 changed files with 56 additions and 45 deletions

View File

@ -11,8 +11,7 @@
namespace {
Sk4f pack_color(SkColor c, bool premul, const Sk4f& component_scale) {
const SkColor4f c4f = SkColor4f::FromColor(c);
Sk4f pack_color(const SkColor4f& c4f, bool premul, const Sk4f& component_scale) {
const Sk4f pm4f = premul
? c4f.premul().to4f()
: Sk4f{c4f.fR, c4f.fG, c4f.fB, c4f.fA};
@ -22,40 +21,40 @@ Sk4f pack_color(SkColor c, bool premul, const Sk4f& component_scale) {
class IntervalIterator {
public:
IntervalIterator(const SkColor* colors, const SkScalar* pos, int count, bool reverse)
: fColors(colors)
, fPos(pos)
, fCount(count)
IntervalIterator(const SkGradientShaderBase& shader, SkColorSpace* dstCS, bool reverse)
: fShader(shader)
, fDstCS(dstCS)
, fFirstPos(reverse ? SK_Scalar1 : 0)
, fBegin(reverse ? count - 1 : 0)
, fBegin(reverse ? shader.fColorCount - 1 : 0)
, fAdvance(reverse ? -1 : 1) {
SkASSERT(colors);
SkASSERT(count > 0);
SkASSERT(shader.fColorCount > 0);
}
void iterate(std::function<void(SkColor, SkColor, SkScalar, SkScalar)> func) const {
if (!fPos) {
void iterate(std::function<void(const SkColor4f&, const SkColor4f&,
SkScalar, SkScalar)> func) const {
if (!fShader.fOrigPos) {
this->iterateImplicitPos(func);
return;
}
const int end = fBegin + fAdvance * (fCount - 1);
const int end = fBegin + fAdvance * (fShader.fColorCount - 1);
const SkScalar lastPos = 1 - fFirstPos;
int prev = fBegin;
SkScalar prevPos = fFirstPos;
do {
const int curr = prev + fAdvance;
SkASSERT(curr >= 0 && curr < fCount);
SkASSERT(curr >= 0 && curr < fShader.fColorCount);
// TODO: this sanitization should be done in SkGradientShaderBase
const SkScalar currPos = (fAdvance > 0)
? SkTPin(fPos[curr], prevPos, lastPos)
: SkTPin(fPos[curr], lastPos, prevPos);
? SkTPin(fShader.fOrigPos[curr], prevPos, lastPos)
: SkTPin(fShader.fOrigPos[curr], lastPos, prevPos);
if (currPos != prevPos) {
SkASSERT((currPos - prevPos > 0) == (fAdvance > 0));
func(fColors[prev], fColors[curr], prevPos, currPos);
func(fShader.getXformedColor(prev, fDstCS), fShader.getXformedColor(curr, fDstCS),
prevPos, currPos);
}
prev = curr;
@ -64,44 +63,48 @@ public:
}
private:
void iterateImplicitPos(std::function<void(SkColor, SkColor, SkScalar, SkScalar)> func) const {
void iterateImplicitPos(std::function<void(const SkColor4f&, const SkColor4f&,
SkScalar, SkScalar)> func) const {
// When clients don't provide explicit color stop positions (fPos == nullptr),
// the color stops are distributed evenly across the unit interval
// (implicit positioning).
const SkScalar dt = fAdvance * SK_Scalar1 / (fCount - 1);
const int end = fBegin + fAdvance * (fCount - 2);
const SkScalar dt = fAdvance * SK_Scalar1 / (fShader.fColorCount - 1);
const int end = fBegin + fAdvance * (fShader.fColorCount - 2);
int prev = fBegin;
SkScalar prevPos = fFirstPos;
while (prev != end) {
const int curr = prev + fAdvance;
SkASSERT(curr >= 0 && curr < fCount);
SkASSERT(curr >= 0 && curr < fShader.fColorCount);
const SkScalar currPos = prevPos + dt;
func(fColors[prev], fColors[curr], prevPos, currPos);
func(fShader.getXformedColor(prev, fDstCS),
fShader.getXformedColor(curr, fDstCS),
prevPos, currPos);
prev = curr;
prevPos = currPos;
}
// emit the last interval with a pinned end position, to avoid precision issues
func(fColors[prev], fColors[prev + fAdvance], prevPos, 1 - fFirstPos);
func(fShader.getXformedColor(prev, fDstCS),
fShader.getXformedColor(prev + fAdvance, fDstCS),
prevPos, 1 - fFirstPos);
}
const SkColor* fColors;
const SkScalar* fPos;
const int fCount;
const SkScalar fFirstPos;
const int fBegin;
const int fAdvance;
const SkGradientShaderBase& fShader;
SkColorSpace* fDstCS;
const SkScalar fFirstPos;
const int fBegin;
const int fAdvance;
};
void addMirrorIntervals(const SkColor colors[],
const SkScalar pos[], int count,
void addMirrorIntervals(const SkGradientShaderBase& shader,
SkColorSpace* dstCS,
const Sk4f& componentScale,
bool premulColors, bool reverse,
Sk4fGradientIntervalBuffer::BufferType* buffer) {
const IntervalIterator iter(colors, pos, count, reverse);
iter.iterate([&] (SkColor c0, SkColor c1, SkScalar t0, SkScalar t1) {
const IntervalIterator iter(shader, dstCS, reverse);
iter.iterate([&] (const SkColor4f& c0, const SkColor4f& c1, SkScalar t0, SkScalar t1) {
SkASSERT(buffer->empty() || buffer->back().fT1 == 2 - t0);
const auto mirror_t0 = 2 - t0;
@ -137,7 +140,7 @@ Sk4fGradientInterval::Sk4fGradientInterval(const Sk4f& c0, SkScalar t0,
dc.store(&fCg.fVec);
}
void Sk4fGradientIntervalBuffer::init(const SkColor colors[], const SkScalar pos[], int count,
void Sk4fGradientIntervalBuffer::init(const SkGradientShaderBase& shader, SkColorSpace* dstCS,
SkShader::TileMode tileMode, bool premulColors,
SkScalar alpha, bool reverse) {
// The main job here is to build a specialized interval list: a different
@ -181,8 +184,9 @@ void Sk4fGradientIntervalBuffer::init(const SkColor colors[], const SkScalar pos
//
// TODO: investigate collapsing intervals << 1px.
const auto count = shader.fColorCount;
SkASSERT(count > 0);
SkASSERT(colors);
fIntervals.reset();
@ -196,18 +200,18 @@ void Sk4fGradientIntervalBuffer::init(const SkColor colors[], const SkScalar pos
if (tileMode == SkShader::kClamp_TileMode) {
// synthetic edge interval: -/+inf .. P0
const Sk4f clamp_color = pack_color(colors[first_index],
const Sk4f clamp_color = pack_color(shader.getXformedColor(first_index, dstCS),
premulColors, componentScale);
const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity;
fIntervals.emplace_back(clamp_color, clamp_pos,
clamp_color, first_pos);
} else if (tileMode == SkShader::kMirror_TileMode && reverse) {
// synthetic mirror intervals injected before main intervals: (2 .. 1]
addMirrorIntervals(colors, pos, count, componentScale, premulColors, false, &fIntervals);
addMirrorIntervals(shader, dstCS, componentScale, premulColors, false, &fIntervals);
}
const IntervalIterator iter(colors, pos, count, reverse);
iter.iterate([&] (SkColor c0, SkColor c1, SkScalar t0, SkScalar t1) {
const IntervalIterator iter(shader, dstCS, reverse);
iter.iterate([&] (const SkColor4f& c0, const SkColor4f& c1, SkScalar t0, SkScalar t1) {
SkASSERT(fIntervals.empty() || fIntervals.back().fT1 == t0);
fIntervals.emplace_back(pack_color(c0, premulColors, componentScale), t0,
@ -216,13 +220,14 @@ void Sk4fGradientIntervalBuffer::init(const SkColor colors[], const SkScalar pos
if (tileMode == SkShader::kClamp_TileMode) {
// synthetic edge interval: Pn .. +/-inf
const Sk4f clamp_color = pack_color(colors[last_index], premulColors, componentScale);
const Sk4f clamp_color = pack_color(shader.getXformedColor(last_index, dstCS),
premulColors, componentScale);
const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity;
fIntervals.emplace_back(clamp_color, last_pos,
clamp_color, clamp_pos);
} else if (tileMode == SkShader::kMirror_TileMode && !reverse) {
// synthetic mirror intervals injected after main intervals: [1 .. 2)
addMirrorIntervals(colors, pos, count, componentScale, premulColors, true, &fIntervals);
addMirrorIntervals(shader, dstCS, componentScale, premulColors, true, &fIntervals);
}
}

View File

@ -37,8 +37,8 @@ struct Sk4fGradientInterval {
class Sk4fGradientIntervalBuffer {
public:
void init(const SkColor colors[], const SkScalar pos[], int count,
SkShader::TileMode tileMode, bool premulColors, SkScalar alpha, bool reverse);
void init(const SkGradientShaderBase&, SkColorSpace* dstCS, SkShader::TileMode tileMode,
bool premulColors, SkScalar alpha, bool reverse);
const Sk4fGradientInterval* find(SkScalar t) const;
const Sk4fGradientInterval* findNext(SkScalar t, const Sk4fGradientInterval* prev,

View File

@ -92,7 +92,7 @@ LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader,
// Our fast path expects interval points to be monotonically increasing in x.
const bool reverseIntervals = this->isFast() && std::signbit(fDstToPos.getScaleX());
fIntervals.init(shader.fOrigColors, shader.fOrigPos, shader.fColorCount, shader.fTileMode,
fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode,
fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals);
SkASSERT(fIntervals->count() > 0);

View File

@ -449,8 +449,7 @@ bool SkGradientShaderBase::onAppendStages(SkRasterPipeline* p,
const bool premulGrad = fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag;
auto prepareColor = [premulGrad, dstCS, this](int i) {
SkColor4f c = dstCS ? to_colorspace(fOrigColors4f[i], fColorSpace.get(), dstCS)
: SkColor4f_from_SkColor(fOrigColors[i], nullptr);
SkColor4f c = this->getXformedColor(i, dstCS);
return premulGrad ? c.premul()
: SkPM4f::From4f(Sk4f::Load(&c));
};
@ -870,6 +869,11 @@ sk_sp<SkGradientShaderBase::GradientShaderCache> SkGradientShaderBase::refCache(
return fCache;
}
SkColor4f SkGradientShaderBase::getXformedColor(size_t i, SkColorSpace* dstCS) const {
return dstCS ? to_colorspace(fOrigColors4f[i], fColorSpace.get(), dstCS)
: SkColor4f_from_SkColor(fOrigColors[i], nullptr);
}
SK_DECLARE_STATIC_MUTEX(gGradientCacheMutex);
/*
* Because our caller might rebuild the same (logically the same) gradient

View File

@ -202,6 +202,8 @@ public:
uint32_t getGradFlags() const { return fGradFlags; }
SkColor4f getXformedColor(size_t index, SkColorSpace*) const;
protected:
struct Rec {
SkFixed fPos; // 0...1