3 * Copyright 2006 The Android Open Source Project
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
9 #include "SkBlurMaskFilter.h"
10 #include "SkBlurMask.h"
11 #include "SkGpuBlurUtils.h"
12 #include "SkReadBuffer.h"
13 #include "SkWriteBuffer.h"
14 #include "SkMaskFilter.h"
17 #include "SkStringUtils.h"
18 #include "SkStrokeRec.h"
21 #include "GrCircleBlurFragmentProcessor.h"
22 #include "GrContext.h"
23 #include "GrDrawContext.h"
24 #include "GrTexture.h"
25 #include "GrFragmentProcessor.h"
26 #include "GrInvariantOutput.h"
27 #include "SkGrPixelRef.h"
29 #include "effects/GrSimpleTextureEffect.h"
30 #include "glsl/GrGLSLFragmentProcessor.h"
31 #include "glsl/GrGLSLFragmentShaderBuilder.h"
32 #include "glsl/GrGLSLProgramBuilder.h"
33 #include "glsl/GrGLSLProgramDataManager.h"
34 #include "glsl/GrGLSLTextureSampler.h"
37 SkScalar SkBlurMaskFilter::ConvertRadiusToSigma(SkScalar radius) {
38 return SkBlurMask::ConvertRadiusToSigma(radius);
41 class SkBlurMaskFilterImpl : public SkMaskFilter {
43 SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle, uint32_t flags);
45 // overrides from SkMaskFilter
46 SkMask::Format getFormat() const override;
47 bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
48 SkIPoint* margin) const override;
51 bool canFilterMaskGPU(const SkRRect& devRRect,
52 const SkIRect& clipBounds,
54 SkRect* maskRect) const override;
55 bool directFilterMaskGPU(GrTextureProvider* texProvider,
56 GrDrawContext* drawContext,
59 const SkMatrix& viewMatrix,
60 const SkStrokeRec& strokeRec,
61 const SkPath& path) const override;
62 bool directFilterRRectMaskGPU(GrTextureProvider* texProvider,
63 GrDrawContext* drawContext,
66 const SkMatrix& viewMatrix,
67 const SkStrokeRec& strokeRec,
68 const SkRRect& rrect) const override;
69 bool filterMaskGPU(GrTexture* src,
71 const SkRect& maskRect,
73 bool canOverwriteSrc) const override;
76 void computeFastBounds(const SkRect&, SkRect*) const override;
77 bool asABlur(BlurRec*) const override;
79 SK_TO_STRING_OVERRIDE()
80 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurMaskFilterImpl)
83 FilterReturn filterRectsToNine(const SkRect[], int count, const SkMatrix&,
84 const SkIRect& clipBounds,
85 NinePatch*) const override;
87 FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&,
88 const SkIRect& clipBounds,
89 NinePatch*) const override;
91 bool filterRectMask(SkMask* dstM, const SkRect& r, const SkMatrix& matrix,
92 SkIPoint* margin, SkMask::CreateMode createMode) const;
93 bool filterRRectMask(SkMask* dstM, const SkRRect& r, const SkMatrix& matrix,
94 SkIPoint* margin, SkMask::CreateMode createMode) const;
97 // To avoid unseemly allocation requests (esp. for finite platforms like
98 // handset) we limit the radius so something manageable. (as opposed to
99 // a request like 10,000)
100 static const SkScalar kMAX_BLUR_SIGMA;
103 SkBlurStyle fBlurStyle;
106 SkBlurQuality getQuality() const {
107 return (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ?
108 kHigh_SkBlurQuality : kLow_SkBlurQuality;
111 SkBlurMaskFilterImpl(SkReadBuffer&);
112 void flatten(SkWriteBuffer&) const override;
114 SkScalar computeXformedSigma(const SkMatrix& ctm) const {
115 bool ignoreTransform = SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag);
117 SkScalar xformedSigma = ignoreTransform ? fSigma : ctm.mapRadius(fSigma);
118 return SkMinScalar(xformedSigma, kMAX_BLUR_SIGMA);
121 friend class SkBlurMaskFilter;
123 typedef SkMaskFilter INHERITED;
126 const SkScalar SkBlurMaskFilterImpl::kMAX_BLUR_SIGMA = SkIntToScalar(128);
128 SkMaskFilter* SkBlurMaskFilter::Create(SkBlurStyle style, SkScalar sigma, uint32_t flags) {
129 if (!SkScalarIsFinite(sigma) || sigma <= 0) {
132 if ((unsigned)style > (unsigned)kLastEnum_SkBlurStyle) {
135 if (flags > SkBlurMaskFilter::kAll_BlurFlag) {
138 return new SkBlurMaskFilterImpl(sigma, style, flags);
141 ///////////////////////////////////////////////////////////////////////////////
143 SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle style, uint32_t flags)
146 , fBlurFlags(flags) {
147 SkASSERT(fSigma > 0);
148 SkASSERT((unsigned)style <= kLastEnum_SkBlurStyle);
149 SkASSERT(flags <= SkBlurMaskFilter::kAll_BlurFlag);
152 SkMask::Format SkBlurMaskFilterImpl::getFormat() const {
153 return SkMask::kA8_Format;
156 bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const {
157 if (fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
162 rec->fSigma = fSigma;
163 rec->fStyle = fBlurStyle;
164 rec->fQuality = this->getQuality();
169 bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
170 const SkMatrix& matrix,
171 SkIPoint* margin) const {
172 SkScalar sigma = this->computeXformedSigma(matrix);
173 return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, this->getQuality(), margin);
176 bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r,
177 const SkMatrix& matrix,
178 SkIPoint* margin, SkMask::CreateMode createMode) const {
179 SkScalar sigma = computeXformedSigma(matrix);
181 return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, margin, createMode);
184 bool SkBlurMaskFilterImpl::filterRRectMask(SkMask* dst, const SkRRect& r,
185 const SkMatrix& matrix,
186 SkIPoint* margin, SkMask::CreateMode createMode) const {
187 SkScalar sigma = computeXformedSigma(matrix);
189 return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, margin, createMode);
192 #include "SkCanvas.h"
194 static bool prepare_to_draw_into_mask(const SkRect& bounds, SkMask* mask) {
195 SkASSERT(mask != nullptr);
197 mask->fBounds = bounds.roundOut();
198 mask->fRowBytes = SkAlign4(mask->fBounds.width());
199 mask->fFormat = SkMask::kA8_Format;
200 const size_t size = mask->computeImageSize();
201 mask->fImage = SkMask::AllocImage(size);
202 if (nullptr == mask->fImage) {
206 // FIXME: use sk_calloc in AllocImage?
207 sk_bzero(mask->fImage, size);
211 static bool draw_rrect_into_mask(const SkRRect rrect, SkMask* mask) {
212 if (!prepare_to_draw_into_mask(rrect.rect(), mask)) {
216 // FIXME: This code duplicates code in draw_rects_into_mask, below. Is there a
217 // clean way to share more code?
219 bitmap.installMaskPixels(*mask);
221 SkCanvas canvas(bitmap);
222 canvas.translate(-SkIntToScalar(mask->fBounds.left()),
223 -SkIntToScalar(mask->fBounds.top()));
226 paint.setAntiAlias(true);
227 canvas.drawRRect(rrect, paint);
231 static bool draw_rects_into_mask(const SkRect rects[], int count, SkMask* mask) {
232 if (!prepare_to_draw_into_mask(rects[0], mask)) {
237 bitmap.installPixels(SkImageInfo::Make(mask->fBounds.width(),
238 mask->fBounds.height(),
239 kAlpha_8_SkColorType,
240 kPremul_SkAlphaType),
241 mask->fImage, mask->fRowBytes);
243 SkCanvas canvas(bitmap);
244 canvas.translate(-SkIntToScalar(mask->fBounds.left()),
245 -SkIntToScalar(mask->fBounds.top()));
248 paint.setAntiAlias(true);
251 canvas.drawRect(rects[0], paint);
253 // todo: do I need a fast way to do this?
255 path.addRect(rects[0]);
256 path.addRect(rects[1]);
257 path.setFillType(SkPath::kEvenOdd_FillType);
258 canvas.drawPath(path, paint);
263 static bool rect_exceeds(const SkRect& r, SkScalar v) {
264 return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v ||
265 r.width() > v || r.height() > v;
268 #include "SkMaskCache.h"
270 static SkCachedData* copy_mask_to_cacheddata(SkMask* mask) {
271 const size_t size = mask->computeTotalImageSize();
272 SkCachedData* data = SkResourceCache::NewCachedData(size);
274 memcpy(data->writable_data(), mask->fImage, size);
275 SkMask::FreeImage(mask->fImage);
276 mask->fImage = (uint8_t*)data->data();
281 static SkCachedData* find_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style,
282 SkBlurQuality quality, const SkRRect& rrect) {
283 return SkMaskCache::FindAndRef(sigma, style, quality, rrect, mask);
286 static SkCachedData* add_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style,
287 SkBlurQuality quality, const SkRRect& rrect) {
288 SkCachedData* cache = copy_mask_to_cacheddata(mask);
290 SkMaskCache::Add(sigma, style, quality, rrect, *mask, cache);
295 static SkCachedData* find_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style,
296 SkBlurQuality quality, const SkRect rects[], int count) {
297 return SkMaskCache::FindAndRef(sigma, style, quality, rects, count, mask);
300 static SkCachedData* add_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style,
301 SkBlurQuality quality, const SkRect rects[], int count) {
302 SkCachedData* cache = copy_mask_to_cacheddata(mask);
304 SkMaskCache::Add(sigma, style, quality, rects, count, *mask, cache);
309 #ifdef SK_IGNORE_FAST_RRECT_BLUR
310 SK_CONF_DECLARE(bool, c_analyticBlurRRect, "mask.filter.blur.analyticblurrrect", false, "Use the faster analytic blur approach for ninepatch rects");
312 SK_CONF_DECLARE(bool, c_analyticBlurRRect, "mask.filter.blur.analyticblurrrect", true, "Use the faster analytic blur approach for ninepatch round rects");
315 SkMaskFilter::FilterReturn
316 SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix,
317 const SkIRect& clipBounds,
318 NinePatch* patch) const {
319 SkASSERT(patch != nullptr);
320 switch (rrect.getType()) {
321 case SkRRect::kEmpty_Type:
323 return kFalse_FilterReturn;
325 case SkRRect::kRect_Type:
326 // We should have caught this earlier.
329 case SkRRect::kOval_Type:
330 // The nine patch special case does not handle ovals, and we
331 // already have code for rectangles.
332 return kUnimplemented_FilterReturn;
334 // These three can take advantage of this fast path.
335 case SkRRect::kSimple_Type:
336 case SkRRect::kNinePatch_Type:
337 case SkRRect::kComplex_Type:
341 // TODO: report correct metrics for innerstyle, where we do not grow the
342 // total bounds, but we do need an inset the size of our blur-radius
343 if (kInner_SkBlurStyle == fBlurStyle) {
344 return kUnimplemented_FilterReturn;
347 // TODO: take clipBounds into account to limit our coordinates up front
348 // for now, just skip too-large src rects (to take the old code path).
349 if (rect_exceeds(rrect.rect(), SkIntToScalar(32767))) {
350 return kUnimplemented_FilterReturn;
355 srcM.fBounds = rrect.rect().roundOut();
356 srcM.fImage = nullptr;
357 srcM.fFormat = SkMask::kA8_Format;
360 bool filterResult = false;
361 if (c_analyticBlurRRect) {
362 // special case for fast round rect blur
363 // don't actually do the blur the first time, just compute the correct size
364 filterResult = this->filterRRectMask(&dstM, rrect, matrix, &margin,
365 SkMask::kJustComputeBounds_CreateMode);
369 filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
373 return kFalse_FilterReturn;
376 // Now figure out the appropriate width and height of the smaller round rectangle
377 // to stretch. It will take into account the larger radius per side as well as double
378 // the margin, to account for inner and outer blur.
379 const SkVector& UL = rrect.radii(SkRRect::kUpperLeft_Corner);
380 const SkVector& UR = rrect.radii(SkRRect::kUpperRight_Corner);
381 const SkVector& LR = rrect.radii(SkRRect::kLowerRight_Corner);
382 const SkVector& LL = rrect.radii(SkRRect::kLowerLeft_Corner);
384 const SkScalar leftUnstretched = SkTMax(UL.fX, LL.fX) + SkIntToScalar(2 * margin.fX);
385 const SkScalar rightUnstretched = SkTMax(UR.fX, LR.fX) + SkIntToScalar(2 * margin.fX);
387 // Extra space in the middle to ensure an unchanging piece for stretching. Use 3 to cover
388 // any fractional space on either side plus 1 for the part to stretch.
389 const SkScalar stretchSize = SkIntToScalar(3);
391 const SkScalar totalSmallWidth = leftUnstretched + rightUnstretched + stretchSize;
392 if (totalSmallWidth >= rrect.rect().width()) {
393 // There is no valid piece to stretch.
394 return kUnimplemented_FilterReturn;
397 const SkScalar topUnstretched = SkTMax(UL.fY, UR.fY) + SkIntToScalar(2 * margin.fY);
398 const SkScalar bottomUnstretched = SkTMax(LL.fY, LR.fY) + SkIntToScalar(2 * margin.fY);
400 const SkScalar totalSmallHeight = topUnstretched + bottomUnstretched + stretchSize;
401 if (totalSmallHeight >= rrect.rect().height()) {
402 // There is no valid piece to stretch.
403 return kUnimplemented_FilterReturn;
406 SkRect smallR = SkRect::MakeWH(totalSmallWidth, totalSmallHeight);
410 radii[SkRRect::kUpperLeft_Corner] = UL;
411 radii[SkRRect::kUpperRight_Corner] = UR;
412 radii[SkRRect::kLowerRight_Corner] = LR;
413 radii[SkRRect::kLowerLeft_Corner] = LL;
414 smallRR.setRectRadii(smallR, radii);
416 const SkScalar sigma = this->computeXformedSigma(matrix);
417 SkCachedData* cache = find_cached_rrect(&patch->fMask, sigma, fBlurStyle,
418 this->getQuality(), smallRR);
420 bool analyticBlurWorked = false;
421 if (c_analyticBlurRRect) {
423 this->filterRRectMask(&patch->fMask, smallRR, matrix, &margin,
424 SkMask::kComputeBoundsAndRenderImage_CreateMode);
427 if (!analyticBlurWorked) {
428 if (!draw_rrect_into_mask(smallRR, &srcM)) {
429 return kFalse_FilterReturn;
432 SkAutoMaskFreeImage amf(srcM.fImage);
434 if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
435 return kFalse_FilterReturn;
438 cache = add_cached_rrect(&patch->fMask, sigma, fBlurStyle, this->getQuality(), smallRR);
441 patch->fMask.fBounds.offsetTo(0, 0);
442 patch->fOuterRect = dstM.fBounds;
443 patch->fCenter.fX = SkScalarCeilToInt(leftUnstretched) + 1;
444 patch->fCenter.fY = SkScalarCeilToInt(topUnstretched) + 1;
445 SkASSERT(nullptr == patch->fCache);
446 patch->fCache = cache; // transfer ownership to patch
447 return kTrue_FilterReturn;
450 SK_CONF_DECLARE(bool, c_analyticBlurNinepatch, "mask.filter.analyticNinePatch", true, "Use the faster analytic blur approach for ninepatch rects");
452 SkMaskFilter::FilterReturn
453 SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
454 const SkMatrix& matrix,
455 const SkIRect& clipBounds,
456 NinePatch* patch) const {
457 if (count < 1 || count > 2) {
458 return kUnimplemented_FilterReturn;
461 // TODO: report correct metrics for innerstyle, where we do not grow the
462 // total bounds, but we do need an inset the size of our blur-radius
463 if (kInner_SkBlurStyle == fBlurStyle || kOuter_SkBlurStyle == fBlurStyle) {
464 return kUnimplemented_FilterReturn;
467 // TODO: take clipBounds into account to limit our coordinates up front
468 // for now, just skip too-large src rects (to take the old code path).
469 if (rect_exceeds(rects[0], SkIntToScalar(32767))) {
470 return kUnimplemented_FilterReturn;
475 srcM.fBounds = rects[0].roundOut();
476 srcM.fImage = nullptr;
477 srcM.fFormat = SkMask::kA8_Format;
480 bool filterResult = false;
481 if (count == 1 && c_analyticBlurNinepatch) {
482 // special case for fast rect blur
483 // don't actually do the blur the first time, just compute the correct size
484 filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin,
485 SkMask::kJustComputeBounds_CreateMode);
487 filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
491 return kFalse_FilterReturn;
495 * smallR is the smallest version of 'rect' that will still guarantee that
496 * we get the same blur results on all edges, plus 1 center row/col that is
497 * representative of the extendible/stretchable edges of the ninepatch.
498 * Since our actual edge may be fractional we inset 1 more to be sure we
499 * don't miss any interior blur.
500 * x is an added pixel of blur, and { and } are the (fractional) edge
501 * pixels from the original rect.
503 * x x { x x .... x x } x x
505 * Thus, in this case, we inset by a total of 5 (on each side) beginning
506 * with our outer-rect (dstM.fBounds)
511 // +2 is from +1 for each edge (to account for possible fractional edges
512 int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2;
513 int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2;
517 innerIR = srcM.fBounds;
518 center.set(smallW, smallH);
520 SkASSERT(2 == count);
521 rects[1].roundIn(&innerIR);
522 center.set(smallW + (innerIR.left() - srcM.fBounds.left()),
523 smallH + (innerIR.top() - srcM.fBounds.top()));
526 // +1 so we get a clean, stretchable, center row/col
530 // we want the inset amounts to be integral, so we don't change any
531 // fractional phase on the fRight or fBottom of our smallR.
532 const SkScalar dx = SkIntToScalar(innerIR.width() - smallW);
533 const SkScalar dy = SkIntToScalar(innerIR.height() - smallH);
534 if (dx < 0 || dy < 0) {
535 // we're too small, relative to our blur, to break into nine-patch,
536 // so we ask to have our normal filterMask() be called.
537 return kUnimplemented_FilterReturn;
540 smallR[0].set(rects[0].left(), rects[0].top(), rects[0].right() - dx, rects[0].bottom() - dy);
541 if (smallR[0].width() < 2 || smallR[0].height() < 2) {
542 return kUnimplemented_FilterReturn;
545 smallR[1].set(rects[1].left(), rects[1].top(),
546 rects[1].right() - dx, rects[1].bottom() - dy);
547 SkASSERT(!smallR[1].isEmpty());
550 const SkScalar sigma = this->computeXformedSigma(matrix);
551 SkCachedData* cache = find_cached_rects(&patch->fMask, sigma, fBlurStyle,
552 this->getQuality(), smallR, count);
554 if (count > 1 || !c_analyticBlurNinepatch) {
555 if (!draw_rects_into_mask(smallR, count, &srcM)) {
556 return kFalse_FilterReturn;
559 SkAutoMaskFreeImage amf(srcM.fImage);
561 if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
562 return kFalse_FilterReturn;
565 if (!this->filterRectMask(&patch->fMask, smallR[0], matrix, &margin,
566 SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
567 return kFalse_FilterReturn;
570 cache = add_cached_rects(&patch->fMask, sigma, fBlurStyle, this->getQuality(), smallR, count);
572 patch->fMask.fBounds.offsetTo(0, 0);
573 patch->fOuterRect = dstM.fBounds;
574 patch->fCenter = center;
575 SkASSERT(nullptr == patch->fCache);
576 patch->fCache = cache; // transfer ownership to patch
577 return kTrue_FilterReturn;
580 void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src,
582 SkScalar pad = 3.0f * fSigma;
584 dst->set(src.fLeft - pad, src.fTop - pad,
585 src.fRight + pad, src.fBottom + pad);
588 SkFlattenable* SkBlurMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
589 const SkScalar sigma = buffer.readScalar();
590 const unsigned style = buffer.readUInt();
591 const unsigned flags = buffer.readUInt();
592 if (style <= kLastEnum_SkBlurStyle) {
593 return SkBlurMaskFilter::Create((SkBlurStyle)style, sigma, flags);
598 void SkBlurMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
599 buffer.writeScalar(fSigma);
600 buffer.writeUInt(fBlurStyle);
601 buffer.writeUInt(fBlurFlags);
606 class GrGLRectBlurEffect;
608 class GrRectBlurEffect : public GrFragmentProcessor {
610 ~GrRectBlurEffect() override { }
612 const char* name() const override { return "RectBlur"; }
614 static GrFragmentProcessor* Create(GrTextureProvider *textureProvider,
615 const SkRect& rect, float sigma) {
616 int doubleProfileSize = SkScalarCeilToInt(12*sigma);
618 if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) {
619 // if the blur sigma is too large so the gaussian overlaps the whole
620 // rect in either direction, fall back to CPU path for now.
624 SkAutoTUnref<GrTexture> blurProfile(CreateBlurProfileTexture(textureProvider, sigma));
628 // in OpenGL ES, mediump floats have a minimum range of 2^14. If we have coordinates bigger
629 // than that, the shader math will end up with infinities and result in the blur effect not
630 // working correctly. To avoid this, we switch into highp when the coordinates are too big.
631 // As 2^14 is the minimum range but the actual range can be bigger, we might end up
632 // switching to highp sooner than strictly necessary, but most devices that have a bigger
633 // range for mediump also have mediump being exactly the same as highp (e.g. all non-OpenGL
634 // ES devices), and thus incur no additional penalty for the switch.
635 static const SkScalar kMAX_BLUR_COORD = SkIntToScalar(16000);
636 GrSLPrecision precision;
637 if (SkScalarAbs(rect.top()) > kMAX_BLUR_COORD ||
638 SkScalarAbs(rect.left()) > kMAX_BLUR_COORD ||
639 SkScalarAbs(rect.bottom()) > kMAX_BLUR_COORD ||
640 SkScalarAbs(rect.right()) > kMAX_BLUR_COORD ||
641 SkScalarAbs(rect.width()) > kMAX_BLUR_COORD ||
642 SkScalarAbs(rect.height()) > kMAX_BLUR_COORD) {
643 precision = kHigh_GrSLPrecision;
646 precision = kDefault_GrSLPrecision;
648 return new GrRectBlurEffect(rect, sigma, blurProfile, precision);
651 const SkRect& getRect() const { return fRect; }
652 float getSigma() const { return fSigma; }
655 GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blurProfile,
656 GrSLPrecision fPrecision);
658 GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
660 void onGetGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override;
662 bool onIsEqual(const GrFragmentProcessor&) const override;
664 void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
666 static GrTexture* CreateBlurProfileTexture(GrTextureProvider*, float sigma);
670 GrTextureAccess fBlurProfileAccess;
671 GrSLPrecision fPrecision;
673 GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
675 typedef GrFragmentProcessor INHERITED;
678 class GrGLRectBlurEffect : public GrGLSLFragmentProcessor {
680 GrGLRectBlurEffect(const GrProcessor&, GrSLPrecision precision)
681 : fPrecision(precision) {
683 void emitCode(EmitArgs&) override;
685 static void GenKey(GrSLPrecision precision, GrProcessorKeyBuilder* b);
688 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
691 typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
693 UniformHandle fProxyRectUniform;
694 UniformHandle fProfileSizeUniform;
695 GrSLPrecision fPrecision;
697 typedef GrGLSLFragmentProcessor INHERITED;
700 void OutputRectBlurProfileLookup(GrGLSLFragmentBuilder* fragBuilder,
701 const GrGLSLTextureSampler& sampler,
703 const char *profileSize, const char *loc,
704 const char *blurred_width,
705 const char *sharp_width) {
706 fragBuilder->codeAppendf("float %s;", output);
707 fragBuilder->codeAppendf("{");
708 fragBuilder->codeAppendf("float coord = ((abs(%s - 0.5 * %s) - 0.5 * %s)) / %s;",
709 loc, blurred_width, sharp_width, profileSize);
710 fragBuilder->codeAppendf("%s = ", output);
711 fragBuilder->appendTextureLookup(sampler, "vec2(coord,0.5)");
712 fragBuilder->codeAppend(".a;");
713 fragBuilder->codeAppendf("}");
717 void GrGLRectBlurEffect::GenKey(GrSLPrecision precision, GrProcessorKeyBuilder* b) {
722 void GrGLRectBlurEffect::emitCode(EmitArgs& args) {
724 const char *rectName;
725 const char *profileSizeName;
727 const char* precisionString = GrGLSLShaderVar::PrecisionString(args.fGLSLCaps, fPrecision);
728 fProxyRectUniform = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
733 fProfileSizeUniform = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
735 kDefault_GrSLPrecision,
739 GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
740 const char *fragmentPos = fragBuilder->fragmentPosition();
742 if (args.fInputColor) {
743 fragBuilder->codeAppendf("vec4 src=%s;", args.fInputColor);
745 fragBuilder->codeAppendf("vec4 src=vec4(1);");
748 fragBuilder->codeAppendf("%s vec2 translatedPos = %s.xy - %s.xy;", precisionString, fragmentPos,
750 fragBuilder->codeAppendf("%s float width = %s.z - %s.x;", precisionString, rectName, rectName);
751 fragBuilder->codeAppendf("%s float height = %s.w - %s.y;", precisionString, rectName, rectName);
753 fragBuilder->codeAppendf("%s vec2 smallDims = vec2(width - %s, height - %s);", precisionString,
754 profileSizeName, profileSizeName);
755 fragBuilder->codeAppendf("%s float center = 2.0 * floor(%s/2.0 + .25) - 1.0;", precisionString,
757 fragBuilder->codeAppendf("%s vec2 wh = smallDims - vec2(center,center);", precisionString);
759 OutputRectBlurProfileLookup(fragBuilder, args.fSamplers[0], "horiz_lookup", profileSizeName,
760 "translatedPos.x", "width", "wh.x");
761 OutputRectBlurProfileLookup(fragBuilder, args.fSamplers[0], "vert_lookup", profileSizeName,
762 "translatedPos.y", "height", "wh.y");
764 fragBuilder->codeAppendf("float final = horiz_lookup * vert_lookup;");
765 fragBuilder->codeAppendf("%s = src * final;", args.fOutputColor);
768 void GrGLRectBlurEffect::onSetData(const GrGLSLProgramDataManager& pdman,
769 const GrProcessor& proc) {
770 const GrRectBlurEffect& rbe = proc.cast<GrRectBlurEffect>();
771 SkRect rect = rbe.getRect();
773 pdman.set4f(fProxyRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
774 pdman.set1f(fProfileSizeUniform, SkScalarCeilToScalar(6*rbe.getSigma()));
777 GrTexture* GrRectBlurEffect::CreateBlurProfileTexture(GrTextureProvider* textureProvider,
779 GrSurfaceDesc texDesc;
781 unsigned int profileSize = SkScalarCeilToInt(6*sigma);
783 texDesc.fWidth = profileSize;
785 texDesc.fConfig = kAlpha_8_GrPixelConfig;
787 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
789 GrUniqueKey::Builder builder(&key, kDomain, 1);
790 builder[0] = profileSize;
793 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key);
796 SkAutoTDeleteArray<uint8_t> profile(SkBlurMask::ComputeBlurProfile(sigma));
798 blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0);
800 textureProvider->assignUniqueKeyToTexture(key, blurProfile);
807 GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blurProfile,
808 GrSLPrecision precision)
811 , fBlurProfileAccess(blurProfile)
812 , fPrecision(precision) {
813 this->initClassID<GrRectBlurEffect>();
814 this->addTextureAccess(&fBlurProfileAccess);
815 this->setWillReadFragmentPosition();
818 void GrRectBlurEffect::onGetGLSLProcessorKey(const GrGLSLCaps& caps,
819 GrProcessorKeyBuilder* b) const {
820 GrGLRectBlurEffect::GenKey(fPrecision, b);
823 GrGLSLFragmentProcessor* GrRectBlurEffect::onCreateGLSLInstance() const {
824 return new GrGLRectBlurEffect(*this, fPrecision);
827 bool GrRectBlurEffect::onIsEqual(const GrFragmentProcessor& sBase) const {
828 const GrRectBlurEffect& s = sBase.cast<GrRectBlurEffect>();
829 return this->getSigma() == s.getSigma() && this->getRect() == s.getRect();
832 void GrRectBlurEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const {
833 inout->mulByUnknownSingleComponent();
836 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRectBlurEffect);
838 const GrFragmentProcessor* GrRectBlurEffect::TestCreate(GrProcessorTestData* d) {
839 float sigma = d->fRandom->nextRangeF(3,8);
840 float width = d->fRandom->nextRangeF(200,300);
841 float height = d->fRandom->nextRangeF(200,300);
842 return GrRectBlurEffect::Create(d->fContext->textureProvider(), SkRect::MakeWH(width, height),
847 bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider,
848 GrDrawContext* drawContext,
851 const SkMatrix& viewMatrix,
852 const SkStrokeRec& strokeRec,
853 const SkPath& path) const {
854 SkASSERT(drawContext);
856 if (fBlurStyle != kNormal_SkBlurStyle) {
860 // TODO: we could handle blurred stroked circles
861 if (!strokeRec.isFillStyle()) {
865 SkScalar xformedSigma = this->computeXformedSigma(viewMatrix);
867 SkAutoTUnref<const GrFragmentProcessor> fp;
870 if (path.isRect(&rect)) {
871 int pad = SkScalarCeilToInt(6*xformedSigma)/2;
872 rect.outset(SkIntToScalar(pad), SkIntToScalar(pad));
874 fp.reset(GrRectBlurEffect::Create(texProvider, rect, xformedSigma));
875 } else if (path.isOval(&rect) && SkScalarNearlyEqual(rect.width(), rect.height())) {
876 fp.reset(GrCircleBlurFragmentProcessor::Create(texProvider, rect, xformedSigma));
878 // expand the rect for the coverage geometry
879 int pad = SkScalarCeilToInt(6*xformedSigma)/2;
880 rect.outset(SkIntToScalar(pad), SkIntToScalar(pad));
889 grp->addCoverageFragmentProcessor(fp);
892 if (!viewMatrix.invert(&inverse)) {
896 drawContext->fillRectWithLocalMatrix(clip, *grp, SkMatrix::I(), rect, inverse);
900 //////////////////////////////////////////////////////////////////////////////
902 class GrRRectBlurEffect : public GrFragmentProcessor {
905 static const GrFragmentProcessor* Create(GrTextureProvider*, float sigma, const SkRRect&);
907 virtual ~GrRRectBlurEffect() {};
908 const char* name() const override { return "GrRRectBlur"; }
910 const SkRRect& getRRect() const { return fRRect; }
911 float getSigma() const { return fSigma; }
914 GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
916 GrRRectBlurEffect(float sigma, const SkRRect&, GrTexture* profileTexture);
918 virtual void onGetGLSLProcessorKey(const GrGLSLCaps& caps,
919 GrProcessorKeyBuilder* b) const override;
921 bool onIsEqual(const GrFragmentProcessor& other) const override;
923 void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
927 GrTextureAccess fNinePatchAccess;
929 GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
931 typedef GrFragmentProcessor INHERITED;
935 const GrFragmentProcessor* GrRRectBlurEffect::Create(GrTextureProvider* texProvider, float sigma,
936 const SkRRect& rrect) {
937 if (rrect.isCircle()) {
938 return GrCircleBlurFragmentProcessor::Create(texProvider, rrect.rect(), sigma);
941 if (!rrect.isSimpleCircular()) {
945 // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
946 // sufficiently small relative to both the size of the corner radius and the
947 // width (and height) of the rrect.
949 unsigned int blurRadius = 3*SkScalarCeilToInt(sigma-1/6.0f);
950 unsigned int cornerRadius = SkScalarCeilToInt(rrect.getSimpleRadii().x());
951 if (cornerRadius + blurRadius > rrect.width()/2 ||
952 cornerRadius + blurRadius > rrect.height()/2) {
956 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
958 GrUniqueKey::Builder builder(&key, kDomain, 2);
959 builder[0] = blurRadius;
960 builder[1] = cornerRadius;
963 SkAutoTUnref<GrTexture> blurNinePatchTexture(texProvider->findAndRefTextureByUniqueKey(key));
965 if (!blurNinePatchTexture) {
968 unsigned int smallRectSide = 2*(blurRadius + cornerRadius) + 1;
970 mask.fBounds = SkIRect::MakeWH(smallRectSide, smallRectSide);
971 mask.fFormat = SkMask::kA8_Format;
972 mask.fRowBytes = mask.fBounds.width();
973 mask.fImage = SkMask::AllocImage(mask.computeTotalImageSize());
974 SkAutoMaskFreeImage amfi(mask.fImage);
976 memset(mask.fImage, 0, mask.computeTotalImageSize());
979 smallRect.setWH(SkIntToScalar(smallRectSide), SkIntToScalar(smallRectSide));
982 smallRRect.setRectXY(smallRect, SkIntToScalar(cornerRadius), SkIntToScalar(cornerRadius));
985 path.addRRect(smallRRect);
987 SkDraw::DrawToMask(path, &mask.fBounds, nullptr, nullptr, &mask,
988 SkMask::kJustRenderImage_CreateMode, SkPaint::kFill_Style);
991 SkBlurMask::BoxBlur(&blurredMask, mask, sigma, kNormal_SkBlurStyle, kHigh_SkBlurQuality,
994 unsigned int texSide = smallRectSide + 2*blurRadius;
995 GrSurfaceDesc texDesc;
996 texDesc.fWidth = texSide;
997 texDesc.fHeight = texSide;
998 texDesc.fConfig = kAlpha_8_GrPixelConfig;
1000 blurNinePatchTexture.reset(
1001 texProvider->createTexture(texDesc, true, blurredMask.fImage, 0));
1002 SkMask::FreeImage(blurredMask.fImage);
1003 if (!blurNinePatchTexture) {
1006 texProvider->assignUniqueKeyToTexture(key, blurNinePatchTexture);
1008 return new GrRRectBlurEffect(sigma, rrect, blurNinePatchTexture);
1011 void GrRRectBlurEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const {
1012 inout->mulByUnknownSingleComponent();
1015 GrRRectBlurEffect::GrRRectBlurEffect(float sigma, const SkRRect& rrect, GrTexture *ninePatchTexture)
1018 fNinePatchAccess(ninePatchTexture) {
1019 this->initClassID<GrRRectBlurEffect>();
1020 this->addTextureAccess(&fNinePatchAccess);
1021 this->setWillReadFragmentPosition();
1024 bool GrRRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
1025 const GrRRectBlurEffect& rrbe = other.cast<GrRRectBlurEffect>();
1026 return fRRect.getSimpleRadii().fX == rrbe.fRRect.getSimpleRadii().fX && fSigma == rrbe.fSigma;
1029 //////////////////////////////////////////////////////////////////////////////
1031 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRRectBlurEffect);
1033 const GrFragmentProcessor* GrRRectBlurEffect::TestCreate(GrProcessorTestData* d) {
1034 SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f);
1035 SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f);
1036 SkScalar r = d->fRandom->nextRangeF(1.f, 9.f);
1037 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f);
1039 rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
1040 return GrRRectBlurEffect::Create(d->fContext->textureProvider(), sigma, rrect);
1043 //////////////////////////////////////////////////////////////////////////////
1045 class GrGLRRectBlurEffect : public GrGLSLFragmentProcessor {
1047 GrGLRRectBlurEffect(const GrProcessor&) {}
1049 virtual void emitCode(EmitArgs&) override;
1052 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
1055 GrGLSLProgramDataManager::UniformHandle fProxyRectUniform;
1056 GrGLSLProgramDataManager::UniformHandle fCornerRadiusUniform;
1057 GrGLSLProgramDataManager::UniformHandle fBlurRadiusUniform;
1058 typedef GrGLSLFragmentProcessor INHERITED;
1061 void GrGLRRectBlurEffect::emitCode(EmitArgs& args) {
1062 const char *rectName;
1063 const char *cornerRadiusName;
1064 const char *blurRadiusName;
1066 // The proxy rect has left, top, right, and bottom edges correspond to
1067 // components x, y, z, and w, respectively.
1069 fProxyRectUniform = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
1071 kDefault_GrSLPrecision,
1074 fCornerRadiusUniform = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
1076 kDefault_GrSLPrecision,
1079 fBlurRadiusUniform = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
1081 kDefault_GrSLPrecision,
1085 GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
1086 const char* fragmentPos = fragBuilder->fragmentPosition();
1088 // warp the fragment position to the appropriate part of the 9patch blur texture
1090 fragBuilder->codeAppendf("vec2 rectCenter = (%s.xy + %s.zw)/2.0;", rectName, rectName);
1091 fragBuilder->codeAppendf("vec2 translatedFragPos = %s.xy - %s.xy;", fragmentPos, rectName);
1092 fragBuilder->codeAppendf("float threshold = %s + 2.0*%s;", cornerRadiusName, blurRadiusName);
1093 fragBuilder->codeAppendf("vec2 middle = %s.zw - %s.xy - 2.0*threshold;", rectName, rectName);
1095 fragBuilder->codeAppendf(
1096 "if (translatedFragPos.x >= threshold && translatedFragPos.x < (middle.x+threshold)) {");
1097 fragBuilder->codeAppendf("translatedFragPos.x = threshold;\n");
1098 fragBuilder->codeAppendf("} else if (translatedFragPos.x >= (middle.x + threshold)) {");
1099 fragBuilder->codeAppendf("translatedFragPos.x -= middle.x - 1.0;");
1100 fragBuilder->codeAppendf("}");
1102 fragBuilder->codeAppendf(
1103 "if (translatedFragPos.y > threshold && translatedFragPos.y < (middle.y+threshold)) {");
1104 fragBuilder->codeAppendf("translatedFragPos.y = threshold;");
1105 fragBuilder->codeAppendf("} else if (translatedFragPos.y >= (middle.y + threshold)) {");
1106 fragBuilder->codeAppendf("translatedFragPos.y -= middle.y - 1.0;");
1107 fragBuilder->codeAppendf("}");
1109 fragBuilder->codeAppendf("vec2 proxyDims = vec2(2.0*threshold+1.0);");
1110 fragBuilder->codeAppendf("vec2 texCoord = translatedFragPos / proxyDims;");
1112 fragBuilder->codeAppendf("%s = ", args.fOutputColor);
1113 fragBuilder->appendTextureLookupAndModulate(args.fInputColor, args.fSamplers[0], "texCoord");
1114 fragBuilder->codeAppend(";");
1117 void GrGLRRectBlurEffect::onSetData(const GrGLSLProgramDataManager& pdman,
1118 const GrProcessor& proc) {
1119 const GrRRectBlurEffect& brre = proc.cast<GrRRectBlurEffect>();
1120 SkRRect rrect = brre.getRRect();
1122 float blurRadius = 3.f*SkScalarCeilToScalar(brre.getSigma()-1/6.0f);
1123 pdman.set1f(fBlurRadiusUniform, blurRadius);
1125 SkRect rect = rrect.getBounds();
1126 rect.outset(blurRadius, blurRadius);
1127 pdman.set4f(fProxyRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
1129 SkScalar radius = 0;
1130 SkASSERT(rrect.isSimpleCircular() || rrect.isRect());
1131 radius = rrect.getSimpleRadii().fX;
1132 pdman.set1f(fCornerRadiusUniform, radius);
1135 void GrRRectBlurEffect::onGetGLSLProcessorKey(const GrGLSLCaps& caps,
1136 GrProcessorKeyBuilder* b) const {
1137 GrGLRRectBlurEffect::GenKey(*this, caps, b);
1140 GrGLSLFragmentProcessor* GrRRectBlurEffect::onCreateGLSLInstance() const {
1141 return new GrGLRRectBlurEffect(*this);
1144 bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrTextureProvider* texProvider,
1145 GrDrawContext* drawContext,
1148 const SkMatrix& viewMatrix,
1149 const SkStrokeRec& strokeRec,
1150 const SkRRect& rrect) const {
1151 SkASSERT(drawContext);
1153 if (fBlurStyle != kNormal_SkBlurStyle) {
1157 if (!strokeRec.isFillStyle()) {
1161 SkScalar xformedSigma = this->computeXformedSigma(viewMatrix);
1162 float extra=3.f*SkScalarCeilToScalar(xformedSigma-1/6.0f);
1164 SkRect proxyRect = rrect.rect();
1165 proxyRect.outset(extra, extra);
1167 SkAutoTUnref<const GrFragmentProcessor> fp(GrRRectBlurEffect::Create(texProvider,
1168 xformedSigma, rrect));
1173 grp->addCoverageFragmentProcessor(fp);
1176 if (!viewMatrix.invert(&inverse)) {
1180 drawContext->fillRectWithLocalMatrix(clip, *grp, SkMatrix::I(), proxyRect, inverse);
1184 bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect,
1185 const SkIRect& clipBounds,
1186 const SkMatrix& ctm,
1187 SkRect* maskRect) const {
1188 SkScalar xformedSigma = this->computeXformedSigma(ctm);
1189 if (xformedSigma <= 0) {
1193 // We always do circles on the GPU
1194 if (!devRRect.isCircle()) {
1195 static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64);
1196 static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32);
1198 if (devRRect.width() <= kMIN_GPU_BLUR_SIZE &&
1199 devRRect.height() <= kMIN_GPU_BLUR_SIZE &&
1200 xformedSigma <= kMIN_GPU_BLUR_SIGMA) {
1201 // We prefer to blur small rects with small radii on the CPU.
1206 if (nullptr == maskRect) {
1207 // don't need to compute maskRect
1211 float sigma3 = 3 * SkScalarToFloat(xformedSigma);
1213 SkRect clipRect = SkRect::Make(clipBounds);
1214 SkRect srcRect(devRRect.rect());
1216 // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
1217 srcRect.outset(sigma3, sigma3);
1218 clipRect.outset(sigma3, sigma3);
1219 if (!srcRect.intersect(clipRect)) {
1222 *maskRect = srcRect;
1226 bool SkBlurMaskFilterImpl::filterMaskGPU(GrTexture* src,
1227 const SkMatrix& ctm,
1228 const SkRect& maskRect,
1230 bool canOverwriteSrc) const {
1231 SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height());
1233 GrContext* context = src->getContext();
1235 SkScalar xformedSigma = this->computeXformedSigma(ctm);
1236 SkASSERT(xformedSigma > 0);
1238 // If we're doing a normal blur, we can clobber the pathTexture in the
1239 // gaussianBlur. Otherwise, we need to save it for later compositing.
1240 bool isNormalBlur = (kNormal_SkBlurStyle == fBlurStyle);
1241 *result = SkGpuBlurUtils::GaussianBlur(context, src, isNormalBlur && canOverwriteSrc,
1242 clipRect, nullptr, xformedSigma, xformedSigma,
1243 GrTextureProvider::kApprox_SizeConstraint);
1244 if (nullptr == *result) {
1248 if (!isNormalBlur) {
1251 matrix.setIDiv(src->width(), src->height());
1252 // Blend pathTexture over blurTexture.
1253 paint.addCoverageFragmentProcessor(GrSimpleTextureEffect::Create(src, matrix))->unref();
1254 if (kInner_SkBlurStyle == fBlurStyle) {
1255 // inner: dst = dst * src
1256 paint.setCoverageSetOpXPFactory(SkRegion::kIntersect_Op);
1257 } else if (kSolid_SkBlurStyle == fBlurStyle) {
1258 // solid: dst = src + dst - src * dst
1259 // = src + (1 - src) * dst
1260 paint.setCoverageSetOpXPFactory(SkRegion::kUnion_Op);
1261 } else if (kOuter_SkBlurStyle == fBlurStyle) {
1262 // outer: dst = dst * (1 - src)
1263 // = 0 * src + (1 - src) * dst
1264 paint.setCoverageSetOpXPFactory(SkRegion::kDifference_Op);
1266 paint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op);
1269 SkAutoTUnref<GrDrawContext> drawContext(context->drawContext((*result)->asRenderTarget()));
1274 drawContext->drawRect(GrClip::WideOpen(), paint, SkMatrix::I(), clipRect);
1280 #endif // SK_SUPPORT_GPU
1283 #ifndef SK_IGNORE_TO_STRING
1284 void SkBlurMaskFilterImpl::toString(SkString* str) const {
1285 str->append("SkBlurMaskFilterImpl: (");
1287 str->append("sigma: ");
1288 str->appendScalar(fSigma);
1291 static const char* gStyleName[kLastEnum_SkBlurStyle + 1] = {
1292 "normal", "solid", "outer", "inner"
1295 str->appendf("style: %s ", gStyleName[fBlurStyle]);
1296 str->append("flags: (");
1298 bool needSeparator = false;
1299 SkAddFlagToString(str,
1300 SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag),
1301 "IgnoreXform", &needSeparator);
1302 SkAddFlagToString(str,
1303 SkToBool(fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag),
1304 "HighQuality", &needSeparator);
1306 str->append("None");
1312 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkBlurMaskFilter)
1313 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurMaskFilterImpl)
1314 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END