Tiling support for SkSweepGradient

Expand the sweep gradient definition to include a color stop angular range ([0, 360] by default).

Color stop positions in [0,1] are mapped to this range, and drawing outside is controlled by a
tile mode param.

This is closer to the CSS gradients spec and allows us to use fewer color stops in Blink conic
gradients.

Impl-wise, the remapping is effected after t calculation, and before tiling.

Change-Id: I5d71be01d134404d6eb9d7e2a904ec636b39f855
Reviewed-on: https://skia-review.googlesource.com/27704
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Mike Klein <mtklein@chromium.org>
This commit is contained in:
Florin Malita 2017-08-01 16:38:08 -04:00 committed by Skia Commit-Bot
parent ce06e261e6
commit 5a9a981edf
8 changed files with 207 additions and 68 deletions

View File

@ -1033,3 +1033,41 @@ DEF_SIMPLE_GM(fancy_gradients, canvas, 800, 300) {
SkBlendMode::kExclusion);
});
}
DEF_SIMPLE_GM(sweep_tiling, canvas, 512, 512) {
static constexpr SkScalar size = 160;
static constexpr SkColor colors[] = { SK_ColorBLUE, SK_ColorYELLOW, SK_ColorGREEN };
static constexpr SkScalar pos[] = { 0, .25f, .50f };
static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(pos), "size mismatch");
static constexpr SkShader::TileMode modes[] = { SkShader::kClamp_TileMode,
SkShader::kRepeat_TileMode,
SkShader::kMirror_TileMode };
static const struct {
SkScalar start, end;
} angles[] = {
{ -330, -270 },
{ 30, 90 },
{ 390, 450 },
};
SkPaint p;
const SkRect r = SkRect::MakeWH(size, size);
for (auto mode : modes) {
{
SkAutoCanvasRestore acr(canvas, true);
for (auto angle : angles) {
p.setShader(SkGradientShader::MakeSweep(size / 2, size / 2, colors, pos,
SK_ARRAY_COUNT(colors), mode,
angle.start, angle.end, 0, nullptr));
canvas->drawRect(r, p);
canvas->translate(size * 1.1f, 0);
}
}
canvas->translate(0, size * 1.1f);
}
}

View File

@ -177,7 +177,8 @@ static sk_sp<SkShader> make_grad(SkShader::TileMode tx, SkShader::TileMode ty) {
case 1:
return SkGradientShader::MakeRadial(center, rad, colors, nullptr, SK_ARRAY_COUNT(colors), tx);
case 2:
return SkGradientShader::MakeSweep(center.fX, center.fY, colors, nullptr, SK_ARRAY_COUNT(colors));
return SkGradientShader::MakeSweep(center.fX, center.fY, colors, nullptr,
SK_ARRAY_COUNT(colors), tx, 135, 225, 0, nullptr);
}
return nullptr;
}

View File

