diff options
author | Robert Phillips <robertphillips@google.com> | 2016-10-20 09:40:55 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2016-10-20 14:06:52 +0000 |
commit | a29a956d0e27819b97592404ae1ec6cc8e7b1045 (patch) | |
tree | 1e5d884a5d0f7f881a2e8fd5579672edd80fc2a9 /src/effects | |
parent | e1d6ff172e219d3be61949b0e44091f4c85de2ad (diff) |
Add SkRRectsGaussianEdgeMaskFilter
SkRRectsGaussianEdgeShader will be removed once the usage of the
MaskFilter flavor has been propagated to Android
I will complete the raster implementation in a follow up CL.
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=3632
Change-Id: I42470b17308582b040a5db1a7283c3d717405345
Reviewed-on: https://skia-review.googlesource.com/3632
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Diffstat (limited to 'src/effects')
-rw-r--r-- | src/effects/SkRRectsGaussianEdgeMaskFilter.cpp | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp b/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp new file mode 100644 index 0000000000..dafab6a8f0 --- /dev/null +++ b/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp @@ -0,0 +1,581 @@ +/* + * 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 "SkRRectsGaussianEdgeMaskFilter.h" +#include "SkReadBuffer.h" +#include "SkRRect.h" +#include "SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "GrFragmentProcessor.h" +#endif + + /** \class SkRRectsGaussianEdgeMaskFilterImpl + * This mask filter applies a gaussian edge to the intersection of two round rects. + * The round rects must have the same radii at each corner and the x&y radii + * must also be equal. + */ +class SkRRectsGaussianEdgeMaskFilterImpl : public SkMaskFilter { +public: + SkRRectsGaussianEdgeMaskFilterImpl(const SkRRect& first, const SkRRect& second, + SkScalar radius) + : fFirst(first) + , fSecond(second) + , fRadius(radius) { + } + + SkMask::Format getFormat() const override { return SkMask::kA8_Format; } + bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, + SkIPoint* margin) const override; + +#if SK_SUPPORT_GPU + bool asFragmentProcessor(GrFragmentProcessor**, GrTexture*, const SkMatrix& ctm) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkRRectsGaussianEdgeMaskFilterImpl) + +protected: + void flatten(SkWriteBuffer&) const override; + +private: + SkRRect fFirst; + SkRRect fSecond; + SkScalar fRadius; + + friend class SkRRectsGaussianEdgeMaskFilter; // for serialization registration system + + typedef SkMaskFilter INHERITED; +}; + +// x & y are in device space +static SkScalar compute_rrect_normalized_dist(const SkRRect& rr, const SkPoint& p, SkScalar rad) { + SkASSERT(rr.getType() == SkRRect::kOval_Type || rr.getType() == SkRRect::kRect_Type || + rr.getType() == SkRRect::kSimple_Type); + SkASSERT(rad > 0.0f); + + SkVector delta = { SkTAbs(p.fX - rr.rect().centerX()), SkTAbs(p.fY - rr.rect().centerY()) }; + + SkScalar halfW = 0.5f * rr.rect().width(); + SkScalar halfH = 0.5f * rr.rect().height(); + SkScalar invRad = 1.0f/rad; + + const SkVector& radii = rr.getSimpleRadii(); + SkASSERT(SkScalarNearlyEqual(radii.fX, radii.fY)); + + switch (rr.getType()) { + case SkRRect::kOval_Type: { + float scaledDist = delta.length() * invRad; + return SkTPin(halfW * invRad - scaledDist, 0.0f, 1.0f); + } + case SkRRect::kRect_Type: { + SkScalar xDist = (halfW - delta.fX) * invRad; + SkScalar yDist = (halfH - delta.fY) * invRad; + + SkVector v = { 1.0f - SkTPin(xDist, 0.0f, 1.0f), 1.0f - SkTPin(yDist, 0.0f, 1.0f) }; + return SkTPin(1.0f - v.length(), 0.0f, 1.0f); + } + case SkRRect::kSimple_Type: { + + //---------------- + // ice-cream-cone fractional distance computation + + // When the blurRadius is larger than the corner radius we want to use it to + // compute the pointy end of the ice cream cone. If it smaller we just want to use + // the center of the corner's circle. When using the blurRadius the inset amount + // can't exceed the halfwidths of the RRect. + SkScalar insetDist = SkTMin(SkTMax(rad, radii.fX), SkTMin(halfW, halfH)); + + // "maxValue" is a correction term for if the blurRadius is larger than the + // size of the RRect. In that case we don't want to go all the way to black. + SkScalar maxValue = insetDist * invRad; + + SkVector coneBottom = { halfW - insetDist, halfH - insetDist }; + SkVector ptInConeSpace = delta - coneBottom; + + SkVector cornerTop = { halfW - radii.fX - coneBottom.fX, halfH - coneBottom.fY }; + SkVector cornerRight = { halfW - coneBottom.fX, halfH - radii.fY - coneBottom.fY }; + + SkScalar cross1 = ptInConeSpace.cross(cornerTop); + SkScalar cross2 = cornerRight.cross(ptInConeSpace); + bool inCone = cross1 > 0.0f && cross2 > 0.0f; + + if (!inCone) { + SkScalar xDist = (halfW - delta.fX) * invRad; + SkScalar yDist = (halfH - delta.fY) * invRad; + + return SkTPin(SkTMin(xDist, yDist), 0.0f, 1.0f); // perpendicular distance + } + + SkVector cornerCenterInConeSpace = { insetDist - radii.fX, insetDist - radii.fY }; + + SkVector connectingVec = ptInConeSpace - cornerCenterInConeSpace; + float distToPtInConeSpace = SkPoint::Normalize(&ptInConeSpace); + + // "a" (i.e., dot(ptInConeSpace, ptInConeSpace) should always be 1.0f since + // ptInConeSpace is now normalized + SkScalar b = 2.0f * ptInConeSpace.dot(connectingVec); + SkScalar c = connectingVec.dot(connectingVec) - radii.fX * radii.fY; + + // lop off negative values that are outside the cone + SkScalar coneDist = SkTMax(0.0f, 0.5f * (-b + SkScalarSqrt(b*b - 4*c))); + + // make the coneDist a fraction of how far it is from the edge to the cone's base + coneDist = (maxValue*coneDist) / (coneDist+distToPtInConeSpace); + return SkTPin(coneDist, 0.0f, 1.0f); + } + default: + return 0.0f; + } +} + +bool SkRRectsGaussianEdgeMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, + const SkMatrix& matrix, + SkIPoint* margin) const { + + if (src.fFormat != SkMask::kA8_Format) { + return false; + } + + if (margin) { + margin->set(0, 0); + } + + dst->fBounds = src.fBounds; + dst->fRowBytes = dst->fBounds.width(); + dst->fFormat = SkMask::kA8_Format; + dst->fImage = nullptr; + + if (src.fImage) { + size_t dstSize = dst->computeImageSize(); + if (0 == dstSize) { + return false; // too big to allocate, abort + } + + const uint8_t* srcPixels = src.fImage; + uint8_t* dstPixels = dst->fImage = SkMask::AllocImage(dstSize); + + SkPoint basePt = { SkIntToScalar(src.fBounds.fLeft), SkIntToScalar(src.fBounds.fTop) }; + matrix.mapPoints(&basePt, 1); + + for (int y = 0; y < dst->fBounds.height(); ++y) { + const uint8_t* srcRow = srcPixels + y * dst->fRowBytes; + uint8_t* dstRow = dstPixels + y*dst->fRowBytes; + + for (int x = 0; x < dst->fBounds.width(); ++x) { + SkPoint curPt = { basePt.fX + x, basePt.fY + y }; + + SkVector vec; + vec.fX = 1.0f - compute_rrect_normalized_dist(fFirst, curPt, fRadius); + vec.fY = 1.0f - compute_rrect_normalized_dist(fSecond, curPt, fRadius); + + SkScalar factor = SkTPin(vec.length(), 0.0f, 1.0f); + factor = exp(-factor * factor * 4.0f) - 0.018f; + SkASSERT(factor >= 0.0f && factor <= 1.0f); + + dstRow[x] = (uint8_t) (factor * srcRow[x]); + } + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "GrCoordTransform.h" +#include "GrFragmentProcessor.h" +#include "GrInvariantOutput.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#include "SkGr.h" +#include "SkGrPriv.h" + +class RRectsGaussianEdgeFP : public GrFragmentProcessor { +public: + enum Mode { + kCircle_Mode, + kRect_Mode, + kSimpleCircular_Mode, + }; + + RRectsGaussianEdgeFP(const SkRRect& first, const SkRRect& second, SkScalar radius) + : fFirst(first) + , fSecond(second) + , fRadius(radius) { + this->initClassID<RRectsGaussianEdgeFP>(); + this->setWillReadFragmentPosition(); + + fFirstMode = ComputeMode(fFirst); + fSecondMode = ComputeMode(fSecond); + } + + class GLSLRRectsGaussianEdgeFP : public GrGLSLFragmentProcessor { + public: + GLSLRRectsGaussianEdgeFP() { } + + // This method emits code so that, for each shape, the distance from the edge is returned + // in 'outputName' clamped to 0..1 with positive distance being towards the center of the + // shape. The distance will have been normalized by the radius. + void emitModeCode(Mode mode, + GrGLSLFPFragmentBuilder* fragBuilder, + const char* posName, + const char* sizesName, + const char* radiiName, + const char* radName, + const char* outputName, + const char indices[2]) { // how to access the params for the 2 rrects + + // Positive distance is towards the center of the circle. + // Map all the cases to the lower right quadrant. + fragBuilder->codeAppendf("vec2 delta = abs(%s.xy - %s.%s);", + fragBuilder->fragmentPosition(), posName, indices); + + switch (mode) { + case kCircle_Mode: + // When a shadow circle gets large we can have some precision issues if + // we do "length(delta)/radius". The scaleDist temporary cuts the + // delta vector down a bit before invoking length. + fragBuilder->codeAppendf("float scaledDist = length(delta/%s);", radName); + fragBuilder->codeAppendf("%s = clamp((%s.%c/%s - scaledDist), 0.0, 1.0);", + outputName, sizesName, indices[0], radName); + break; + case kRect_Mode: + fragBuilder->codeAppendf( + "vec2 rectDist = vec2(1.0 - clamp((%s.%c - delta.x)/%s, 0.0, 1.0)," + "1.0 - clamp((%s.%c - delta.y)/%s, 0.0, 1.0));", + sizesName, indices[0], radName, + sizesName, indices[1], radName); + fragBuilder->codeAppendf("%s = clamp(1.0 - length(rectDist), 0.0, 1.0);", + outputName); + break; + case kSimpleCircular_Mode: + // For the circular round rect we combine 2 distances: + // the fractional position from the corner inset point to the corner's circle + // the minimum perpendicular distance to the bounding rectangle + // The first distance is used when the pixel is inside the ice-cream-cone-shaped + // portion of a corner. The second is used everywhere else. + // This is intended to approximate the interpolation pattern if we had + // tessellated this geometry into a RRect outside and a rect inside. + + //---------------- + // rect distance computation + fragBuilder->codeAppendf("float xDist = (%s.%c - delta.x) / %s;", + sizesName, indices[0], radName); + fragBuilder->codeAppendf("float yDist = (%s.%c - delta.y) / %s;", + sizesName, indices[1], radName); + fragBuilder->codeAppend("float rectDist = clamp(min(xDist, yDist), 0.0, 1.0);"); + + //---------------- + // ice-cream-cone fractional distance computation + + // When the blurRadius is larger than the corner radius we want to use it to + // compute the pointy end of the ice cream cone. If it smaller we just want to + // use the center of the corner's circle. When using the blurRadius the inset + // amount can't exceed the halfwidths of the RRect. + fragBuilder->codeAppendf("float insetDist = min(max(%s, %s.%c)," + "min(%s.%c, %s.%c));", + radName, radiiName, indices[0], + sizesName, indices[0], sizesName, indices[1]); + // "maxValue" is a correction term for if the blurRadius is larger than the + // size of the RRect. In that case we don't want to go all the way to black. + fragBuilder->codeAppendf("float maxValue = insetDist/%s;", radName); + + fragBuilder->codeAppendf("vec2 coneBottom = vec2(%s.%c - insetDist," + "%s.%c - insetDist);", + sizesName, indices[0], sizesName, indices[1]); + + fragBuilder->codeAppendf("vec2 cornerTop = vec2(%s.%c - %s.%c, %s.%c) -" + "coneBottom;", + sizesName, indices[0], radiiName, indices[0], + sizesName, indices[1]); + fragBuilder->codeAppendf("vec2 cornerRight = vec2(%s.%c, %s.%c - %s.%c) -" + "coneBottom;", + sizesName, indices[0], + sizesName, indices[1], radiiName, indices[1]); + + fragBuilder->codeAppend("vec2 ptInConeSpace = delta - coneBottom;"); + fragBuilder->codeAppend("float distToPtInConeSpace = length(ptInConeSpace);"); + + fragBuilder->codeAppend("float cross1 = ptInConeSpace.x * cornerTop.y -" + "ptInConeSpace.y * cornerTop.x;"); + fragBuilder->codeAppend("float cross2 = -ptInConeSpace.x * cornerRight.y + " + "ptInConeSpace.y * cornerRight.x;"); + + fragBuilder->codeAppend("float inCone = step(0.0, cross1) *" + "step(0.0, cross2);"); + + fragBuilder->codeAppendf("vec2 cornerCenterInConeSpace = vec2(insetDist -" + "%s.%c);", + radiiName, indices[0]); + + fragBuilder->codeAppend("vec2 connectingVec = ptInConeSpace -" + "cornerCenterInConeSpace;"); + fragBuilder->codeAppend("ptInConeSpace = normalize(ptInConeSpace);"); + + // "a" (i.e., dot(ptInConeSpace, ptInConeSpace) should always be 1.0f since + // ptInConeSpace is now normalized + fragBuilder->codeAppend("float b = 2.0 * dot(ptInConeSpace, connectingVec);"); + fragBuilder->codeAppendf("float c = dot(connectingVec, connectingVec) - " + "%s.%c * %s.%c;", + radiiName, indices[0], radiiName, indices[0]); + + fragBuilder->codeAppend("float fourAC = 4*c;"); + // This max prevents sqrt(-1) when outside the cone + fragBuilder->codeAppend("float bSq = max(b*b, fourAC);"); + + // lop off negative values that are outside the cone + fragBuilder->codeAppend("float coneDist = " + "max(0.0, 0.5 * (-b + sqrt(bSq - fourAC)));"); + // make the coneDist a fraction of how far it is from the edge to the + // cone's base + fragBuilder->codeAppend("coneDist = (maxValue*coneDist) /" + "(coneDist+distToPtInConeSpace);"); + fragBuilder->codeAppend("coneDist = clamp(coneDist, 0.0, 1.0);"); + + //---------------- + fragBuilder->codeAppendf("%s = mix(rectDist, coneDist, inCone);", outputName); + break; + } + } + + void emitCode(EmitArgs& args) override { + const RRectsGaussianEdgeFP& fp = args.fFp.cast<RRectsGaussianEdgeFP>(); + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + const char* positionsUniName = nullptr; + fPositionsUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec4f_GrSLType, kDefault_GrSLPrecision, + "Positions", &positionsUniName); + const char* sizesUniName = nullptr; + fSizesUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec4f_GrSLType, kDefault_GrSLPrecision, + "Sizes", &sizesUniName); + const char* radiiUniName = nullptr; + if (fp.fFirstMode == kSimpleCircular_Mode || fp.fSecondMode == kSimpleCircular_Mode) { + fRadiiUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec4f_GrSLType, kDefault_GrSLPrecision, + "Radii", &radiiUniName); + } + const char* radUniName = nullptr; + fRadiusUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat_GrSLType, kDefault_GrSLPrecision, + "Radius", &radUniName); + + fragBuilder->codeAppend("float firstDist;"); + fragBuilder->codeAppend("{"); + this->emitModeCode(fp.firstMode(), fragBuilder, + positionsUniName, sizesUniName, radiiUniName, + radUniName, "firstDist", "xy"); + fragBuilder->codeAppend("}"); + + fragBuilder->codeAppend("float secondDist;"); + fragBuilder->codeAppend("{"); + this->emitModeCode(fp.secondMode(), fragBuilder, + positionsUniName, sizesUniName, radiiUniName, + radUniName, "secondDist", "zw"); + fragBuilder->codeAppend("}"); + + fragBuilder->codeAppend("vec2 distVec = vec2(1.0 - firstDist, 1.0 - secondDist);"); + + // Finally use the distance to apply the Gaussian edge + fragBuilder->codeAppend("float factor = clamp(length(distVec), 0.0, 1.0);"); + fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;"); + fragBuilder->codeAppendf("%s = factor*%s;", + args.fOutputColor, args.fInputColor); + } + + static void GenKey(const GrProcessor& proc, const GrGLSLCaps&, + GrProcessorKeyBuilder* b) { + const RRectsGaussianEdgeFP& fp = proc.cast<RRectsGaussianEdgeFP>(); + + b->add32(fp.firstMode() | (fp.secondMode() << 4)); + } + + protected: + void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override { + const RRectsGaussianEdgeFP& edgeFP = proc.cast<RRectsGaussianEdgeFP>(); + + const SkRRect& first = edgeFP.first(); + const SkRRect& second = edgeFP.second(); + + pdman.set4f(fPositionsUni, + first.getBounds().centerX(), + first.getBounds().centerY(), + second.getBounds().centerX(), + second.getBounds().centerY()); + + pdman.set4f(fSizesUni, + 0.5f * first.rect().width(), + 0.5f * first.rect().height(), + 0.5f * second.rect().width(), + 0.5f * second.rect().height()); + + if (edgeFP.firstMode() == kSimpleCircular_Mode || + edgeFP.secondMode() == kSimpleCircular_Mode) { + // This is a bit of overkill since fX should equal fY for both round rects but it + // makes the shader code simpler. + pdman.set4f(fRadiiUni, + first.getSimpleRadii().fX, first.getSimpleRadii().fY, + second.getSimpleRadii().fX, second.getSimpleRadii().fY); + } + + pdman.set1f(fRadiusUni, edgeFP.radius()); + } + + private: + // The centers of the two round rects (x1, y1, x2, y2) + GrGLSLProgramDataManager::UniformHandle fPositionsUni; + + // The half widths and half heights of the two round rects (w1/2, h1/2, w2/2, h2/2) + // For circles we still upload both width & height to simplify things + GrGLSLProgramDataManager::UniformHandle fSizesUni; + + // The corner radii of the two round rects (rx1, ry1, rx2, ry2) + // We upload both the x&y radii (although they are currently always the same) to make + // the indexing in the shader code simpler. In some future world we could also support + // non-circular corner round rects & ellipses. + GrGLSLProgramDataManager::UniformHandle fRadiiUni; + + // The radius parameters (radius) + GrGLSLProgramDataManager::UniformHandle fRadiusUni; + + typedef GrGLSLFragmentProcessor INHERITED; + }; + + void onGetGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLRRectsGaussianEdgeFP::GenKey(*this, caps, b); + } + + const char* name() const override { return "RRectsGaussianEdgeFP"; } + + void onComputeInvariantOutput(GrInvariantOutput* inout) const override { + inout->setToUnknown(GrInvariantOutput::kWill_ReadInput); + } + + const SkRRect& first() const { return fFirst; } + Mode firstMode() const { return fFirstMode; } + const SkRRect& second() const { return fSecond; } + Mode secondMode() const { return fSecondMode; } + SkScalar radius() const { return fRadius; } + +private: + static Mode ComputeMode(const SkRRect& rr) { + if (rr.isCircle()) { + return kCircle_Mode; + } else if (rr.isRect()) { + return kRect_Mode; + } else { + SkASSERT(rr.isSimpleCircular()); + return kSimpleCircular_Mode; + } + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + return new GLSLRRectsGaussianEdgeFP; + } + + bool onIsEqual(const GrFragmentProcessor& proc) const override { + const RRectsGaussianEdgeFP& edgeFP = proc.cast<RRectsGaussianEdgeFP>(); + return fFirst == edgeFP.fFirst && + fSecond == edgeFP.fSecond && + fRadius == edgeFP.fRadius; + } + + SkRRect fFirst; + Mode fFirstMode; + SkRRect fSecond; + Mode fSecondMode; + SkScalar fRadius; + + typedef GrFragmentProcessor INHERITED; +}; + +//////////////////////////////////////////////////////////////////////////// +bool SkRRectsGaussianEdgeMaskFilterImpl::asFragmentProcessor(GrFragmentProcessor** fp, + GrTexture*, const + SkMatrix& ctm) const { + if (fp) { + *fp = new RRectsGaussianEdgeFP(fFirst, fSecond, fRadius); + } + + return true; +} + +#endif + +//////////////////////////////////////////////////////////////////////////// + +#ifndef SK_IGNORE_TO_STRING +void SkRRectsGaussianEdgeMaskFilterImpl::toString(SkString* str) const { + str->appendf("RRectsGaussianEdgeMaskFilter: ()"); +} +#endif + +sk_sp<SkFlattenable> SkRRectsGaussianEdgeMaskFilterImpl::CreateProc(SkReadBuffer& buf) { + SkRect rect1, rect2; + + buf.readRect(&rect1); + SkScalar xRad1 = buf.readScalar(); + SkScalar yRad1 = buf.readScalar(); + + buf.readRect(&rect2); + SkScalar xRad2 = buf.readScalar(); + SkScalar yRad2 = buf.readScalar(); + + SkScalar radius = buf.readScalar(); + + return sk_make_sp<SkRRectsGaussianEdgeMaskFilterImpl>(SkRRect::MakeRectXY(rect1, xRad1, yRad1), + SkRRect::MakeRectXY(rect2, xRad2, yRad2), + radius); +} + +void SkRRectsGaussianEdgeMaskFilterImpl::flatten(SkWriteBuffer& buf) const { + INHERITED::flatten(buf); + + SkASSERT(fFirst.isRect() || fFirst.isCircle() || fFirst.isSimpleCircular()); + buf.writeRect(fFirst.rect()); + const SkVector& radii1 = fFirst.getSimpleRadii(); + buf.writeScalar(radii1.fX); + buf.writeScalar(radii1.fY); + + SkASSERT(fSecond.isRect() || fSecond.isCircle() || fSecond.isSimpleCircular()); + buf.writeRect(fSecond.rect()); + const SkVector& radii2 = fSecond.getSimpleRadii(); + buf.writeScalar(radii2.fX); + buf.writeScalar(radii2.fY); + + buf.writeScalar(fRadius); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp<SkMaskFilter> SkRRectsGaussianEdgeMaskFilter::Make(const SkRRect& first, + const SkRRect& second, + SkScalar radius) { + if ((!first.isRect() && !first.isCircle() && !first.isSimpleCircular()) || + (!second.isRect() && !second.isCircle() && !second.isSimpleCircular())) { + // we only deal with the shapes where the x & y radii are equal + // and the same for all four corners + return nullptr; + } + + return sk_make_sp<SkRRectsGaussianEdgeMaskFilterImpl>(first, second, radius); +} + +/////////////////////////////////////////////////////////////////////////////// + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkRRectsGaussianEdgeMaskFilter) +SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkRRectsGaussianEdgeMaskFilterImpl) +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END + +/////////////////////////////////////////////////////////////////////////////// |