Home | History | Annotate | Download | only in effects
      1 /*
      2  * Copyright 2006 The Android Open Source Project
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkBlurMaskFilter.h"
      9 #include "SkBlurMask.h"
     10 #include "SkGpuBlurUtils.h"
     11 #include "SkMaskFilterBase.h"
     12 #include "SkReadBuffer.h"
     13 #include "SkWriteBuffer.h"
     14 #include "SkMaskFilter.h"
     15 #include "SkRRect.h"
     16 #include "SkStringUtils.h"
     17 #include "SkStrokeRec.h"
     18 #include "SkVertices.h"
     19 
     20 #if SK_SUPPORT_GPU
     21 #include "GrCircleBlurFragmentProcessor.h"
     22 #include "GrClip.h"
     23 #include "GrContext.h"
     24 #include "GrFragmentProcessor.h"
     25 #include "GrRenderTargetContext.h"
     26 #include "GrResourceProvider.h"
     27 #include "GrShaderCaps.h"
     28 #include "GrStyle.h"
     29 #include "GrTextureProxy.h"
     30 #include "effects/GrRectBlurEffect.h"
     31 #include "effects/GrRRectBlurEffect.h"
     32 #include "effects/GrSimpleTextureEffect.h"
     33 #include "effects/GrTextureDomain.h"
     34 #include "glsl/GrGLSLFragmentProcessor.h"
     35 #include "glsl/GrGLSLFragmentShaderBuilder.h"
     36 #include "glsl/GrGLSLProgramDataManager.h"
     37 #include "glsl/GrGLSLUniformHandler.h"
     38 #endif
     39 
     40 SkScalar SkBlurMaskFilter::ConvertRadiusToSigma(SkScalar radius) {
     41     return SkBlurMask::ConvertRadiusToSigma(radius);
     42 }
     43 
     44 class SkBlurMaskFilterImpl : public SkMaskFilterBase {
     45 public:
     46     SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle, const SkRect& occluder, uint32_t flags);
     47 
     48     // overrides from SkMaskFilter
     49     SkMask::Format getFormat() const override;
     50     bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
     51                     SkIPoint* margin) const override;
     52 
     53 #if SK_SUPPORT_GPU
     54     bool canFilterMaskGPU(const SkRRect& devRRect,
     55                           const SkIRect& clipBounds,
     56                           const SkMatrix& ctm,
     57                           SkRect* maskRect) const override;
     58     bool directFilterMaskGPU(GrContext*,
     59                              GrRenderTargetContext* renderTargetContext,
     60                              GrPaint&&,
     61                              const GrClip&,
     62                              const SkMatrix& viewMatrix,
     63                              const SkStrokeRec& strokeRec,
     64                              const SkPath& path) const override;
     65     bool directFilterRRectMaskGPU(GrContext*,
     66                                   GrRenderTargetContext* renderTargetContext,
     67                                   GrPaint&&,
     68                                   const GrClip&,
     69                                   const SkMatrix& viewMatrix,
     70                                   const SkStrokeRec& strokeRec,
     71                                   const SkRRect& rrect,
     72                                   const SkRRect& devRRect) const override;
     73     sk_sp<GrTextureProxy> filterMaskGPU(GrContext*,
     74                                         sk_sp<GrTextureProxy> srcProxy,
     75                                         const SkMatrix& ctm,
     76                                         const SkIRect& maskRect) const override;
     77 #endif
     78 
     79     void computeFastBounds(const SkRect&, SkRect*) const override;
     80     bool asABlur(BlurRec*) const override;
     81 
     82     SK_TO_STRING_OVERRIDE()
     83     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurMaskFilterImpl)
     84 
     85 protected:
     86     FilterReturn filterRectsToNine(const SkRect[], int count, const SkMatrix&,
     87                                    const SkIRect& clipBounds,
     88                                    NinePatch*) const override;
     89 
     90     FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&,
     91                                    const SkIRect& clipBounds,
     92                                    NinePatch*) const override;
     93 
     94     bool filterRectMask(SkMask* dstM, const SkRect& r, const SkMatrix& matrix,
     95                         SkIPoint* margin, SkMask::CreateMode createMode) const;
     96     bool filterRRectMask(SkMask* dstM, const SkRRect& r, const SkMatrix& matrix,
     97                         SkIPoint* margin, SkMask::CreateMode createMode) const;
     98 
     99     bool ignoreXform() const {
    100         return SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag);
    101     }
    102 
    103 private:
    104     // To avoid unseemly allocation requests (esp. for finite platforms like
    105     // handset) we limit the radius so something manageable. (as opposed to
    106     // a request like 10,000)
    107     static const SkScalar kMAX_BLUR_SIGMA;
    108 
    109     SkScalar    fSigma;
    110     SkBlurStyle fBlurStyle;
    111     SkRect      fOccluder;
    112     uint32_t    fBlurFlags;
    113 
    114     SkBlurQuality getQuality() const {
    115         return (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ?
    116                 kHigh_SkBlurQuality : kLow_SkBlurQuality;
    117     }
    118 
    119     SkBlurMaskFilterImpl(SkReadBuffer&);
    120     void flatten(SkWriteBuffer&) const override;
    121 
    122     SkScalar computeXformedSigma(const SkMatrix& ctm) const {
    123         SkScalar xformedSigma = this->ignoreXform() ? fSigma : ctm.mapRadius(fSigma);
    124         return SkMinScalar(xformedSigma, kMAX_BLUR_SIGMA);
    125     }
    126 
    127     friend class SkBlurMaskFilter;
    128 
    129     typedef SkMaskFilter INHERITED;
    130 };
    131 
    132 const SkScalar SkBlurMaskFilterImpl::kMAX_BLUR_SIGMA = SkIntToScalar(128);
    133 
    134 sk_sp<SkMaskFilter> SkBlurMaskFilter::Make(SkBlurStyle style, SkScalar sigma,
    135                                            const SkRect& occluder, uint32_t flags) {
    136     SkASSERT(!(flags & ~SkBlurMaskFilter::kAll_BlurFlag));
    137     SkASSERT(style <= kLastEnum_SkBlurStyle);
    138 
    139     if (!SkScalarIsFinite(sigma) || sigma <= 0) {
    140         return nullptr;
    141     }
    142 
    143     return sk_sp<SkMaskFilter>(new SkBlurMaskFilterImpl(sigma, style, occluder, flags));
    144 }
    145 
    146 // linearly interpolate between y1 & y3 to match x2's position between x1 & x3
    147 static SkScalar interp(SkScalar x1, SkScalar x2, SkScalar x3, SkScalar y1, SkScalar y3) {
    148     SkASSERT(x1 <= x2 && x2 <= x3);
    149     SkASSERT(y1 <= y3);
    150 
    151     SkScalar t = (x2 - x1) / (x3 - x1);
    152     return y1 + t * (y3 - y1);
    153 }
    154 
    155 // Insert 'lower' and 'higher' into 'array1' and insert a new value at each matching insertion
    156 // point in 'array2' that linearly interpolates between the existing values.
    157 // Return a bit mask which contains a copy of 'inputMask' for all the cells between the two
    158 // insertion points.
    159 static uint32_t insert_into_arrays(SkScalar* array1, SkScalar* array2,
    160                                    SkScalar lower, SkScalar higher,
    161                                    int* num, uint32_t inputMask, int maskSize) {
    162     SkASSERT(lower < higher);
    163     SkASSERT(lower >= array1[0] && higher <= array1[*num-1]);
    164 
    165     int32_t skipMask = 0x0;
    166     int i;
    167     for (i = 0; i < *num; ++i) {
    168         if (lower >= array1[i] && lower < array1[i+1]) {
    169             if (!SkScalarNearlyEqual(lower, array1[i])) {
    170                 memmove(&array1[i+2], &array1[i+1], (*num-i-1)*sizeof(SkScalar));
    171                 array1[i+1] = lower;
    172                 memmove(&array2[i+2], &array2[i+1], (*num-i-1)*sizeof(SkScalar));
    173                 array2[i+1] = interp(array1[i], lower, array1[i+2], array2[i], array2[i+2]);
    174                 i++;
    175                 (*num)++;
    176             }
    177             break;
    178         }
    179     }
    180     for ( ; i < *num; ++i) {
    181         skipMask |= inputMask << (i*maskSize);
    182         if (higher > array1[i] && higher <= array1[i+1]) {
    183             if (!SkScalarNearlyEqual(higher, array1[i+1])) {
    184                 memmove(&array1[i+2], &array1[i+1], (*num-i-1)*sizeof(SkScalar));
    185                 array1[i+1] = higher;
    186                 memmove(&array2[i+2], &array2[i+1], (*num-i-1)*sizeof(SkScalar));
    187                 array2[i+1] = interp(array1[i], higher, array1[i+2], array2[i], array2[i+2]);
    188                 (*num)++;
    189             }
    190             break;
    191         }
    192     }
    193 
    194     return skipMask;
    195 }
    196 
    197 bool SkBlurMaskFilter::ComputeBlurredRRectParams(const SkRRect& srcRRect, const SkRRect& devRRect,
    198                                                  const SkRect& occluder,
    199                                                  SkScalar sigma, SkScalar xformedSigma,
    200                                                  SkRRect* rrectToDraw,
    201                                                  SkISize* widthHeight,
    202                                                  SkScalar rectXs[kMaxDivisions],
    203                                                  SkScalar rectYs[kMaxDivisions],
    204                                                  SkScalar texXs[kMaxDivisions],
    205                                                  SkScalar texYs[kMaxDivisions],
    206                                                  int* numXs, int* numYs, uint32_t* skipMask) {
    207     unsigned int devBlurRadius = 3*SkScalarCeilToInt(xformedSigma-1/6.0f);
    208     SkScalar srcBlurRadius = 3.0f * sigma;
    209 
    210     const SkRect& devOrig = devRRect.getBounds();
    211     const SkVector& devRadiiUL = devRRect.radii(SkRRect::kUpperLeft_Corner);
    212     const SkVector& devRadiiUR = devRRect.radii(SkRRect::kUpperRight_Corner);
    213     const SkVector& devRadiiLR = devRRect.radii(SkRRect::kLowerRight_Corner);
    214     const SkVector& devRadiiLL = devRRect.radii(SkRRect::kLowerLeft_Corner);
    215 
    216     const int devLeft  = SkScalarCeilToInt(SkTMax<SkScalar>(devRadiiUL.fX, devRadiiLL.fX));
    217     const int devTop   = SkScalarCeilToInt(SkTMax<SkScalar>(devRadiiUL.fY, devRadiiUR.fY));
    218     const int devRight = SkScalarCeilToInt(SkTMax<SkScalar>(devRadiiUR.fX, devRadiiLR.fX));
    219     const int devBot   = SkScalarCeilToInt(SkTMax<SkScalar>(devRadiiLL.fY, devRadiiLR.fY));
    220 
    221     // This is a conservative check for nine-patchability
    222     if (devOrig.fLeft + devLeft + devBlurRadius >= devOrig.fRight  - devRight - devBlurRadius ||
    223         devOrig.fTop  + devTop  + devBlurRadius >= devOrig.fBottom - devBot   - devBlurRadius) {
    224         return false;
    225     }
    226 
    227     const SkVector& srcRadiiUL = srcRRect.radii(SkRRect::kUpperLeft_Corner);
    228     const SkVector& srcRadiiUR = srcRRect.radii(SkRRect::kUpperRight_Corner);
    229     const SkVector& srcRadiiLR = srcRRect.radii(SkRRect::kLowerRight_Corner);
    230     const SkVector& srcRadiiLL = srcRRect.radii(SkRRect::kLowerLeft_Corner);
    231 
    232     const SkScalar srcLeft  = SkTMax<SkScalar>(srcRadiiUL.fX, srcRadiiLL.fX);
    233     const SkScalar srcTop   = SkTMax<SkScalar>(srcRadiiUL.fY, srcRadiiUR.fY);
    234     const SkScalar srcRight = SkTMax<SkScalar>(srcRadiiUR.fX, srcRadiiLR.fX);
    235     const SkScalar srcBot   = SkTMax<SkScalar>(srcRadiiLL.fY, srcRadiiLR.fY);
    236 
    237     int newRRWidth = 2*devBlurRadius + devLeft + devRight + 1;
    238     int newRRHeight = 2*devBlurRadius + devTop + devBot + 1;
    239     widthHeight->fWidth = newRRWidth + 2 * devBlurRadius;
    240     widthHeight->fHeight = newRRHeight + 2 * devBlurRadius;
    241 
    242     const SkRect srcProxyRect = srcRRect.getBounds().makeOutset(srcBlurRadius, srcBlurRadius);
    243 
    244     rectXs[0] = srcProxyRect.fLeft;
    245     rectXs[1] = srcProxyRect.fLeft + 2*srcBlurRadius + srcLeft;
    246     rectXs[2] = srcProxyRect.fRight - 2*srcBlurRadius - srcRight;
    247     rectXs[3] = srcProxyRect.fRight;
    248 
    249     rectYs[0] = srcProxyRect.fTop;
    250     rectYs[1] = srcProxyRect.fTop + 2*srcBlurRadius + srcTop;
    251     rectYs[2] = srcProxyRect.fBottom - 2*srcBlurRadius - srcBot;
    252     rectYs[3] = srcProxyRect.fBottom;
    253 
    254     texXs[0] = 0.0f;
    255     texXs[1] = 2.0f*devBlurRadius + devLeft;
    256     texXs[2] = 2.0f*devBlurRadius + devLeft + 1;
    257     texXs[3] = SkIntToScalar(widthHeight->fWidth);
    258 
    259     texYs[0] = 0.0f;
    260     texYs[1] = 2.0f*devBlurRadius + devTop;
    261     texYs[2] = 2.0f*devBlurRadius + devTop + 1;
    262     texYs[3] = SkIntToScalar(widthHeight->fHeight);
    263 
    264     SkRect temp = occluder;
    265 
    266     *numXs = 4;
    267     *numYs = 4;
    268     *skipMask = 0;
    269     if (!temp.isEmpty() && (srcProxyRect.contains(temp) || temp.intersect(srcProxyRect))) {
    270         *skipMask = insert_into_arrays(rectXs, texXs, temp.fLeft, temp.fRight, numXs, 0x1, 1);
    271         *skipMask = insert_into_arrays(rectYs, texYs, temp.fTop, temp.fBottom,
    272                                        numYs, *skipMask, *numXs-1);
    273     }
    274 
    275     const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(devBlurRadius),
    276                                             SkIntToScalar(devBlurRadius),
    277                                             SkIntToScalar(newRRWidth),
    278                                             SkIntToScalar(newRRHeight));
    279     SkVector newRadii[4];
    280     newRadii[0] = { SkScalarCeilToScalar(devRadiiUL.fX), SkScalarCeilToScalar(devRadiiUL.fY) };
    281     newRadii[1] = { SkScalarCeilToScalar(devRadiiUR.fX), SkScalarCeilToScalar(devRadiiUR.fY) };
    282     newRadii[2] = { SkScalarCeilToScalar(devRadiiLR.fX), SkScalarCeilToScalar(devRadiiLR.fY) };
    283     newRadii[3] = { SkScalarCeilToScalar(devRadiiLL.fX), SkScalarCeilToScalar(devRadiiLL.fY) };
    284 
    285     rrectToDraw->setRectRadii(newRect, newRadii);
    286     return true;
    287 }
    288 
    289 ///////////////////////////////////////////////////////////////////////////////
    290 
    291 SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle style,
    292                                            const SkRect& occluder, uint32_t flags)
    293     : fSigma(sigma)
    294     , fBlurStyle(style)
    295     , fOccluder(occluder)
    296     , fBlurFlags(flags) {
    297     SkASSERT(fSigma > 0);
    298     SkASSERT((unsigned)style <= kLastEnum_SkBlurStyle);
    299     SkASSERT(flags <= SkBlurMaskFilter::kAll_BlurFlag);
    300 }
    301 
    302 SkMask::Format SkBlurMaskFilterImpl::getFormat() const {
    303     return SkMask::kA8_Format;
    304 }
    305 
    306 bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const {
    307     if (this->ignoreXform()) {
    308         return false;
    309     }
    310 
    311     if (rec) {
    312         rec->fSigma = fSigma;
    313         rec->fStyle = fBlurStyle;
    314         rec->fQuality = this->getQuality();
    315     }
    316     return true;
    317 }
    318 
    319 bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
    320                                       const SkMatrix& matrix,
    321                                       SkIPoint* margin) const {
    322     SkScalar sigma = this->computeXformedSigma(matrix);
    323     return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, this->getQuality(), margin);
    324 }
    325 
    326 bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r,
    327                                           const SkMatrix& matrix,
    328                                           SkIPoint* margin, SkMask::CreateMode createMode) const {
    329     SkScalar sigma = computeXformedSigma(matrix);
    330 
    331     return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, margin, createMode);
    332 }
    333 
    334 bool SkBlurMaskFilterImpl::filterRRectMask(SkMask* dst, const SkRRect& r,
    335                                           const SkMatrix& matrix,
    336                                           SkIPoint* margin, SkMask::CreateMode createMode) const {
    337     SkScalar sigma = computeXformedSigma(matrix);
    338 
    339     return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, margin, createMode);
    340 }
    341 
    342 #include "SkCanvas.h"
    343 
    344 static bool prepare_to_draw_into_mask(const SkRect& bounds, SkMask* mask) {
    345     SkASSERT(mask != nullptr);
    346 
    347     mask->fBounds = bounds.roundOut();
    348     mask->fRowBytes = SkAlign4(mask->fBounds.width());
    349     mask->fFormat = SkMask::kA8_Format;
    350     const size_t size = mask->computeImageSize();
    351     mask->fImage = SkMask::AllocImage(size, SkMask::kZeroInit_Alloc);
    352     if (nullptr == mask->fImage) {
    353         return false;
    354     }
    355     return true;
    356 }
    357 
    358 static bool draw_rrect_into_mask(const SkRRect rrect, SkMask* mask) {
    359     if (!prepare_to_draw_into_mask(rrect.rect(), mask)) {
    360         return false;
    361     }
    362 
    363     // FIXME: This code duplicates code in draw_rects_into_mask, below. Is there a
    364     // clean way to share more code?
    365     SkBitmap bitmap;
    366     bitmap.installMaskPixels(*mask);
    367 
    368     SkCanvas canvas(bitmap);
    369     canvas.translate(-SkIntToScalar(mask->fBounds.left()),
    370                      -SkIntToScalar(mask->fBounds.top()));
    371 
    372     SkPaint paint;
    373     paint.setAntiAlias(true);
    374     canvas.drawRRect(rrect, paint);
    375     return true;
    376 }
    377 
    378 static bool draw_rects_into_mask(const SkRect rects[], int count, SkMask* mask) {
    379     if (!prepare_to_draw_into_mask(rects[0], mask)) {
    380         return false;
    381     }
    382 
    383     SkBitmap bitmap;
    384     bitmap.installPixels(SkImageInfo::Make(mask->fBounds.width(),
    385                                            mask->fBounds.height(),
    386                                            kAlpha_8_SkColorType,
    387                                            kPremul_SkAlphaType),
    388                          mask->fImage, mask->fRowBytes);
    389 
    390     SkCanvas canvas(bitmap);
    391     canvas.translate(-SkIntToScalar(mask->fBounds.left()),
    392                      -SkIntToScalar(mask->fBounds.top()));
    393 
    394     SkPaint paint;
    395     paint.setAntiAlias(true);
    396 
    397     if (1 == count) {
    398         canvas.drawRect(rects[0], paint);
    399     } else {
    400         // todo: do I need a fast way to do this?
    401         SkPath path;
    402         path.addRect(rects[0]);
    403         path.addRect(rects[1]);
    404         path.setFillType(SkPath::kEvenOdd_FillType);
    405         canvas.drawPath(path, paint);
    406     }
    407     return true;
    408 }
    409 
    410 static bool rect_exceeds(const SkRect& r, SkScalar v) {
    411     return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v ||
    412            r.width() > v || r.height() > v;
    413 }
    414 
    415 #include "SkMaskCache.h"
    416 
    417 static SkCachedData* copy_mask_to_cacheddata(SkMask* mask) {
    418     const size_t size = mask->computeTotalImageSize();
    419     SkCachedData* data = SkResourceCache::NewCachedData(size);
    420     if (data) {
    421         memcpy(data->writable_data(), mask->fImage, size);
    422         SkMask::FreeImage(mask->fImage);
    423         mask->fImage = (uint8_t*)data->data();
    424     }
    425     return data;
    426 }
    427 
    428 static SkCachedData* find_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style,
    429                                        SkBlurQuality quality, const SkRRect& rrect) {
    430     return SkMaskCache::FindAndRef(sigma, style, quality, rrect, mask);
    431 }
    432 
    433 static SkCachedData* add_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style,
    434                                       SkBlurQuality quality, const SkRRect& rrect) {
    435     SkCachedData* cache = copy_mask_to_cacheddata(mask);
    436     if (cache) {
    437         SkMaskCache::Add(sigma, style, quality, rrect, *mask, cache);
    438     }
    439     return cache;
    440 }
    441 
    442 static SkCachedData* find_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style,
    443                                        SkBlurQuality quality, const SkRect rects[], int count) {
    444     return SkMaskCache::FindAndRef(sigma, style, quality, rects, count, mask);
    445 }
    446 
    447 static SkCachedData* add_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style,
    448                                       SkBlurQuality quality, const SkRect rects[], int count) {
    449     SkCachedData* cache = copy_mask_to_cacheddata(mask);
    450     if (cache) {
    451         SkMaskCache::Add(sigma, style, quality, rects, count, *mask, cache);
    452     }
    453     return cache;
    454 }
    455 
    456 #ifdef SK_IGNORE_FAST_RRECT_BLUR
    457   // Use the faster analytic blur approach for ninepatch round rects
    458   static const bool c_analyticBlurRRect{false};
    459 #else
    460   static const bool c_analyticBlurRRect{true};
    461 #endif
    462 
    463 SkMaskFilterBase::FilterReturn
    464 SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix,
    465                                         const SkIRect& clipBounds,
    466                                         NinePatch* patch) const {
    467     SkASSERT(patch != nullptr);
    468     switch (rrect.getType()) {
    469         case SkRRect::kEmpty_Type:
    470             // Nothing to draw.
    471             return kFalse_FilterReturn;
    472 
    473         case SkRRect::kRect_Type:
    474             // We should have caught this earlier.
    475             SkASSERT(false);
    476             // Fall through.
    477         case SkRRect::kOval_Type:
    478             // The nine patch special case does not handle ovals, and we
    479             // already have code for rectangles.
    480             return kUnimplemented_FilterReturn;
    481 
    482         // These three can take advantage of this fast path.
    483         case SkRRect::kSimple_Type:
    484         case SkRRect::kNinePatch_Type:
    485         case SkRRect::kComplex_Type:
    486             break;
    487     }
    488 
    489     // TODO: report correct metrics for innerstyle, where we do not grow the
    490     // total bounds, but we do need an inset the size of our blur-radius
    491     if (kInner_SkBlurStyle == fBlurStyle) {
    492         return kUnimplemented_FilterReturn;
    493     }
    494 
    495     // TODO: take clipBounds into account to limit our coordinates up front
    496     // for now, just skip too-large src rects (to take the old code path).
    497     if (rect_exceeds(rrect.rect(), SkIntToScalar(32767))) {
    498         return kUnimplemented_FilterReturn;
    499     }
    500 
    501     SkIPoint margin;
    502     SkMask  srcM, dstM;
    503     srcM.fBounds = rrect.rect().roundOut();
    504     srcM.fFormat = SkMask::kA8_Format;
    505     srcM.fRowBytes = 0;
    506 
    507     bool filterResult = false;
    508     if (c_analyticBlurRRect) {
    509         // special case for fast round rect blur
    510         // don't actually do the blur the first time, just compute the correct size
    511         filterResult = this->filterRRectMask(&dstM, rrect, matrix, &margin,
    512                                             SkMask::kJustComputeBounds_CreateMode);
    513     }
    514 
    515     if (!filterResult) {
    516         filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
    517     }
    518 
    519     if (!filterResult) {
    520         return kFalse_FilterReturn;
    521     }
    522 
    523     // Now figure out the appropriate width and height of the smaller round rectangle
    524     // to stretch. It will take into account the larger radius per side as well as double
    525     // the margin, to account for inner and outer blur.
    526     const SkVector& UL = rrect.radii(SkRRect::kUpperLeft_Corner);
    527     const SkVector& UR = rrect.radii(SkRRect::kUpperRight_Corner);
    528     const SkVector& LR = rrect.radii(SkRRect::kLowerRight_Corner);
    529     const SkVector& LL = rrect.radii(SkRRect::kLowerLeft_Corner);
    530 
    531     const SkScalar leftUnstretched = SkTMax(UL.fX, LL.fX) + SkIntToScalar(2 * margin.fX);
    532     const SkScalar rightUnstretched = SkTMax(UR.fX, LR.fX) + SkIntToScalar(2 * margin.fX);
    533 
    534     // Extra space in the middle to ensure an unchanging piece for stretching. Use 3 to cover
    535     // any fractional space on either side plus 1 for the part to stretch.
    536     const SkScalar stretchSize = SkIntToScalar(3);
    537 
    538     const SkScalar totalSmallWidth = leftUnstretched + rightUnstretched + stretchSize;
    539     if (totalSmallWidth >= rrect.rect().width()) {
    540         // There is no valid piece to stretch.
    541         return kUnimplemented_FilterReturn;
    542     }
    543 
    544     const SkScalar topUnstretched = SkTMax(UL.fY, UR.fY) + SkIntToScalar(2 * margin.fY);
    545     const SkScalar bottomUnstretched = SkTMax(LL.fY, LR.fY) + SkIntToScalar(2 * margin.fY);
    546 
    547     const SkScalar totalSmallHeight = topUnstretched + bottomUnstretched + stretchSize;
    548     if (totalSmallHeight >= rrect.rect().height()) {
    549         // There is no valid piece to stretch.
    550         return kUnimplemented_FilterReturn;
    551     }
    552 
    553     SkRect smallR = SkRect::MakeWH(totalSmallWidth, totalSmallHeight);
    554 
    555     SkRRect smallRR;
    556     SkVector radii[4];
    557     radii[SkRRect::kUpperLeft_Corner] = UL;
    558     radii[SkRRect::kUpperRight_Corner] = UR;
    559     radii[SkRRect::kLowerRight_Corner] = LR;
    560     radii[SkRRect::kLowerLeft_Corner] = LL;
    561     smallRR.setRectRadii(smallR, radii);
    562 
    563     const SkScalar sigma = this->computeXformedSigma(matrix);
    564     SkCachedData* cache = find_cached_rrect(&patch->fMask, sigma, fBlurStyle,
    565                                             this->getQuality(), smallRR);
    566     if (!cache) {
    567         bool analyticBlurWorked = false;
    568         if (c_analyticBlurRRect) {
    569             analyticBlurWorked =
    570                 this->filterRRectMask(&patch->fMask, smallRR, matrix, &margin,
    571                                       SkMask::kComputeBoundsAndRenderImage_CreateMode);
    572         }
    573 
    574         if (!analyticBlurWorked) {
    575             if (!draw_rrect_into_mask(smallRR, &srcM)) {
    576                 return kFalse_FilterReturn;
    577             }
    578 
    579             SkAutoMaskFreeImage amf(srcM.fImage);
    580 
    581             if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
    582                 return kFalse_FilterReturn;
    583             }
    584         }
    585         cache = add_cached_rrect(&patch->fMask, sigma, fBlurStyle, this->getQuality(), smallRR);
    586     }
    587 
    588     patch->fMask.fBounds.offsetTo(0, 0);
    589     patch->fOuterRect = dstM.fBounds;
    590     patch->fCenter.fX = SkScalarCeilToInt(leftUnstretched) + 1;
    591     patch->fCenter.fY = SkScalarCeilToInt(topUnstretched) + 1;
    592     SkASSERT(nullptr == patch->fCache);
    593     patch->fCache = cache;  // transfer ownership to patch
    594     return kTrue_FilterReturn;
    595 }
    596 
    597 // Use the faster analytic blur approach for ninepatch rects
    598 static const bool c_analyticBlurNinepatch{true};
    599 
    600 SkMaskFilterBase::FilterReturn
    601 SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
    602                                         const SkMatrix& matrix,
    603                                         const SkIRect& clipBounds,
    604                                         NinePatch* patch) const {
    605     if (count < 1 || count > 2) {
    606         return kUnimplemented_FilterReturn;
    607     }
    608 
    609     // TODO: report correct metrics for innerstyle, where we do not grow the
    610     // total bounds, but we do need an inset the size of our blur-radius
    611     if (kInner_SkBlurStyle == fBlurStyle || kOuter_SkBlurStyle == fBlurStyle) {
    612         return kUnimplemented_FilterReturn;
    613     }
    614 
    615     // TODO: take clipBounds into account to limit our coordinates up front
    616     // for now, just skip too-large src rects (to take the old code path).
    617     if (rect_exceeds(rects[0], SkIntToScalar(32767))) {
    618         return kUnimplemented_FilterReturn;
    619     }
    620 
    621     SkIPoint margin;
    622     SkMask  srcM, dstM;
    623     srcM.fBounds = rects[0].roundOut();
    624     srcM.fFormat = SkMask::kA8_Format;
    625     srcM.fRowBytes = 0;
    626 
    627     bool filterResult = false;
    628     if (count == 1 && c_analyticBlurNinepatch) {
    629         // special case for fast rect blur
    630         // don't actually do the blur the first time, just compute the correct size
    631         filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin,
    632                                             SkMask::kJustComputeBounds_CreateMode);
    633     } else {
    634         filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
    635     }
    636 
    637     if (!filterResult) {
    638         return kFalse_FilterReturn;
    639     }
    640 
    641     /*
    642      *  smallR is the smallest version of 'rect' that will still guarantee that
    643      *  we get the same blur results on all edges, plus 1 center row/col that is
    644      *  representative of the extendible/stretchable edges of the ninepatch.
    645      *  Since our actual edge may be fractional we inset 1 more to be sure we
    646      *  don't miss any interior blur.
    647      *  x is an added pixel of blur, and { and } are the (fractional) edge
    648      *  pixels from the original rect.
    649      *
    650      *   x x { x x .... x x } x x
    651      *
    652      *  Thus, in this case, we inset by a total of 5 (on each side) beginning
    653      *  with our outer-rect (dstM.fBounds)
    654      */
    655     SkRect smallR[2];
    656     SkIPoint center;
    657 
    658     // +2 is from +1 for each edge (to account for possible fractional edges
    659     int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2;
    660     int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2;
    661     SkIRect innerIR;
    662 
    663     if (1 == count) {
    664         innerIR = srcM.fBounds;
    665         center.set(smallW, smallH);
    666     } else {
    667         SkASSERT(2 == count);
    668         rects[1].roundIn(&innerIR);
    669         center.set(smallW + (innerIR.left() - srcM.fBounds.left()),
    670                    smallH + (innerIR.top() - srcM.fBounds.top()));
    671     }
    672 
    673     // +1 so we get a clean, stretchable, center row/col
    674     smallW += 1;
    675     smallH += 1;
    676 
    677     // we want the inset amounts to be integral, so we don't change any
    678     // fractional phase on the fRight or fBottom of our smallR.
    679     const SkScalar dx = SkIntToScalar(innerIR.width() - smallW);
    680     const SkScalar dy = SkIntToScalar(innerIR.height() - smallH);
    681     if (dx < 0 || dy < 0) {
    682         // we're too small, relative to our blur, to break into nine-patch,
    683         // so we ask to have our normal filterMask() be called.
    684         return kUnimplemented_FilterReturn;
    685     }
    686 
    687     smallR[0].set(rects[0].left(), rects[0].top(), rects[0].right() - dx, rects[0].bottom() - dy);
    688     if (smallR[0].width() < 2 || smallR[0].height() < 2) {
    689         return kUnimplemented_FilterReturn;
    690     }
    691     if (2 == count) {
    692         smallR[1].set(rects[1].left(), rects[1].top(),
    693                       rects[1].right() - dx, rects[1].bottom() - dy);
    694         SkASSERT(!smallR[1].isEmpty());
    695     }
    696 
    697     const SkScalar sigma = this->computeXformedSigma(matrix);
    698     SkCachedData* cache = find_cached_rects(&patch->fMask, sigma, fBlurStyle,
    699                                             this->getQuality(), smallR, count);
    700     if (!cache) {
    701         if (count > 1 || !c_analyticBlurNinepatch) {
    702             if (!draw_rects_into_mask(smallR, count, &srcM)) {
    703                 return kFalse_FilterReturn;
    704             }
    705 
    706             SkAutoMaskFreeImage amf(srcM.fImage);
    707 
    708             if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
    709                 return kFalse_FilterReturn;
    710             }
    711         } else {
    712             if (!this->filterRectMask(&patch->fMask, smallR[0], matrix, &margin,
    713                                       SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
    714                 return kFalse_FilterReturn;
    715             }
    716         }
    717         cache = add_cached_rects(&patch->fMask, sigma, fBlurStyle, this->getQuality(), smallR, count);
    718     }
    719     patch->fMask.fBounds.offsetTo(0, 0);
    720     patch->fOuterRect = dstM.fBounds;
    721     patch->fCenter = center;
    722     SkASSERT(nullptr == patch->fCache);
    723     patch->fCache = cache;  // transfer ownership to patch
    724     return kTrue_FilterReturn;
    725 }
    726 
    727 void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src,
    728                                              SkRect* dst) const {
    729     SkScalar pad = 3.0f * fSigma;
    730 
    731     dst->set(src.fLeft  - pad, src.fTop    - pad,
    732              src.fRight + pad, src.fBottom + pad);
    733 }
    734 
    735 sk_sp<SkFlattenable> SkBlurMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
    736     const SkScalar sigma = buffer.readScalar();
    737     SkBlurStyle style = buffer.read32LE(kLastEnum_SkBlurStyle);
    738     unsigned flags = buffer.read32LE(SkBlurMaskFilter::kAll_BlurFlag);
    739 
    740     SkRect occluder;
    741     buffer.readRect(&occluder);
    742 
    743     return SkBlurMaskFilter::Make((SkBlurStyle)style, sigma, occluder, flags);
    744 }
    745 
    746 void SkBlurMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
    747     buffer.writeScalar(fSigma);
    748     buffer.writeUInt(fBlurStyle);
    749     buffer.writeUInt(fBlurFlags);
    750     buffer.writeRect(fOccluder);
    751 }
    752 
    753 
    754 #if SK_SUPPORT_GPU
    755 
    756 bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrContext* context,
    757                                                GrRenderTargetContext* renderTargetContext,
    758                                                GrPaint&& paint,
    759                                                const GrClip& clip,
    760                                                const SkMatrix& viewMatrix,
    761                                                const SkStrokeRec& strokeRec,
    762                                                const SkPath& path) const {
    763     SkASSERT(renderTargetContext);
    764 
    765     if (fBlurStyle != kNormal_SkBlurStyle) {
    766         return false;
    767     }
    768 
    769     // TODO: we could handle blurred stroked circles
    770     if (!strokeRec.isFillStyle()) {
    771         return false;
    772     }
    773 
    774     SkScalar xformedSigma = this->computeXformedSigma(viewMatrix);
    775 
    776     GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
    777     std::unique_ptr<GrFragmentProcessor> fp;
    778 
    779     SkRect rect;
    780     if (path.isRect(&rect)) {
    781         SkScalar pad = 3.0f * xformedSigma;
    782         rect.outset(pad, pad);
    783 
    784         fp = GrRectBlurEffect::Make(proxyProvider, rect, xformedSigma);
    785     } else if (path.isOval(&rect) && SkScalarNearlyEqual(rect.width(), rect.height())) {
    786         fp = GrCircleBlurFragmentProcessor::Make(proxyProvider, rect, xformedSigma);
    787 
    788         // expand the rect for the coverage geometry
    789         int pad = SkScalarCeilToInt(6*xformedSigma)/2;
    790         rect.outset(SkIntToScalar(pad), SkIntToScalar(pad));
    791     } else {
    792         return false;
    793     }
    794 
    795     if (!fp) {
    796         return false;
    797     }
    798 
    799     SkMatrix inverse;
    800     if (!viewMatrix.invert(&inverse)) {
    801         return false;
    802     }
    803 
    804     paint.addCoverageFragmentProcessor(std::move(fp));
    805     renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
    806                                                  rect, inverse);
    807     return true;
    808 }
    809 
    810 bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrContext* context,
    811                                                     GrRenderTargetContext* renderTargetContext,
    812                                                     GrPaint&& paint,
    813                                                     const GrClip& clip,
    814                                                     const SkMatrix& viewMatrix,
    815                                                     const SkStrokeRec& strokeRec,
    816                                                     const SkRRect& srcRRect,
    817                                                     const SkRRect& devRRect) const {
    818     SkASSERT(renderTargetContext);
    819 
    820     if (fBlurStyle != kNormal_SkBlurStyle) {
    821         return false;
    822     }
    823 
    824     if (!strokeRec.isFillStyle()) {
    825         return false;
    826     }
    827 
    828     GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
    829     SkScalar xformedSigma = this->computeXformedSigma(viewMatrix);
    830 
    831     if (devRRect.isRect() || devRRect.isCircle()) {
    832         std::unique_ptr<GrFragmentProcessor> fp;
    833         if (devRRect.isRect()) {
    834             SkScalar pad = 3.0f * xformedSigma;
    835             const SkRect dstCoverageRect = devRRect.rect().makeOutset(pad, pad);
    836 
    837             fp = GrRectBlurEffect::Make(proxyProvider, dstCoverageRect, xformedSigma);
    838         } else {
    839             fp = GrCircleBlurFragmentProcessor::Make(proxyProvider,
    840                                                      devRRect.rect(), xformedSigma);
    841         }
    842 
    843         if (!fp) {
    844             return false;
    845         }
    846         paint.addCoverageFragmentProcessor(std::move(fp));
    847 
    848         SkRect srcProxyRect = srcRRect.rect();
    849         SkScalar outsetX = 3.0f*fSigma;
    850         SkScalar outsetY = 3.0f*fSigma;
    851         if (this->ignoreXform()) {
    852             // When we're ignoring the CTM the padding added to the source rect also needs to ignore
    853             // the CTM. The matrix passed in here is guaranteed to be just scale and translate so we
    854             // can just grab the X and Y scales off the matrix and pre-undo the scale.
    855             outsetX /= viewMatrix.getScaleX();
    856             outsetY /= viewMatrix.getScaleY();
    857         }
    858         srcProxyRect.outset(outsetX, outsetY);
    859 
    860         renderTargetContext->drawRect(clip, std::move(paint), GrAA::kNo, viewMatrix, srcProxyRect);
    861         return true;
    862     }
    863 
    864     auto fp = GrRRectBlurEffect::Make(context, fSigma, xformedSigma, srcRRect, devRRect);
    865     if (!fp) {
    866         return false;
    867     }
    868 
    869     if (!this->ignoreXform()) {
    870         SkRect srcProxyRect = srcRRect.rect();
    871         srcProxyRect.outset(3.0f*fSigma, 3.0f*fSigma);
    872 
    873         sk_sp<SkVertices> vertices = nullptr;
    874         SkRect temp = fOccluder;
    875 
    876         if (!temp.isEmpty() && (srcProxyRect.contains(temp) || temp.intersect(srcProxyRect))) {
    877             SkVertices::Builder builder(SkVertices::kTriangles_VertexMode, 8, 24, 0);
    878             srcProxyRect.toQuad(builder.positions());
    879             temp.toQuad(builder.positions() + 4);
    880 
    881             static const uint16_t ringI[24] = { 0, 1, 5, 5, 4, 0,
    882                                                 1, 2, 6, 6, 5, 1,
    883                                                 2, 3, 7, 7, 6, 2,
    884                                                 3, 0, 4, 4, 7, 3 };
    885             memcpy(builder.indices(), ringI, sizeof(ringI));
    886             vertices = builder.detach();
    887         } else {
    888             // full rect case
    889             SkVertices::Builder builder(SkVertices::kTriangles_VertexMode, 4, 6, 0);
    890             srcProxyRect.toQuad(builder.positions());
    891 
    892             static const uint16_t fullI[6] = { 0, 1, 2, 0, 2, 3 };
    893             memcpy(builder.indices(), fullI, sizeof(fullI));
    894             vertices = builder.detach();
    895         }
    896 
    897         paint.addCoverageFragmentProcessor(std::move(fp));
    898         renderTargetContext->drawVertices(clip, std::move(paint), viewMatrix, std::move(vertices));
    899     } else {
    900         SkMatrix inverse;
    901         if (!viewMatrix.invert(&inverse)) {
    902             return false;
    903         }
    904 
    905         float extra=3.f*SkScalarCeilToScalar(xformedSigma-1/6.0f);
    906         SkRect proxyRect = devRRect.rect();
    907         proxyRect.outset(extra, extra);
    908 
    909         paint.addCoverageFragmentProcessor(std::move(fp));
    910         renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo,
    911                                                      SkMatrix::I(), proxyRect, inverse);
    912     }
    913 
    914     return true;
    915 }
    916 
    917 bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect,
    918                                             const SkIRect& clipBounds,
    919                                             const SkMatrix& ctm,
    920                                             SkRect* maskRect) const {
    921     SkScalar xformedSigma = this->computeXformedSigma(ctm);
    922     if (xformedSigma <= 0) {
    923         return false;
    924     }
    925 
    926     // We always do circles and simple circular rrects on the GPU
    927     if (!devRRect.isCircle() && !devRRect.isSimpleCircular()) {
    928         static const SkScalar kMIN_GPU_BLUR_SIZE  = SkIntToScalar(64);
    929         static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32);
    930 
    931         if (devRRect.width() <= kMIN_GPU_BLUR_SIZE &&
    932             devRRect.height() <= kMIN_GPU_BLUR_SIZE &&
    933             xformedSigma <= kMIN_GPU_BLUR_SIGMA) {
    934             // We prefer to blur small rects with small radii on the CPU.
    935             return false;
    936         }
    937     }
    938 
    939     if (nullptr == maskRect) {
    940         // don't need to compute maskRect
    941         return true;
    942     }
    943 
    944     float sigma3 = 3 * SkScalarToFloat(xformedSigma);
    945 
    946     SkRect clipRect = SkRect::Make(clipBounds);
    947     SkRect srcRect(devRRect.rect());
    948 
    949     // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
    950     srcRect.outset(sigma3, sigma3);
    951     clipRect.outset(sigma3, sigma3);
    952     if (!srcRect.intersect(clipRect)) {
    953         srcRect.setEmpty();
    954     }
    955     *maskRect = srcRect;
    956     return true;
    957 }
    958 
    959 sk_sp<GrTextureProxy> SkBlurMaskFilterImpl::filterMaskGPU(GrContext* context,
    960                                                           sk_sp<GrTextureProxy> srcProxy,
    961                                                           const SkMatrix& ctm,
    962                                                           const SkIRect& maskRect) const {
    963     // 'maskRect' isn't snapped to the UL corner but the mask in 'src' is.
    964     const SkIRect clipRect = SkIRect::MakeWH(maskRect.width(), maskRect.height());
    965 
    966     SkScalar xformedSigma = this->computeXformedSigma(ctm);
    967     SkASSERT(xformedSigma > 0);
    968 
    969     // If we're doing a normal blur, we can clobber the pathTexture in the
    970     // gaussianBlur.  Otherwise, we need to save it for later compositing.
    971     bool isNormalBlur = (kNormal_SkBlurStyle == fBlurStyle);
    972     sk_sp<GrRenderTargetContext> renderTargetContext(
    973               SkGpuBlurUtils::GaussianBlur(context,
    974                                            srcProxy,
    975                                            nullptr,
    976                                            clipRect,
    977                                            SkIRect::EmptyIRect(),
    978                                            xformedSigma,
    979                                            xformedSigma,
    980                                            GrTextureDomain::kIgnore_Mode));
    981     if (!renderTargetContext) {
    982         return nullptr;
    983     }
    984 
    985     if (!isNormalBlur) {
    986         GrPaint paint;
    987         // Blend pathTexture over blurTexture.
    988         paint.addCoverageFragmentProcessor(GrSimpleTextureEffect::Make(std::move(srcProxy),
    989                                                                        SkMatrix::I()));
    990         if (kInner_SkBlurStyle == fBlurStyle) {
    991             // inner:  dst = dst * src
    992             paint.setCoverageSetOpXPFactory(SkRegion::kIntersect_Op);
    993         } else if (kSolid_SkBlurStyle == fBlurStyle) {
    994             // solid:  dst = src + dst - src * dst
    995             //             = src + (1 - src) * dst
    996             paint.setCoverageSetOpXPFactory(SkRegion::kUnion_Op);
    997         } else if (kOuter_SkBlurStyle == fBlurStyle) {
    998             // outer:  dst = dst * (1 - src)
    999             //             = 0 * src + (1 - src) * dst
   1000             paint.setCoverageSetOpXPFactory(SkRegion::kDifference_Op);
   1001         } else {
   1002             paint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op);
   1003         }
   1004 
   1005         renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
   1006                                       SkRect::Make(clipRect));
   1007     }
   1008 
   1009     return renderTargetContext->asTextureProxyRef();
   1010 }
   1011 
   1012 #endif // SK_SUPPORT_GPU
   1013 
   1014 
   1015 #ifndef SK_IGNORE_TO_STRING
   1016 void SkBlurMaskFilterImpl::toString(SkString* str) const {
   1017     str->append("SkBlurMaskFilterImpl: (");
   1018 
   1019     str->append("sigma: ");
   1020     str->appendScalar(fSigma);
   1021     str->append(" ");
   1022 
   1023     static const char* gStyleName[kLastEnum_SkBlurStyle + 1] = {
   1024         "normal", "solid", "outer", "inner"
   1025     };
   1026 
   1027     str->appendf("style: %s ", gStyleName[fBlurStyle]);
   1028     str->append("flags: (");
   1029     if (fBlurFlags) {
   1030         bool needSeparator = false;
   1031         SkAddFlagToString(str, this->ignoreXform(), "IgnoreXform", &needSeparator);
   1032         SkAddFlagToString(str,
   1033                           SkToBool(fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag),
   1034                           "HighQuality", &needSeparator);
   1035     } else {
   1036         str->append("None");
   1037     }
   1038     str->append("))");
   1039 }
   1040 #endif
   1041 
   1042 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkBlurMaskFilter)
   1043     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurMaskFilterImpl)
   1044 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
   1045