From 5a9a981edf54d203e06adab0909da03343a1c596 Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Tue, 1 Aug 2017 16:38:08 -0400 Subject: 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 Reviewed-by: Brian Salomon Reviewed-by: Mike Klein --- gm/gradients.cpp | 38 +++++++++ gm/tilemodes.cpp | 3 +- include/core/SkPicture.h | 3 +- include/effects/SkGradientShader.h | 65 +++++++++----- src/core/SkReadBuffer.h | 1 + src/shaders/gradients/SkGradientShader.cpp | 26 ++++-- src/shaders/gradients/SkSweepGradient.cpp | 131 ++++++++++++++++++++--------- src/shaders/gradients/SkSweepGradient.h | 6 +- 8 files changed, 206 insertions(+), 67 deletions(-) diff --git a/gm/gradients.cpp b/gm/gradients.cpp index 9abd6f0e9a..85ac6906da 100644 --- a/gm/gradients.cpp +++ b/gm/gradients.cpp @@ -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); + } +} diff --git a/gm/tilemodes.cpp b/gm/tilemodes.cpp index 390ce4adcd..f6bd362813 100644 --- a/gm/tilemodes.cpp +++ b/gm/tilemodes.cpp @@ -177,7 +177,8 @@ static sk_sp 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; } diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h index bd4dc9a7de..891c11dae5 100644 --- a/include/core/SkPicture.h +++ b/include/core/SkPicture.h @@ -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 Forwardport(const SkPictInfo&, diff --git a/include/effects/SkGradientShader.h b/include/effects/SkGradientShader.h index 393749be65..a4408ad9c3 100644 --- a/include/effects/SkGradientShader.h +++ b/include/effects/SkGradientShader.h @@ -158,44 +158,69 @@ public: /** Returns a shader that generates a sweep gradient given a center.

- @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 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 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 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.

- @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 MakeSweep(SkScalar cx, SkScalar cy, const SkColor4f colors[], sk_sp colorSpace, const SkScalar pos[], int count, + SkShader::TileMode mode, + SkScalar startAngle, SkScalar endAngle, uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeSweep(SkScalar cx, SkScalar cy, + const SkColor4f colors[], sk_sp 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 MakeSweep(SkScalar cx, SkScalar cy, const SkColor4f colors[], sk_sp 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() diff --git a/src/core/SkReadBuffer.h b/src/core/SkReadBuffer.h index dd0484c450..0653ab6865 100644 --- a/src/core/SkReadBuffer.h +++ b/src/core/SkReadBuffer.h @@ -74,6 +74,7 @@ public: kComposeShaderCanLerp_Version = 54, kNoModesInMergeImageFilter_Verison = 55, kTileModeInBlurImageFilter_Version = 56, + kTileInfoInSweepGradient_Version = 57, }; /** diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp index e2989e67f1..a203e88333 100644 --- a/src/shaders/gradients/SkGradientShader.cpp +++ b/src/shaders/gradients/SkGradientShader.cpp @@ -1228,11 +1228,14 @@ sk_sp 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 SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy, @@ -1240,26 +1243,39 @@ sk_sp SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy, sk_sp 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(cx, cy, desc); + + const SkScalar t0 = startAngle / 360, + t1 = endAngle / 360; + + return sk_make_sp(SkPoint::Make(cx, cy), t0, t1, desc); } SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader) diff --git a/src/shaders/gradients/SkSweepGradient.cpp b/src/shaders/gradients/SkSweepGradient.cpp index e70066a4d4..c4003bfd42 100644 --- a/src/shaders/gradients/SkSweepGradient.cpp +++ b/src/shaders/gradients/SkSweepGradient.cpp @@ -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 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 Make(const CreateArgs& args) { - auto processor = sk_sp(new GrSweepGradient(args)); + static sk_sp Make(const CreateArgs& args, SkScalar tBias, + SkScalar tScale) { + auto processor = sk_sp(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(); } - explicit GrSweepGradient(const GrSweepGradient& that) : INHERITED(that) { + explicit GrSweepGradient(const GrSweepGradient& that) + : INHERITED(that) + , fTBias(that.fTBias) + , fTScale(that.fTScale) { this->initClassID(); } @@ -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(); + 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)); } +protected: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) override { + INHERITED::onSetData(pdman, processor); + const GrSweepGradient& data = processor.cast(); + + if (fCachedTBias != data.fTBias || fCachedTScale != data.fTScale) { + fCachedTBias = data.fTBias; + fCachedTScale = data.fTScale; + pdman.set2f(fTBiasScaleUni, fCachedTBias, fCachedTScale); + } + } + private: - typedef GrGradientEffect::GLSLProcessor INHERITED; + UniformHandle fTBiasScaleUni; + + // Uploaded uniform values. + float fCachedTBias, + fCachedTScale; + typedef GrGradientEffect::GLSLProcessor INHERITED; }; ///////////////////////////////////////////////////////////////////// @@ -147,22 +193,28 @@ sk_sp GrSweepGradient::TestCreate(GrProcessorTestData* d) { void GrSweepGradient::GLSLSweepProcessor::emitCode(EmitArgs& args) { const GrSweepGradient& ge = args.fFp.cast(); - 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 SkSweepGradient::asFragmentProcessor(const AsFPArgs& sk_sp colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), args.fDstColorSpace); sk_sp 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 diff --git a/src/shaders/gradients/SkSweepGradient.h b/src/shaders/gradients/SkSweepGradient.h index 647b454ab5..92d3411b01 100644 --- a/src/shaders/gradients/SkSweepGradient.h +++ b/src/shaders/gradients/SkSweepGradient.h @@ -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; -- cgit v1.2.3