@ -199,10 +199,11 @@ private:
// V54: ComposeShader can use a Mode or a Lerp
// V55: Drop blendmode[] from MergeImageFilter
// V56: Add TileMode in SkBlurImageFilter.
// V57: Sweep tiling info.
// Only SKPs within the min/current picture version range (inclusive) can be read.
static const uint32_t MIN_PICTURE_VERSION = 51; // Produced by Chrome ~M56.
static const uint32_t CURRENT_PICTURE_VERSION = 56;
static const uint32_t CURRENT_PICTURE_VERSION = 57;
static bool IsValidPictInfo(const SkPictInfo& info);
static sk_sp<SkPicture> Forwardport(const SkPictInfo&,

View File

@ -158,44 +158,69 @@ public:
/** Returns a shader that generates a sweep gradient given a center.
<p />
@param cx The X coordinate of the center of the sweep
@param cx The Y coordinate of the center of the sweep
@param colors The array[count] of colors, to be distributed around the center.
@param pos May be NULL. The array[count] of SkScalars, or NULL, of the relative position of
each corresponding color in the colors array. If this is NULL,
the the colors are distributed evenly between the center and edge of the circle.
If this is not null, the values must begin with 0, end with 1.0, and
intermediate values must be strictly increasing.
@param count Must be >= 2. The number of colors (and pos if not NULL) entries
@param cx The X coordinate of the center of the sweep
@param cx The Y coordinate of the center of the sweep
@param colors The array[count] of colors, to be distributed around the center, within
the gradient angle range.
@param pos May be NULL. The array[count] of SkScalars, or NULL, of the relative
position of each corresponding color in the colors array. If this is
NULL, then the colors are distributed evenly within the angular range.
If this is not null, the values must begin with 0, end with 1.0, and
intermediate values must be strictly increasing.
@param count Must be >= 2. The number of colors (and pos if not NULL) entries
@param mode Tiling mode: controls drawing outside of the gradient angular range.
@param startAngle Start of the angular range, corresponding to pos == 0.
@param endAngle End of the angular range, corresponding to pos == 1.
*/
static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
const SkColor colors[], const SkScalar pos[], int count,
SkShader::TileMode mode,
SkScalar startAngle, SkScalar endAngle,
uint32_t flags, const SkMatrix* localMatrix);
static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
const SkColor colors[], const SkScalar pos[], int count,
uint32_t flags, const SkMatrix* localMatrix) {
return MakeSweep(cx, cy, colors, pos, count, SkShader::kClamp_TileMode, 0, 360, flags,
localMatrix);
}
static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
const SkColor colors[], const SkScalar pos[], int count) {
return MakeSweep(cx, cy, colors, pos, count, 0, NULL);
return MakeSweep(cx, cy, colors, pos, count, 0, nullptr);
}
/** Returns a shader that generates a sweep gradient given a center.
<p />
@param cx The X coordinate of the center of the sweep
@param cx The Y coordinate of the center of the sweep
@param colors The array[count] of colors, to be distributed around the center.
@param pos May be NULL. The array[count] of SkScalars, or NULL, of the relative position of
each corresponding color in the colors array. If this is NULL,
the the colors are distributed evenly between the center and edge of the circle.
If this is not null, the values must begin with 0, end with 1.0, and
intermediate values must be strictly increasing.
@param count Must be >= 2. The number of colors (and pos if not NULL) entries
@param cx The X coordinate of the center of the sweep
@param cx The Y coordinate of the center of the sweep
@param colors The array[count] of colors, to be distributed around the center, within
the gradient angle range.
@param pos May be NULL. The array[count] of SkScalars, or NULL, of the relative
position of each corresponding color in the colors array. If this is
NULL, then the colors are distributed evenly within the angular range.
If this is not null, the values must begin with 0, end with 1.0, and
intermediate values must be strictly increasing.
@param count Must be >= 2. The number of colors (and pos if not NULL) entries
@param mode Tiling mode: controls drawing outside of the gradient angular range.
@param startAngle Start of the angular range, corresponding to pos == 0.
@param endAngle End of the angular range, corresponding to pos == 1.
*/
static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
const SkScalar pos[], int count,
SkShader::TileMode mode,
SkScalar startAngle, SkScalar endAngle,
uint32_t flags, const SkMatrix* localMatrix);
static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
const SkScalar pos[], int count,
uint32_t flags, const SkMatrix* localMatrix) {
return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count,
SkShader::kClamp_TileMode, 0, 360, flags, localMatrix);
}
static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
const SkScalar pos[], int count) {
return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count, 0, NULL);
return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count, 0, nullptr);
}
SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()

View File

