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 "GrContext.h"
22 #include "GrTexture.h"
23 #include "GrFragmentProcessor.h"
24 #include "gl/GrGLProcessor.h"
25 #include "gl/builders/GrGLProgramBuilder.h"
26 #include "effects/GrSimpleTextureEffect.h"
27 #include "GrTBackendProcessorFactory.h"
28 #include "SkGrPixelRef.h"
32 SkScalar SkBlurMaskFilter::ConvertRadiusToSigma(SkScalar radius) {
33 return SkBlurMask::ConvertRadiusToSigma(radius);
36 class SkBlurMaskFilterImpl : public SkMaskFilter {
38 SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle, uint32_t flags);
40 // overrides from SkMaskFilter
41 virtual SkMask::Format getFormat() const SK_OVERRIDE;
42 virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
43 SkIPoint* margin) const SK_OVERRIDE;
46 virtual bool canFilterMaskGPU(const SkRect& devBounds,
47 const SkIRect& clipBounds,
49 SkRect* maskRect) const SK_OVERRIDE;
50 virtual bool directFilterMaskGPU(GrContext* context,
52 const SkStrokeRec& strokeRec,
53 const SkPath& path) const SK_OVERRIDE;
54 virtual bool directFilterRRectMaskGPU(GrContext* context,
56 const SkStrokeRec& strokeRec,
57 const SkRRect& rrect) const SK_OVERRIDE;
59 virtual bool filterMaskGPU(GrTexture* src,
61 const SkRect& maskRect,
63 bool canOverwriteSrc) const SK_OVERRIDE;
66 virtual void computeFastBounds(const SkRect&, SkRect*) const SK_OVERRIDE;
67 virtual bool asABlur(BlurRec*) const SK_OVERRIDE;
69 SK_TO_STRING_OVERRIDE()
70 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurMaskFilterImpl)
73 virtual FilterReturn filterRectsToNine(const SkRect[], int count, const SkMatrix&,
74 const SkIRect& clipBounds,
75 NinePatch*) const SK_OVERRIDE;
77 virtual FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&,
78 const SkIRect& clipBounds,
79 NinePatch*) const SK_OVERRIDE;
81 bool filterRectMask(SkMask* dstM, const SkRect& r, const SkMatrix& matrix,
82 SkIPoint* margin, SkMask::CreateMode createMode) const;
83 bool filterRRectMask(SkMask* dstM, const SkRRect& r, const SkMatrix& matrix,
84 SkIPoint* margin, SkMask::CreateMode createMode) const;
87 // To avoid unseemly allocation requests (esp. for finite platforms like
88 // handset) we limit the radius so something manageable. (as opposed to
89 // a request like 10,000)
90 static const SkScalar kMAX_BLUR_SIGMA;
93 SkBlurStyle fBlurStyle;
96 SkBlurQuality getQuality() const {
97 return (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ?
98 kHigh_SkBlurQuality : kLow_SkBlurQuality;
101 SkBlurMaskFilterImpl(SkReadBuffer&);
102 virtual void flatten(SkWriteBuffer&) const SK_OVERRIDE;
104 SkScalar computeXformedSigma(const SkMatrix& ctm) const {
105 bool ignoreTransform = SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag);
107 SkScalar xformedSigma = ignoreTransform ? fSigma : ctm.mapRadius(fSigma);
108 return SkMinScalar(xformedSigma, kMAX_BLUR_SIGMA);
111 friend class SkBlurMaskFilter;
113 typedef SkMaskFilter INHERITED;
116 const SkScalar SkBlurMaskFilterImpl::kMAX_BLUR_SIGMA = SkIntToScalar(128);
118 SkMaskFilter* SkBlurMaskFilter::Create(SkBlurStyle style, SkScalar sigma, uint32_t flags) {
119 if (!SkScalarIsFinite(sigma) || sigma <= 0) {
122 if ((unsigned)style > (unsigned)kLastEnum_SkBlurStyle) {
125 if (flags > SkBlurMaskFilter::kAll_BlurFlag) {
128 return SkNEW_ARGS(SkBlurMaskFilterImpl, (sigma, style, flags));
131 ///////////////////////////////////////////////////////////////////////////////
133 SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle style, uint32_t flags)
136 , fBlurFlags(flags) {
137 SkASSERT(fSigma > 0);
138 SkASSERT((unsigned)style <= kLastEnum_SkBlurStyle);
139 SkASSERT(flags <= SkBlurMaskFilter::kAll_BlurFlag);
142 SkMask::Format SkBlurMaskFilterImpl::getFormat() const {
143 return SkMask::kA8_Format;
146 bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const {
147 if (fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
152 rec->fSigma = fSigma;
153 rec->fStyle = fBlurStyle;
154 rec->fQuality = this->getQuality();
159 bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
160 const SkMatrix& matrix,
161 SkIPoint* margin) const{
162 SkScalar sigma = this->computeXformedSigma(matrix);
163 return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, this->getQuality(), margin);
166 bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r,
167 const SkMatrix& matrix,
168 SkIPoint* margin, SkMask::CreateMode createMode) const{
169 SkScalar sigma = computeXformedSigma(matrix);
171 return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle,
175 bool SkBlurMaskFilterImpl::filterRRectMask(SkMask* dst, const SkRRect& r,
176 const SkMatrix& matrix,
177 SkIPoint* margin, SkMask::CreateMode createMode) const{
178 SkScalar sigma = computeXformedSigma(matrix);
180 return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle,
184 #include "SkCanvas.h"
186 static bool prepare_to_draw_into_mask(const SkRect& bounds, SkMask* mask) {
187 SkASSERT(mask != NULL);
189 bounds.roundOut(&mask->fBounds);
190 mask->fRowBytes = SkAlign4(mask->fBounds.width());
191 mask->fFormat = SkMask::kA8_Format;
192 const size_t size = mask->computeImageSize();
193 mask->fImage = SkMask::AllocImage(size);
194 if (NULL == mask->fImage) {
198 // FIXME: use sk_calloc in AllocImage?
199 sk_bzero(mask->fImage, size);
203 static bool draw_rrect_into_mask(const SkRRect rrect, SkMask* mask) {
204 if (!prepare_to_draw_into_mask(rrect.rect(), mask)) {
208 // FIXME: This code duplicates code in draw_rects_into_mask, below. Is there a
209 // clean way to share more code?
211 bitmap.installMaskPixels(*mask);
213 SkCanvas canvas(bitmap);
214 canvas.translate(-SkIntToScalar(mask->fBounds.left()),
215 -SkIntToScalar(mask->fBounds.top()));
218 paint.setAntiAlias(true);
219 canvas.drawRRect(rrect, paint);
223 static bool draw_rects_into_mask(const SkRect rects[], int count, SkMask* mask) {
224 if (!prepare_to_draw_into_mask(rects[0], mask)) {
229 bitmap.installPixels(SkImageInfo::Make(mask->fBounds.width(),
230 mask->fBounds.height(),
231 kAlpha_8_SkColorType,
232 kPremul_SkAlphaType),
233 mask->fImage, mask->fRowBytes);
235 SkCanvas canvas(bitmap);
236 canvas.translate(-SkIntToScalar(mask->fBounds.left()),
237 -SkIntToScalar(mask->fBounds.top()));
240 paint.setAntiAlias(true);
243 canvas.drawRect(rects[0], paint);
245 // todo: do I need a fast way to do this?
247 path.addRect(rects[0]);
248 path.addRect(rects[1]);
249 path.setFillType(SkPath::kEvenOdd_FillType);
250 canvas.drawPath(path, paint);
255 static bool rect_exceeds(const SkRect& r, SkScalar v) {
256 return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v ||
257 r.width() > v || r.height() > v;
260 #include "SkMaskCache.h"
262 static bool copy_cacheddata_to_mask(SkCachedData* data, SkMask* mask) {
263 const size_t size = data->size();
264 SkASSERT(mask->computeTotalImageSize() <= size);
266 mask->fImage = SkMask::AllocImage(size);
268 memcpy(mask->fImage, data->data(), size);
274 static SkCachedData* copy_mask_to_cacheddata(const SkMask& mask) {
275 const size_t size = mask.computeTotalImageSize();
276 SkCachedData* data = SkResourceCache::NewCachedData(size);
278 memcpy(data->writable_data(), mask.fImage, size);
284 static bool find_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style,
285 SkBlurQuality quality, const SkRRect& rrect) {
286 SkAutoTUnref<SkCachedData> data(SkMaskCache::FindAndRef(sigma, style, quality, rrect, mask));
287 return data.get() && copy_cacheddata_to_mask(data, mask);
290 static void add_cached_rrect(const SkMask& mask, SkScalar sigma, SkBlurStyle style,
291 SkBlurQuality quality, const SkRRect& rrect) {
292 SkAutoTUnref<SkCachedData> data(copy_mask_to_cacheddata(mask));
294 SkMaskCache::Add(sigma, style, quality, rrect, mask, data);
298 static bool find_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style,
299 SkBlurQuality quality, const SkRect rects[], int count) {
300 SkAutoTUnref<SkCachedData> data(SkMaskCache::FindAndRef(sigma, style, quality, rects, count, mask));
301 return data.get() && copy_cacheddata_to_mask(data, mask);
304 static void add_cached_rects(const SkMask& mask, SkScalar sigma, SkBlurStyle style,
305 SkBlurQuality quality, const SkRect rects[], int count) {
306 SkAutoTUnref<SkCachedData> data(copy_mask_to_cacheddata(mask));
308 SkMaskCache::Add(sigma, style, quality, rects, count, mask, data);
312 #ifdef SK_IGNORE_FAST_RRECT_BLUR
313 SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticblurrrect", false, "Use the faster analytic blur approach for ninepatch rects" );
315 SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticblurrrect", true, "Use the faster analytic blur approach for ninepatch round rects" );
318 SkMaskFilter::FilterReturn
319 SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix,
320 const SkIRect& clipBounds,
321 NinePatch* patch) const {
322 SkASSERT(patch != NULL);
323 switch (rrect.getType()) {
324 case SkRRect::kUnknown_Type:
325 // Unknown should never be returned.
328 case SkRRect::kEmpty_Type:
330 return kFalse_FilterReturn;
332 case SkRRect::kRect_Type:
333 // We should have caught this earlier.
336 case SkRRect::kOval_Type:
337 // The nine patch special case does not handle ovals, and we
338 // already have code for rectangles.
339 return kUnimplemented_FilterReturn;
341 // These three can take advantage of this fast path.
342 case SkRRect::kSimple_Type:
343 case SkRRect::kNinePatch_Type:
344 case SkRRect::kComplex_Type:
348 // TODO: report correct metrics for innerstyle, where we do not grow the
349 // total bounds, but we do need an inset the size of our blur-radius
350 if (kInner_SkBlurStyle == fBlurStyle) {
351 return kUnimplemented_FilterReturn;
354 // TODO: take clipBounds into account to limit our coordinates up front
355 // for now, just skip too-large src rects (to take the old code path).
356 if (rect_exceeds(rrect.rect(), SkIntToScalar(32767))) {
357 return kUnimplemented_FilterReturn;
362 rrect.rect().roundOut(&srcM.fBounds);
364 srcM.fFormat = SkMask::kA8_Format;
367 bool filterResult = false;
368 if (c_analyticBlurRRect) {
369 // special case for fast round rect blur
370 // don't actually do the blur the first time, just compute the correct size
371 filterResult = this->filterRRectMask(&dstM, rrect, matrix, &margin,
372 SkMask::kJustComputeBounds_CreateMode);
376 filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
380 return kFalse_FilterReturn;
383 // Now figure out the appropriate width and height of the smaller round rectangle
384 // to stretch. It will take into account the larger radius per side as well as double
385 // the margin, to account for inner and outer blur.
386 const SkVector& UL = rrect.radii(SkRRect::kUpperLeft_Corner);
387 const SkVector& UR = rrect.radii(SkRRect::kUpperRight_Corner);
388 const SkVector& LR = rrect.radii(SkRRect::kLowerRight_Corner);
389 const SkVector& LL = rrect.radii(SkRRect::kLowerLeft_Corner);
391 const SkScalar leftUnstretched = SkTMax(UL.fX, LL.fX) + SkIntToScalar(2 * margin.fX);
392 const SkScalar rightUnstretched = SkTMax(UR.fX, LR.fX) + SkIntToScalar(2 * margin.fX);
394 // Extra space in the middle to ensure an unchanging piece for stretching. Use 3 to cover
395 // any fractional space on either side plus 1 for the part to stretch.
396 const SkScalar stretchSize = SkIntToScalar(3);
398 const SkScalar totalSmallWidth = leftUnstretched + rightUnstretched + stretchSize;
399 if (totalSmallWidth >= rrect.rect().width()) {
400 // There is no valid piece to stretch.
401 return kUnimplemented_FilterReturn;
404 const SkScalar topUnstretched = SkTMax(UL.fY, UR.fY) + SkIntToScalar(2 * margin.fY);
405 const SkScalar bottomUnstretched = SkTMax(LL.fY, LR.fY) + SkIntToScalar(2 * margin.fY);
407 const SkScalar totalSmallHeight = topUnstretched + bottomUnstretched + stretchSize;
408 if (totalSmallHeight >= rrect.rect().height()) {
409 // There is no valid piece to stretch.
410 return kUnimplemented_FilterReturn;
413 SkRect smallR = SkRect::MakeWH(totalSmallWidth, totalSmallHeight);
417 radii[SkRRect::kUpperLeft_Corner] = UL;
418 radii[SkRRect::kUpperRight_Corner] = UR;
419 radii[SkRRect::kLowerRight_Corner] = LR;
420 radii[SkRRect::kLowerLeft_Corner] = LL;
421 smallRR.setRectRadii(smallR, radii);
423 const SkScalar sigma = this->computeXformedSigma(matrix);
424 if (!find_cached_rrect(&patch->fMask, sigma, fBlurStyle, this->getQuality(), smallRR)) {
425 bool analyticBlurWorked = false;
426 if (c_analyticBlurRRect) {
428 this->filterRRectMask(&patch->fMask, smallRR, matrix, &margin,
429 SkMask::kComputeBoundsAndRenderImage_CreateMode);
432 if (!analyticBlurWorked) {
433 if (!draw_rrect_into_mask(smallRR, &srcM)) {
434 return kFalse_FilterReturn;
437 SkAutoMaskFreeImage amf(srcM.fImage);
439 if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
440 return kFalse_FilterReturn;
443 add_cached_rrect(patch->fMask, sigma, fBlurStyle, this->getQuality(), smallRR);
446 patch->fMask.fBounds.offsetTo(0, 0);
447 patch->fOuterRect = dstM.fBounds;
448 patch->fCenter.fX = SkScalarCeilToInt(leftUnstretched) + 1;
449 patch->fCenter.fY = SkScalarCeilToInt(topUnstretched) + 1;
450 return kTrue_FilterReturn;
453 SK_CONF_DECLARE( bool, c_analyticBlurNinepatch, "mask.filter.analyticNinePatch", true, "Use the faster analytic blur approach for ninepatch rects" );
455 SkMaskFilter::FilterReturn
456 SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
457 const SkMatrix& matrix,
458 const SkIRect& clipBounds,
459 NinePatch* patch) const {
460 if (count < 1 || count > 2) {
461 return kUnimplemented_FilterReturn;
464 // TODO: report correct metrics for innerstyle, where we do not grow the
465 // total bounds, but we do need an inset the size of our blur-radius
466 if (kInner_SkBlurStyle == fBlurStyle || kOuter_SkBlurStyle == fBlurStyle) {
467 return kUnimplemented_FilterReturn;
470 // TODO: take clipBounds into account to limit our coordinates up front
471 // for now, just skip too-large src rects (to take the old code path).
472 if (rect_exceeds(rects[0], SkIntToScalar(32767))) {
473 return kUnimplemented_FilterReturn;
478 rects[0].roundOut(&srcM.fBounds);
480 srcM.fFormat = SkMask::kA8_Format;
483 bool filterResult = false;
484 if (count == 1 && c_analyticBlurNinepatch) {
485 // special case for fast rect blur
486 // don't actually do the blur the first time, just compute the correct size
487 filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin,
488 SkMask::kJustComputeBounds_CreateMode);
490 filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
494 return kFalse_FilterReturn;
498 * smallR is the smallest version of 'rect' that will still guarantee that
499 * we get the same blur results on all edges, plus 1 center row/col that is
500 * representative of the extendible/stretchable edges of the ninepatch.
501 * Since our actual edge may be fractional we inset 1 more to be sure we
502 * don't miss any interior blur.
503 * x is an added pixel of blur, and { and } are the (fractional) edge
504 * pixels from the original rect.
506 * x x { x x .... x x } x x
508 * Thus, in this case, we inset by a total of 5 (on each side) beginning
509 * with our outer-rect (dstM.fBounds)
514 // +2 is from +1 for each edge (to account for possible fractional edges
515 int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2;
516 int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2;
520 innerIR = srcM.fBounds;
521 center.set(smallW, smallH);
523 SkASSERT(2 == count);
524 rects[1].roundIn(&innerIR);
525 center.set(smallW + (innerIR.left() - srcM.fBounds.left()),
526 smallH + (innerIR.top() - srcM.fBounds.top()));
529 // +1 so we get a clean, stretchable, center row/col
533 // we want the inset amounts to be integral, so we don't change any
534 // fractional phase on the fRight or fBottom of our smallR.
535 const SkScalar dx = SkIntToScalar(innerIR.width() - smallW);
536 const SkScalar dy = SkIntToScalar(innerIR.height() - smallH);
537 if (dx < 0 || dy < 0) {
538 // we're too small, relative to our blur, to break into nine-patch,
539 // so we ask to have our normal filterMask() be called.
540 return kUnimplemented_FilterReturn;
543 smallR[0].set(rects[0].left(), rects[0].top(), rects[0].right() - dx, rects[0].bottom() - dy);
544 if (smallR[0].width() < 2 || smallR[0].height() < 2) {
545 return kUnimplemented_FilterReturn;
548 smallR[1].set(rects[1].left(), rects[1].top(),
549 rects[1].right() - dx, rects[1].bottom() - dy);
550 SkASSERT(!smallR[1].isEmpty());
553 const SkScalar sigma = this->computeXformedSigma(matrix);
554 if (!find_cached_rects(&patch->fMask, sigma, fBlurStyle, this->getQuality(), rects, count)) {
555 if (count > 1 || !c_analyticBlurNinepatch) {
556 if (!draw_rects_into_mask(smallR, count, &srcM)) {
557 return kFalse_FilterReturn;
560 SkAutoMaskFreeImage amf(srcM.fImage);
562 if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
563 return kFalse_FilterReturn;
566 if (!this->filterRectMask(&patch->fMask, smallR[0], matrix, &margin,
567 SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
568 return kFalse_FilterReturn;
571 add_cached_rects(patch->fMask, sigma, fBlurStyle, this->getQuality(), rects, count);
573 patch->fMask.fBounds.offsetTo(0, 0);
574 patch->fOuterRect = dstM.fBounds;
575 patch->fCenter = center;
576 return kTrue_FilterReturn;
579 void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src,
581 SkScalar pad = 3.0f * fSigma;
583 dst->set(src.fLeft - pad, src.fTop - pad,
584 src.fRight + pad, src.fBottom + pad);
587 #ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
588 SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkReadBuffer& buffer) : SkMaskFilter(buffer) {
589 fSigma = buffer.readScalar();
590 fBlurStyle = (SkBlurStyle)buffer.readInt();
591 fBlurFlags = buffer.readUInt() & SkBlurMaskFilter::kAll_BlurFlag;
592 SkASSERT(fSigma > 0);
593 SkASSERT((unsigned)fBlurStyle <= kLastEnum_SkBlurStyle);
597 SkFlattenable* SkBlurMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
598 const SkScalar sigma = buffer.readScalar();
599 const unsigned style = buffer.readUInt();
600 const unsigned flags = buffer.readUInt();
601 if (style <= kLastEnum_SkBlurStyle) {
602 return SkBlurMaskFilter::Create((SkBlurStyle)style, sigma, flags);
607 void SkBlurMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
608 buffer.writeScalar(fSigma);
609 buffer.writeUInt(fBlurStyle);
610 buffer.writeUInt(fBlurFlags);
615 class GrGLRectBlurEffect;
617 class GrRectBlurEffect : public GrFragmentProcessor {
619 virtual ~GrRectBlurEffect();
621 static const char* Name() { return "RectBlur"; }
623 typedef GrGLRectBlurEffect GLProcessor;
625 virtual const GrBackendFragmentProcessorFactory& getFactory() const SK_OVERRIDE;
627 * Create a simple filter effect with custom bicubic coefficients.
629 static GrFragmentProcessor* Create(GrContext *context, const SkRect& rect, float sigma) {
630 GrTexture *blurProfileTexture = NULL;
631 int doubleProfileSize = SkScalarCeilToInt(12*sigma);
633 if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) {
634 // if the blur sigma is too large so the gaussian overlaps the whole
635 // rect in either direction, fall back to CPU path for now.
640 bool createdBlurProfileTexture = CreateBlurProfileTexture(context, sigma, &blurProfileTexture);
641 SkAutoTUnref<GrTexture> hunref(blurProfileTexture);
642 if (!createdBlurProfileTexture) {
645 return SkNEW_ARGS(GrRectBlurEffect, (rect, sigma, blurProfileTexture));
648 const SkRect& getRect() const { return fRect; }
649 float getSigma() const { return fSigma; }
652 GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blur_profile);
653 virtual bool onIsEqual(const GrFragmentProcessor&) const SK_OVERRIDE;
655 virtual void onComputeInvariantOutput(InvariantOutput* inout) const SK_OVERRIDE;
657 static bool CreateBlurProfileTexture(GrContext *context, float sigma,
658 GrTexture **blurProfileTexture);
662 GrTextureAccess fBlurProfileAccess;
664 GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
666 typedef GrFragmentProcessor INHERITED;
669 class GrGLRectBlurEffect : public GrGLFragmentProcessor {
671 GrGLRectBlurEffect(const GrBackendProcessorFactory& factory,
673 virtual void emitCode(GrGLFPBuilder*,
674 const GrFragmentProcessor&,
675 const GrProcessorKey&,
676 const char* outputColor,
677 const char* inputColor,
678 const TransformedCoordsArray&,
679 const TextureSamplerArray&) SK_OVERRIDE;
681 virtual void setData(const GrGLProgramDataManager&, const GrProcessor&) SK_OVERRIDE;
684 typedef GrGLProgramDataManager::UniformHandle UniformHandle;
686 UniformHandle fProxyRectUniform;
687 UniformHandle fProfileSizeUniform;
689 typedef GrGLFragmentProcessor INHERITED;
694 GrGLRectBlurEffect::GrGLRectBlurEffect(const GrBackendProcessorFactory& factory, const GrProcessor&)
695 : INHERITED(factory) {
698 void OutputRectBlurProfileLookup(GrGLFPFragmentBuilder* fsBuilder,
699 const GrGLShaderBuilder::TextureSampler& sampler,
701 const char *profileSize, const char *loc,
702 const char *blurred_width,
703 const char *sharp_width) {
704 fsBuilder->codeAppendf("\tfloat %s;\n", output);
705 fsBuilder->codeAppendf("\t\t{\n");
706 fsBuilder->codeAppendf("\t\t\tfloat coord = (0.5 * (abs(2.0*%s - %s) - %s))/%s;\n",
707 loc, blurred_width, sharp_width, profileSize);
708 fsBuilder->codeAppendf("\t\t\t%s = ", output);
709 fsBuilder->appendTextureLookup(sampler, "vec2(coord,0.5)");
710 fsBuilder->codeAppend(".a;\n");
711 fsBuilder->codeAppendf("\t\t}\n");
714 void GrGLRectBlurEffect::emitCode(GrGLFPBuilder* builder,
715 const GrFragmentProcessor&,
716 const GrProcessorKey& key,
717 const char* outputColor,
718 const char* inputColor,
719 const TransformedCoordsArray& coords,
720 const TextureSamplerArray& samplers) {
722 const char *rectName;
723 const char *profileSizeName;
725 fProxyRectUniform = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
729 fProfileSizeUniform = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
734 GrGLFPFragmentBuilder* fsBuilder = builder->getFragmentShaderBuilder();
735 const char *fragmentPos = fsBuilder->fragmentPosition();
738 fsBuilder->codeAppendf("\tvec4 src=%s;\n", inputColor);
740 fsBuilder->codeAppendf("\tvec4 src=vec4(1)\n;");
743 fsBuilder->codeAppendf("\tvec2 translatedPos = %s.xy - %s.xy;\n", fragmentPos, rectName );
744 fsBuilder->codeAppendf("\tfloat width = %s.z - %s.x;\n", rectName, rectName);
745 fsBuilder->codeAppendf("\tfloat height = %s.w - %s.y;\n", rectName, rectName);
747 fsBuilder->codeAppendf("\tvec2 smallDims = vec2(width - %s, height-%s);\n", profileSizeName, profileSizeName);
748 fsBuilder->codeAppendf("\tfloat center = 2.0 * floor(%s/2.0 + .25) - 1.0;\n", profileSizeName);
749 fsBuilder->codeAppendf("\tvec2 wh = smallDims - vec2(center,center);\n");
751 OutputRectBlurProfileLookup(fsBuilder, samplers[0], "horiz_lookup", profileSizeName, "translatedPos.x", "width", "wh.x");
752 OutputRectBlurProfileLookup(fsBuilder, samplers[0], "vert_lookup", profileSizeName, "translatedPos.y", "height", "wh.y");
754 fsBuilder->codeAppendf("\tfloat final = horiz_lookup * vert_lookup;\n");
755 fsBuilder->codeAppendf("\t%s = src * final;\n", outputColor );
758 void GrGLRectBlurEffect::setData(const GrGLProgramDataManager& pdman,
759 const GrProcessor& proc) {
760 const GrRectBlurEffect& rbe = proc.cast<GrRectBlurEffect>();
761 SkRect rect = rbe.getRect();
763 pdman.set4f(fProxyRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
764 pdman.set1f(fProfileSizeUniform, SkScalarCeilToScalar(6*rbe.getSigma()));
767 bool GrRectBlurEffect::CreateBlurProfileTexture(GrContext *context, float sigma,
768 GrTexture **blurProfileTexture) {
769 GrTextureParams params;
770 GrSurfaceDesc texDesc;
772 unsigned int profile_size = SkScalarCeilToInt(6*sigma);
774 texDesc.fWidth = profile_size;
776 texDesc.fConfig = kAlpha_8_GrPixelConfig;
778 static const GrCacheID::Domain gBlurProfileDomain = GrCacheID::GenerateDomain();
780 memset(&key, 0, sizeof(key));
781 key.fData32[0] = profile_size;
783 GrCacheID blurProfileKey(gBlurProfileDomain, key);
785 uint8_t *profile = NULL;
786 SkAutoTDeleteArray<uint8_t> ada(NULL);
788 *blurProfileTexture = context->findAndRefTexture(texDesc, blurProfileKey, ¶ms);
790 if (NULL == *blurProfileTexture) {
792 SkBlurMask::ComputeBlurProfile(sigma, &profile);
795 *blurProfileTexture = context->createTexture(¶ms, texDesc, blurProfileKey,
798 if (NULL == *blurProfileTexture) {
806 GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma,
807 GrTexture *blur_profile)
811 fBlurProfileAccess(blur_profile) {
812 this->addTextureAccess(&fBlurProfileAccess);
813 this->setWillReadFragmentPosition();
816 GrRectBlurEffect::~GrRectBlurEffect() {
819 const GrBackendFragmentProcessorFactory& GrRectBlurEffect::getFactory() const {
820 return GrTBackendFragmentProcessorFactory<GrRectBlurEffect>::getInstance();
823 bool GrRectBlurEffect::onIsEqual(const GrFragmentProcessor& sBase) const {
824 const GrRectBlurEffect& s = sBase.cast<GrRectBlurEffect>();
825 return this->getSigma() == s.getSigma() && this->getRect() == s.getRect();
828 void GrRectBlurEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
829 inout->mulByUnknownAlpha();
832 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRectBlurEffect);
834 GrFragmentProcessor* GrRectBlurEffect::TestCreate(SkRandom* random,
836 const GrDrawTargetCaps&,
838 float sigma = random->nextRangeF(3,8);
839 float width = random->nextRangeF(200,300);
840 float height = random->nextRangeF(200,300);
841 return GrRectBlurEffect::Create(context, SkRect::MakeWH(width, height), sigma);
845 bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrContext* context,
847 const SkStrokeRec& strokeRec,
848 const SkPath& path) const {
849 if (fBlurStyle != kNormal_SkBlurStyle) {
854 if (!path.isRect(&rect)) {
858 if (!strokeRec.isFillStyle()) {
862 SkMatrix ctm = context->getMatrix();
863 SkScalar xformedSigma = this->computeXformedSigma(ctm);
865 int pad=SkScalarCeilToInt(6*xformedSigma)/2;
866 rect.outset(SkIntToScalar(pad), SkIntToScalar(pad));
868 SkAutoTUnref<GrFragmentProcessor> fp(GrRectBlurEffect::Create(context, rect, xformedSigma));
873 GrContext::AutoMatrix am;
874 if (!am.setIdentity(context, grp)) {
878 grp->addCoverageProcessor(fp);
880 context->drawRect(*grp, rect);
884 class GrGLRRectBlurEffect;
886 class GrRRectBlurEffect : public GrFragmentProcessor {
889 static GrFragmentProcessor* Create(GrContext* context, float sigma, const SkRRect&);
891 virtual ~GrRRectBlurEffect() {};
892 static const char* Name() { return "GrRRectBlur"; }
894 const SkRRect& getRRect() const { return fRRect; }
895 float getSigma() const { return fSigma; }
897 typedef GrGLRRectBlurEffect GLProcessor;
899 virtual const GrBackendFragmentProcessorFactory& getFactory() const SK_OVERRIDE;
902 GrRRectBlurEffect(float sigma, const SkRRect&, GrTexture* profileTexture);
904 virtual bool onIsEqual(const GrFragmentProcessor& other) const SK_OVERRIDE;
906 virtual void onComputeInvariantOutput(InvariantOutput* inout) const SK_OVERRIDE;
910 GrTextureAccess fNinePatchAccess;
912 GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
914 typedef GrFragmentProcessor INHERITED;
918 GrFragmentProcessor* GrRRectBlurEffect::Create(GrContext* context, float sigma,
919 const SkRRect& rrect) {
920 if (!rrect.isSimpleCircular()) {
924 // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
925 // sufficiently small relative to both the size of the corner radius and the
926 // width (and height) of the rrect.
928 unsigned int blurRadius = 3*SkScalarCeilToInt(sigma-1/6.0f);
929 unsigned int cornerRadius = SkScalarCeilToInt(rrect.getSimpleRadii().x());
930 if (cornerRadius + blurRadius > rrect.width()/2 ||
931 cornerRadius + blurRadius > rrect.height()/2) {
935 static const GrCacheID::Domain gRRectBlurDomain = GrCacheID::GenerateDomain();
937 memset(&key, 0, sizeof(key));
938 key.fData32[0] = blurRadius;
939 key.fData32[1] = cornerRadius;
940 GrCacheID blurRRectNinePatchID(gRRectBlurDomain, key);
942 GrTextureParams params;
943 params.setFilterMode(GrTextureParams::kBilerp_FilterMode);
945 unsigned int smallRectSide = 2*(blurRadius + cornerRadius) + 1;
946 unsigned int texSide = smallRectSide + 2*blurRadius;
947 GrSurfaceDesc texDesc;
948 texDesc.fWidth = texSide;
949 texDesc.fHeight = texSide;
950 texDesc.fConfig = kAlpha_8_GrPixelConfig;
952 GrTexture *blurNinePatchTexture = context->findAndRefTexture(texDesc, blurRRectNinePatchID, ¶ms);
954 if (NULL == blurNinePatchTexture) {
957 mask.fBounds = SkIRect::MakeWH(smallRectSide, smallRectSide);
958 mask.fFormat = SkMask::kA8_Format;
959 mask.fRowBytes = mask.fBounds.width();
960 mask.fImage = SkMask::AllocImage(mask.computeTotalImageSize());
961 SkAutoMaskFreeImage amfi(mask.fImage);
963 memset(mask.fImage, 0, mask.computeTotalImageSize());
966 smallRect.setWH(SkIntToScalar(smallRectSide), SkIntToScalar(smallRectSide));
969 smallRRect.setRectXY(smallRect, SkIntToScalar(cornerRadius), SkIntToScalar(cornerRadius));
972 path.addRRect( smallRRect );
974 SkDraw::DrawToMask(path, &mask.fBounds, NULL, NULL, &mask, SkMask::kJustRenderImage_CreateMode, SkPaint::kFill_Style);
977 SkBlurMask::BoxBlur(&blurred_mask, mask, sigma, kNormal_SkBlurStyle, kHigh_SkBlurQuality, NULL, true );
979 blurNinePatchTexture = context->createTexture(¶ms, texDesc, blurRRectNinePatchID, blurred_mask.fImage, 0);
980 SkMask::FreeImage(blurred_mask.fImage);
983 SkAutoTUnref<GrTexture> blurunref(blurNinePatchTexture);
984 if (NULL == blurNinePatchTexture) {
988 return SkNEW_ARGS(GrRRectBlurEffect, (sigma, rrect, blurNinePatchTexture));
991 void GrRRectBlurEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
992 inout->mulByUnknownAlpha();
995 const GrBackendFragmentProcessorFactory& GrRRectBlurEffect::getFactory() const {
996 return GrTBackendFragmentProcessorFactory<GrRRectBlurEffect>::getInstance();
999 GrRRectBlurEffect::GrRRectBlurEffect(float sigma, const SkRRect& rrect, GrTexture *ninePatchTexture)
1002 fNinePatchAccess(ninePatchTexture) {
1003 this->addTextureAccess(&fNinePatchAccess);
1004 this->setWillReadFragmentPosition();
1007 bool GrRRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
1008 const GrRRectBlurEffect& rrbe = other.cast<GrRRectBlurEffect>();
1009 return fRRect.getSimpleRadii().fX == rrbe.fRRect.getSimpleRadii().fX && fSigma == rrbe.fSigma;
1012 //////////////////////////////////////////////////////////////////////////////
1014 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRRectBlurEffect);
1016 GrFragmentProcessor* GrRRectBlurEffect::TestCreate(SkRandom* random,
1018 const GrDrawTargetCaps& caps,
1020 SkScalar w = random->nextRangeScalar(100.f, 1000.f);
1021 SkScalar h = random->nextRangeScalar(100.f, 1000.f);
1022 SkScalar r = random->nextRangeF(1.f, 9.f);
1023 SkScalar sigma = random->nextRangeF(1.f,10.f);
1025 rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
1026 return GrRRectBlurEffect::Create(context, sigma, rrect);
1029 //////////////////////////////////////////////////////////////////////////////
1031 class GrGLRRectBlurEffect : public GrGLFragmentProcessor {
1033 GrGLRRectBlurEffect(const GrBackendProcessorFactory&, const GrProcessor&);
1035 virtual void emitCode(GrGLFPBuilder*,
1036 const GrFragmentProcessor&,
1037 const GrProcessorKey&,
1038 const char* outputColor,
1039 const char* inputColor,
1040 const TransformedCoordsArray&,
1041 const TextureSamplerArray&) SK_OVERRIDE;
1043 virtual void setData(const GrGLProgramDataManager&, const GrProcessor&) SK_OVERRIDE;
1046 GrGLProgramDataManager::UniformHandle fProxyRectUniform;
1047 GrGLProgramDataManager::UniformHandle fCornerRadiusUniform;
1048 GrGLProgramDataManager::UniformHandle fBlurRadiusUniform;
1049 typedef GrGLFragmentProcessor INHERITED;
1052 GrGLRRectBlurEffect::GrGLRRectBlurEffect(const GrBackendProcessorFactory& factory,
1054 : INHERITED (factory) {
1057 void GrGLRRectBlurEffect::emitCode(GrGLFPBuilder* builder,
1058 const GrFragmentProcessor&,
1059 const GrProcessorKey&,
1060 const char* outputColor,
1061 const char* inputColor,
1062 const TransformedCoordsArray&,
1063 const TextureSamplerArray& samplers) {
1064 const char *rectName;
1065 const char *cornerRadiusName;
1066 const char *blurRadiusName;
1068 // The proxy rect has left, top, right, and bottom edges correspond to
1069 // components x, y, z, and w, respectively.
1071 fProxyRectUniform = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
1075 fCornerRadiusUniform = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
1079 fBlurRadiusUniform = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
1084 GrGLFPFragmentBuilder* fsBuilder = builder->getFragmentShaderBuilder();
1085 const char* fragmentPos = fsBuilder->fragmentPosition();
1087 // warp the fragment position to the appropriate part of the 9patch blur texture
1089 fsBuilder->codeAppendf("\t\tvec2 rectCenter = (%s.xy + %s.zw)/2.0;\n", rectName, rectName);
1090 fsBuilder->codeAppendf("\t\tvec2 translatedFragPos = %s.xy - %s.xy;\n", fragmentPos, rectName);
1091 fsBuilder->codeAppendf("\t\tfloat threshold = %s + 2.0*%s;\n", cornerRadiusName, blurRadiusName );
1092 fsBuilder->codeAppendf("\t\tvec2 middle = %s.zw - %s.xy - 2.0*threshold;\n", rectName, rectName );
1094 fsBuilder->codeAppendf("\t\tif (translatedFragPos.x >= threshold && translatedFragPos.x < (middle.x+threshold)) {\n" );
1095 fsBuilder->codeAppendf("\t\t\ttranslatedFragPos.x = threshold;\n");
1096 fsBuilder->codeAppendf("\t\t} else if (translatedFragPos.x >= (middle.x + threshold)) {\n");
1097 fsBuilder->codeAppendf("\t\t\ttranslatedFragPos.x -= middle.x - 1.0;\n");
1098 fsBuilder->codeAppendf("\t\t}\n");
1100 fsBuilder->codeAppendf("\t\tif (translatedFragPos.y > threshold && translatedFragPos.y < (middle.y+threshold)) {\n" );
1101 fsBuilder->codeAppendf("\t\t\ttranslatedFragPos.y = threshold;\n");
1102 fsBuilder->codeAppendf("\t\t} else if (translatedFragPos.y >= (middle.y + threshold)) {\n");
1103 fsBuilder->codeAppendf("\t\t\ttranslatedFragPos.y -= middle.y - 1.0;\n");
1104 fsBuilder->codeAppendf("\t\t}\n");
1106 fsBuilder->codeAppendf("\t\tvec2 proxyDims = vec2(2.0*threshold+1.0);\n");
1107 fsBuilder->codeAppendf("\t\tvec2 texCoord = translatedFragPos / proxyDims;\n");
1109 fsBuilder->codeAppendf("\t%s = ", outputColor);
1110 fsBuilder->appendTextureLookupAndModulate(inputColor, samplers[0], "texCoord");
1111 fsBuilder->codeAppend(";\n");
1114 void GrGLRRectBlurEffect::setData(const GrGLProgramDataManager& pdman,
1115 const GrProcessor& proc) {
1116 const GrRRectBlurEffect& brre = proc.cast<GrRRectBlurEffect>();
1117 SkRRect rrect = brre.getRRect();
1119 float blurRadius = 3.f*SkScalarCeilToScalar(brre.getSigma()-1/6.0f);
1120 pdman.set1f(fBlurRadiusUniform, blurRadius);
1122 SkRect rect = rrect.getBounds();
1123 rect.outset(blurRadius, blurRadius);
1124 pdman.set4f(fProxyRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
1126 SkScalar radius = 0;
1127 SkASSERT(rrect.isSimpleCircular() || rrect.isRect());
1128 radius = rrect.getSimpleRadii().fX;
1129 pdman.set1f(fCornerRadiusUniform, radius);
1133 bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrContext* context,
1135 const SkStrokeRec& strokeRec,
1136 const SkRRect& rrect) const {
1137 if (fBlurStyle != kNormal_SkBlurStyle) {
1141 if (!strokeRec.isFillStyle()) {
1145 SkRect proxy_rect = rrect.rect();
1146 SkMatrix ctm = context->getMatrix();
1147 SkScalar xformedSigma = this->computeXformedSigma(ctm);
1148 float extra=3.f*SkScalarCeilToScalar(xformedSigma-1/6.0f);
1149 proxy_rect.outset(extra, extra);
1151 SkAutoTUnref<GrFragmentProcessor> fp(GrRRectBlurEffect::Create(context, xformedSigma, rrect));
1156 GrContext::AutoMatrix am;
1157 if (!am.setIdentity(context, grp)) {
1161 grp->addCoverageProcessor(fp);
1163 context->drawRect(*grp, proxy_rect);
1167 bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
1168 const SkIRect& clipBounds,
1169 const SkMatrix& ctm,
1170 SkRect* maskRect) const {
1171 SkScalar xformedSigma = this->computeXformedSigma(ctm);
1172 if (xformedSigma <= 0) {
1176 static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64);
1177 static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32);
1179 if (srcBounds.width() <= kMIN_GPU_BLUR_SIZE &&
1180 srcBounds.height() <= kMIN_GPU_BLUR_SIZE &&
1181 xformedSigma <= kMIN_GPU_BLUR_SIGMA) {
1182 // We prefer to blur small rect with small radius via CPU.
1186 if (NULL == maskRect) {
1187 // don't need to compute maskRect
1191 float sigma3 = 3 * SkScalarToFloat(xformedSigma);
1193 SkRect clipRect = SkRect::Make(clipBounds);
1194 SkRect srcRect(srcBounds);
1196 // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
1197 srcRect.outset(sigma3, sigma3);
1198 clipRect.outset(sigma3, sigma3);
1199 srcRect.intersect(clipRect);
1200 *maskRect = srcRect;
1204 bool SkBlurMaskFilterImpl::filterMaskGPU(GrTexture* src,
1205 const SkMatrix& ctm,
1206 const SkRect& maskRect,
1208 bool canOverwriteSrc) const {
1209 SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height());
1211 GrContext* context = src->getContext();
1213 GrContext::AutoWideOpenIdentityDraw awo(context, NULL);
1215 SkScalar xformedSigma = this->computeXformedSigma(ctm);
1216 SkASSERT(xformedSigma > 0);
1218 // If we're doing a normal blur, we can clobber the pathTexture in the
1219 // gaussianBlur. Otherwise, we need to save it for later compositing.
1220 bool isNormalBlur = (kNormal_SkBlurStyle == fBlurStyle);
1221 *result = SkGpuBlurUtils::GaussianBlur(context, src, isNormalBlur && canOverwriteSrc,
1222 clipRect, false, xformedSigma, xformedSigma);
1223 if (NULL == *result) {
1227 if (!isNormalBlur) {
1228 context->setIdentityMatrix();
1231 matrix.setIDiv(src->width(), src->height());
1232 // Blend pathTexture over blurTexture.
1233 GrContext::AutoRenderTarget art(context, (*result)->asRenderTarget());
1234 paint.addColorProcessor(GrSimpleTextureEffect::Create(src, matrix))->unref();
1235 if (kInner_SkBlurStyle == fBlurStyle) {
1236 // inner: dst = dst * src
1237 paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff);
1238 } else if (kSolid_SkBlurStyle == fBlurStyle) {
1239 // solid: dst = src + dst - src * dst
1240 // = (1 - dst) * src + 1 * dst
1241 paint.setBlendFunc(kIDC_GrBlendCoeff, kOne_GrBlendCoeff);
1242 } else if (kOuter_SkBlurStyle == fBlurStyle) {
1243 // outer: dst = dst * (1 - src)
1244 // = 0 * src + (1 - src) * dst
1245 paint.setBlendFunc(kZero_GrBlendCoeff, kISC_GrBlendCoeff);
1247 context->drawRect(paint, clipRect);
1253 #endif // SK_SUPPORT_GPU
1256 #ifndef SK_IGNORE_TO_STRING
1257 void SkBlurMaskFilterImpl::toString(SkString* str) const {
1258 str->append("SkBlurMaskFilterImpl: (");
1260 str->append("sigma: ");
1261 str->appendScalar(fSigma);
1264 static const char* gStyleName[kLastEnum_SkBlurStyle + 1] = {
1265 "normal", "solid", "outer", "inner"
1268 str->appendf("style: %s ", gStyleName[fBlurStyle]);
1269 str->append("flags: (");
1271 bool needSeparator = false;
1272 SkAddFlagToString(str,
1273 SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag),
1274 "IgnoreXform", &needSeparator);
1275 SkAddFlagToString(str,
1276 SkToBool(fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag),
1277 "HighQuality", &needSeparator);
1279 str->append("None");
1285 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkBlurMaskFilter)
1286 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurMaskFilterImpl)
1287 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END