@ -74,6 +74,7 @@ public:
kComposeShaderCanLerp_Version = 54,
kNoModesInMergeImageFilter_Verison = 55,
kTileModeInBlurImageFilter_Version = 56,
kTileInfoInSweepGradient_Version = 57,
};
/**

View File

@ -1228,11 +1228,14 @@ sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
const SkColor colors[],
const SkScalar pos[],
int colorCount,
SkShader::TileMode mode,
SkScalar startAngle,
SkScalar endAngle,
uint32_t flags,
const SkMatrix* localMatrix) {
ColorConverter converter(colors, colorCount);
return MakeSweep(cx, cy, converter.fColors4f.begin(), nullptr, pos, colorCount, flags,
localMatrix);
return MakeSweep(cx, cy, converter.fColors4f.begin(), nullptr, pos, colorCount,
mode, startAngle, endAngle, flags, localMatrix);
}
sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
@ -1240,26 +1243,39 @@ sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
sk_sp<SkColorSpace> colorSpace,
const SkScalar pos[],
int colorCount,
SkShader::TileMode mode,
SkScalar startAngle,
SkScalar endAngle,
uint32_t flags,
const SkMatrix* localMatrix) {
if (!valid_grad(colors, pos, colorCount, SkShader::kClamp_TileMode)) {
if (!valid_grad(colors, pos, colorCount, mode)) {
return nullptr;
}
if (1 == colorCount) {
return SkShader::MakeColorShader(colors[0], std::move(colorSpace));
}
if (startAngle >= endAngle) {
return nullptr;
}
if (localMatrix && !localMatrix->invert(nullptr)) {
return nullptr;
}
auto mode = SkShader::kClamp_TileMode;
if (startAngle <= 0 && endAngle >= 360) {
// If the t-range includes [0,1], then we can always use clamping (presumably faster).
mode = SkShader::kClamp_TileMode;
}
ColorStopOptimizer opt(colors, pos, colorCount, mode);
SkGradientShaderBase::Descriptor desc;
desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
localMatrix);
return sk_make_sp<SkSweepGradient>(cx, cy, desc);
const SkScalar t0 = startAngle / 360,
t1 = endAngle / 360;
return sk_make_sp<SkSweepGradient>(SkPoint::Make(cx, cy), t0, t1, desc);
}
SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)

View File

@ -11,18 +11,14 @@
#include "SkPM4fPriv.h"
#include "SkRasterPipeline.h"
static SkMatrix translate(SkScalar dx, SkScalar dy) {
SkMatrix matrix;
matrix.setTranslate(dx, dy);
return matrix;
}
SkSweepGradient::SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor& desc)
: SkGradientShaderBase(desc, translate(-cx, -cy))
, fCenter(SkPoint::Make(cx, cy))
SkSweepGradient::SkSweepGradient(const SkPoint& center, SkScalar t0, SkScalar t1,
const Descriptor& desc)
: SkGradientShaderBase(desc, SkMatrix::MakeTrans(-center.x(), -center.y()))
, fCenter(center)
, fTBias(-t0)
, fTScale(1 / (t1 - t0))
{
// overwrite the tilemode to a canonical value (since sweep ignores it)
fTileMode = SkShader::kClamp_TileMode;
SkASSERT(t0 < t1);
}
SkShader::GradientType SkSweepGradient::asAGradient(GradientInfo* info) const {
@ -39,14 +35,27 @@ sk_sp<SkFlattenable> SkSweepGradient::CreateProc(SkReadBuffer& buffer) {
return nullptr;
}
const SkPoint center = buffer.readPoint();
SkScalar startAngle = 0,
endAngle = 360;
if (!buffer.isVersionLT(SkReadBuffer::kTileInfoInSweepGradient_Version)) {
const auto tBias = buffer.readScalar(),
tScale = buffer.readScalar();
startAngle = -tBias * 360;
endAngle = (1 / tScale - tBias) * 360;
}
return SkGradientShader::MakeSweep(center.x(), center.y(), desc.fColors,
std::move(desc.fColorSpace), desc.fPos, desc.fCount,
desc.fTileMode, startAngle, endAngle,
desc.fGradFlags, desc.fLocalMatrix);
}
void SkSweepGradient::flatten(SkWriteBuffer& buffer) const {
this->INHERITED::flatten(buffer);
buffer.writePoint(fCenter);
buffer.writeScalar(fTBias);
buffer.writeScalar(fTScale);
}
/////////////////////////////////////////////////////////////////////
@ -62,8 +71,9 @@ class GrSweepGradient : public GrGradientEffect {
public:
class GLSLSweepProcessor;
static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args) {
auto processor = sk_sp<GrSweepGradient>(new GrSweepGradient(args));
static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args, SkScalar tBias,
SkScalar tScale) {
auto processor = sk_sp<GrSweepGradient>(new GrSweepGradient(args, tBias, tScale));
return processor->isValid() ? std::move(processor) : nullptr;
}
@ -74,12 +84,17 @@ public:
}
private:
explicit GrSweepGradient(const CreateArgs& args)
: INHERITED(args, args.fShader->colorsAreOpaque()) {
explicit GrSweepGradient(const CreateArgs& args, SkScalar tBias, SkScalar tScale)
: INHERITED(args, args.fShader->colorsAreOpaque())
, fTBias(tBias)
, fTScale(tScale){
this->initClassID<GrSweepGradient>();
}
explicit GrSweepGradient(const GrSweepGradient& that) : INHERITED(that) {
explicit GrSweepGradient(const GrSweepGradient& that)
: INHERITED(that)
, fTBias(that.fTBias)
, fTScale(that.fTScale) {
this->initClassID<GrSweepGradient>();
}
@ -88,8 +103,18 @@ private:
virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps,
GrProcessorKeyBuilder* b) const override;
bool onIsEqual(const GrFragmentProcessor& base) const override {
const GrSweepGradient& fp = base.cast<GrSweepGradient>();
return INHERITED::onIsEqual(base)
&& fTBias == fp.fTBias
&& fTScale == fp.fTScale;
}
GR_DECLARE_FRAGMENT_PROCESSOR_TEST
SkScalar fTBias;
SkScalar fTScale;
typedef GrGradientEffect INHERITED;
};
@ -97,17 +122,38 @@ private:
class GrSweepGradient::GLSLSweepProcessor : public GrGradientEffect::GLSLProcessor {
public:
GLSLSweepProcessor(const GrProcessor&) {}
GLSLSweepProcessor(const GrProcessor&)
: fCachedTBias(SK_FloatNaN)
, fCachedTScale(SK_FloatNaN) {}
virtual void emitCode(EmitArgs&) override;
void emitCode(EmitArgs&) override;
static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) {
static void GenKey(const GrProcessor& processor, const GrShaderCaps&,
GrProcessorKeyBuilder* b) {
b->add32(GenBaseGradientKey(processor));
}
private:
typedef GrGradientEffect::GLSLProcessor INHERITED;
protected:
void onSetData(const GrGLSLProgramDataManager& pdman,
const GrFragmentProcessor& processor) override {
INHERITED::onSetData(pdman, processor);
const GrSweepGradient& data = processor.cast<GrSweepGradient>();
if (fCachedTBias != data.fTBias || fCachedTScale != data.fTScale) {
fCachedTBias = data.fTBias;
fCachedTScale = data.fTScale;
pdman.set2f(fTBiasScaleUni, fCachedTBias, fCachedTScale);
}
}
private:
UniformHandle fTBiasScaleUni;
// Uploaded uniform values.
float fCachedTBias,
fCachedTScale;
typedef GrGradientEffect::GLSLProcessor INHERITED;
};
/////////////////////////////////////////////////////////////////////
@ -147,22 +193,28 @@ sk_sp<GrFragmentProcessor> GrSweepGradient::TestCreate(GrProcessorTestData* d) {
void GrSweepGradient::GLSLSweepProcessor::emitCode(EmitArgs& args) {
const GrSweepGradient& ge = args.fFp.cast<GrSweepGradient>();
this->emitUniforms(args.fUniformHandler, ge);
SkString coords2D = args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
SkString t;
GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
this->emitUniforms(uniformHandler, ge);
fTBiasScaleUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kVec2f_GrSLType,
kDefault_GrSLPrecision, "SweepFSParams");
const char* tBiasScaleV = uniformHandler->getUniformCStr(fTBiasScaleUni);
const SkString coords2D = args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
// On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
// atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in
// (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device
// handle the undefined behavior of the second paramenter being 0 instead of doing the
// divide ourselves and using atan instead.
const SkString atan = args.fShaderCaps->atan2ImplementedAsAtanYOverX()
? SkStringPrintf("2.0 * atan(- %s.y, length(%s) - %s.x)",
coords2D.c_str(), coords2D.c_str(), coords2D.c_str())
: SkStringPrintf("atan(- %s.y, - %s.x)", coords2D.c_str(), coords2D.c_str());
// 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
if (args.fShaderCaps->atan2ImplementedAsAtanYOverX()) {
// On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
// atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in
// (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device
// handle the undefined behavior of the second paramenter being 0 instead of doing the
// divide ourselves and using atan instead.
t.printf("(2.0 * atan(- %s.y, length(%s) - %s.x) * 0.1591549430918 + 0.5)",
coords2D.c_str(), coords2D.c_str(), coords2D.c_str());
} else {
t.printf("(atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5)",
coords2D.c_str(), coords2D.c_str());
}
const SkString t = SkStringPrintf("((%s * 0.1591549430918 + 0.5 + %s[0]) * %s[1])",
atan.c_str(), tBiasScaleV, tBiasScaleV);
this->emitColor(args.fFragBuilder,
args.fUniformHandler,
args.fShaderCaps,
@ -192,8 +244,9 @@ sk_sp<GrFragmentProcessor> SkSweepGradient::asFragmentProcessor(const AsFPArgs&
sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
args.fDstColorSpace);
sk_sp<GrFragmentProcessor> inner(GrSweepGradient::Make(
GrGradientEffect::CreateArgs(args.fContext, this, &matrix, SkShader::kClamp_TileMode,
std::move(colorSpaceXform), SkToBool(args.fDstColorSpace))));
GrGradientEffect::CreateArgs(args.fContext, this, &matrix, fTileMode,
std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)),
fTBias, fTScale));
if (!inner) {
return nullptr;
}
@ -224,9 +277,11 @@ void SkSweepGradient::toString(SkString* str) const {
str->append(")");
}
void SkSweepGradient::appendGradientStages(SkArenaAlloc*, SkRasterPipeline* p,
void SkSweepGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
SkRasterPipeline*) const {
p->append(SkRasterPipeline::xy_to_unit_angle);
p->append_matrix(alloc, SkMatrix::Concat(SkMatrix::MakeScale(fTScale, 1),
SkMatrix::MakeTrans(fTBias , 0)));
}
#endif

View File

@ -12,7 +12,7 @@
class SkSweepGradient final : public SkGradientShaderBase {
public:
SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor&);
SkSweepGradient(const SkPoint& center, SkScalar t0, SkScalar t1, const Descriptor&);
GradientType asAGradient(GradientInfo* info) const override;
@ -33,7 +33,9 @@ protected:
bool onIsRasterPipelineOnly() const override { return true; }
private:
const SkPoint fCenter;
const SkPoint fCenter;
const SkScalar fTBias,
fTScale;
friend class SkGradientShader;
typedef SkGradientShaderBase INHERITED;