Add ability to ninepatch blurred rounded rectangle
authorscroggo@google.com <scroggo@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Fri, 8 Nov 2013 18:02:53 +0000 (18:02 +0000)
committerscroggo@google.com <scroggo@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Fri, 8 Nov 2013 18:02:53 +0000 (18:02 +0000)
Speed up drawing large blurry round rectangles by converting them to
nine patches.

SkDraw:
Add drawRRect.

SkBitmapDevice:
Call SkDraw::drawRRect instead of converting SkRRect to an SkPath.

SkMaskFilter/SkBlurMaskFilter:
Create a nine patch of a blurred round rect and draw it instead of
drawing the entire thing.

SkPDFDevice:
Override drawRRect to perform the old behavior in
SkBitmapDevice::drawRect.

Depends on https://codereview.chromium.org/52703003

Tests are in https://codereview.chromium.org/52793005

BUG=https://b.corp.google.com/issue?id=11174385
R=reed@google.com, robertphillips@google.com

Review URL: https://codereview.chromium.org/48623006

git-svn-id: http://skia.googlecode.com/svn/trunk@12198 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkDraw.h
include/core/SkMaskFilter.h
include/pdf/SkPDFDevice.h
src/core/SkBitmapDevice.cpp
src/core/SkDraw.cpp
src/core/SkMaskFilter.cpp
src/effects/SkBlurMaskFilter.cpp
src/pdf/SkPDFDevice.cpp

index 91830946791823b66736e99b8000176314e045ca..f7ae1c6208fceeb465b422de7bc3d35b089a860c 100644 (file)
@@ -24,6 +24,7 @@ class SkRegion;
 class SkRasterClip;
 struct SkDrawProcs;
 struct SkRect;
+class SkRRect;
 
 class SkDraw {
 public:
@@ -34,6 +35,7 @@ public:
     void    drawPoints(SkCanvas::PointMode, size_t count, const SkPoint[],
                        const SkPaint&, bool forceUseDevice = false) const;
     void    drawRect(const SkRect&, const SkPaint&) const;
+    void    drawRRect(const SkRRect&, const SkPaint&) const;
     /**
      *  To save on mallocs, we allow a flag that tells us that srcPath is
      *  mutable, so that we don't have to make copies of it as we transform it.
index 93e2d4bc33eea61e4079dbda6a6d507afc54b354..f4448ddddb453e9502f075f928303a0c9fb428f0 100644 (file)
@@ -21,6 +21,7 @@ class SkBounder;
 class SkMatrix;
 class SkPath;
 class SkRasterClip;
+class SkRRect;
 
 /** \class SkMaskFilter
 
@@ -162,6 +163,12 @@ protected:
                                            const SkMatrix&,
                                            const SkIRect& clipBounds,
                                            NinePatch*) const;
+    /**
+     *  Similar to filterRectsToNine, except it performs the work on a round rect.
+     */
+    virtual FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&,
+                                           const SkIRect& clipBounds,
+                                           NinePatch*) const;
 
 private:
     friend class SkDraw;
@@ -175,6 +182,14 @@ private:
                     const SkRasterClip&, SkBounder*, SkBlitter* blitter,
                     SkPaint::Style style) const;
 
+    /** Helper method that, given a roundRect in device space, will rasterize it into a kA8_Format
+     mask and then call filterMask(). If this returns true, the specified blitter will be called
+     to render that mask. Returns false if filterMask() returned false.
+     */
+    bool filterRRect(const SkRRect& devRRect, const SkMatrix& devMatrix,
+                     const SkRasterClip&, SkBounder*, SkBlitter* blitter,
+                     SkPaint::Style style) const;
+
     typedef SkFlattenable INHERITED;
 };
 
index d8e1aa283f80ca84b18a75921cace94b372c351b..8e1f41823f03618628148917ab27e8963fc55fa9 100644 (file)
@@ -33,6 +33,7 @@ class SkPDFObject;
 class SkPDFResourceDict;
 class SkPDFShader;
 class SkPDFStream;
+class SkRRect;
 template <typename T> class SkTSet;
 
 // Private classes.
@@ -83,6 +84,8 @@ public:
                             size_t count, const SkPoint[],
                             const SkPaint& paint) SK_OVERRIDE;
     virtual void drawRect(const SkDraw&, const SkRect& r, const SkPaint& paint);
+    virtual void drawRRect(const SkDraw&, const SkRRect& rr,
+                           const SkPaint& paint) SK_OVERRIDE;
     virtual void drawPath(const SkDraw&, const SkPath& origpath,
                           const SkPaint& paint, const SkMatrix* prePathMatrix,
                           bool pathIsMutable) SK_OVERRIDE;
index eac21e2d48b427f4b7d0d2743ecd450a18fa27e5..b5ce122eb8d1444a71cc7de6b8a1ea2d79b012ec 100644 (file)
@@ -232,11 +232,7 @@ void SkBitmapDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPa
 void SkBitmapDevice::drawRRect(const SkDraw& draw, const SkRRect& rrect, const SkPaint& paint) {
     CHECK_FOR_ANNOTATION(paint);
 
-    SkPath  path;
-    path.addRRect(rrect);
-    // call the VIRTUAL version, so any subclasses who do handle drawPath aren't
-    // required to override drawRRect.
-    this->drawPath(draw, path, paint, NULL, true);
+    draw.drawRRect(rrect, paint);
 }
 
 void SkBitmapDevice::drawPath(const SkDraw& draw, const SkPath& path,
index af4f1ca0474fdecee56f137092f606cf31317133..f282bf5b7b5d317d63fa06d2519b0c9c9a68c54e 100644 (file)
@@ -18,6 +18,7 @@
 #include "SkPathEffect.h"
 #include "SkRasterClip.h"
 #include "SkRasterizer.h"
+#include "SkRRect.h"
 #include "SkScan.h"
 #include "SkShader.h"
 #include "SkString.h"
@@ -1022,6 +1023,51 @@ bool SkDrawTreatAsHairline(const SkPaint& paint, const SkMatrix& matrix,
     return false;
 }
 
+void SkDraw::drawRRect(const SkRRect& rrect, const SkPaint& paint) const {
+    SkDEBUGCODE(this->validate());
+
+    if (fRC->isEmpty()) {
+        return;
+    }
+
+    {
+        // TODO: Investigate optimizing these options. They are in the same
+        // order as SkDraw::drawPath, which handles each case. It may be
+        // that there is no way to optimize for these using the SkRRect path.
+        SkScalar coverage;
+        if (SkDrawTreatAsHairline(paint, *fMatrix, &coverage)) {
+            goto DRAW_PATH;
+        }
+
+        if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
+            goto DRAW_PATH;
+        }
+
+        if (paint.getRasterizer()) {
+            goto DRAW_PATH;
+        }
+    }
+
+    if (paint.getMaskFilter()) {
+        // Transform the rrect into device space.
+        SkRRect devRRect;
+        if (rrect.transform(*fMatrix, &devRRect)) {
+            SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, paint);
+            if (paint.getMaskFilter()->filterRRect(devRRect, *fMatrix, *fRC,
+                                                   fBounder, blitter.get(),
+                                                   SkPaint::kFill_Style)) {
+                return; // filterRRect() called the blitter, so we're done
+            }
+        }
+    }
+
+DRAW_PATH:
+    // Now fall back to the default case of using a path.
+    SkPath path;
+    path.addRRect(rrect);
+    this->drawPath(path, paint, NULL, true);
+}
+
 void SkDraw::drawPath(const SkPath& origSrcPath, const SkPaint& origPaint,
                       const SkMatrix* prePathMatrix, bool pathIsMutable,
                       bool drawCoverage) const {
index 9c367f946b552798447eb89eec20b3237f481afe..cd2571637749ac1f318ada65167ba7ab1aa0187e 100644 (file)
@@ -12,6 +12,7 @@
 #include "SkBounder.h"
 #include "SkDraw.h"
 #include "SkRasterClip.h"
+#include "SkRRect.h"
 #include "SkTypes.h"
 
 #if SK_SUPPORT_GPU
@@ -204,6 +205,26 @@ static int countNestedRects(const SkPath& path, SkRect rects[2]) {
     return path.isRect(&rects[0]);
 }
 
+bool SkMaskFilter::filterRRect(const SkRRect& devRRect, const SkMatrix& matrix,
+                               const SkRasterClip& clip, SkBounder* bounder,
+                               SkBlitter* blitter, SkPaint::Style style) const {
+    // Attempt to speed up drawing by creating a nine patch. If a nine patch
+    // cannot be used, return false to allow our caller to recover and perform
+    // the drawing another way.
+    NinePatch patch;
+    patch.fMask.fImage = NULL;
+    if (kTrue_FilterReturn != this->filterRRectToNine(devRRect, matrix,
+                                                      clip.getBounds(),
+                                                      &patch)) {
+        SkASSERT(NULL == patch.fMask.fImage);
+        return false;
+    }
+    draw_nine(patch.fMask, patch.fOuterRect, patch.fCenter, true, clip,
+              bounder, blitter);
+    SkMask::FreeImage(patch.fMask.fImage);
+    return true;
+}
+
 bool SkMaskFilter::filterPath(const SkPath& devPath, const SkMatrix& matrix,
                               const SkRasterClip& clip, SkBounder* bounder,
                               SkBlitter* blitter, SkPaint::Style style) const {
@@ -266,6 +287,12 @@ bool SkMaskFilter::filterPath(const SkPath& devPath, const SkMatrix& matrix,
     return true;
 }
 
+SkMaskFilter::FilterReturn
+SkMaskFilter::filterRRectToNine(const SkRRect&, const SkMatrix&,
+                                const SkIRect& clipBounds, NinePatch*) const {
+    return kUnimplemented_FilterReturn;
+}
+
 SkMaskFilter::FilterReturn
 SkMaskFilter::filterRectsToNine(const SkRect[], int count, const SkMatrix&,
                                 const SkIRect& clipBounds, NinePatch*) const {
index a2e8065284f2467b1714f12a099b4921717e96e8..17690236fff1ef347de5804060a19a7794a16593 100644 (file)
@@ -11,6 +11,7 @@
 #include "SkGpuBlurUtils.h"
 #include "SkFlattenableBuffers.h"
 #include "SkMaskFilter.h"
+#include "SkRRect.h"
 #include "SkRTConf.h"
 #include "SkStringUtils.h"
 #include "SkStrokeRec.h"
@@ -52,6 +53,10 @@ protected:
                                            const SkIRect& clipBounds,
                                            NinePatch*) const SK_OVERRIDE;
 
+    virtual FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&,
+                                           const SkIRect& clipBounds,
+                                           NinePatch*) const SK_OVERRIDE;
+
     bool filterRectMask(SkMask* dstM, const SkRect& r, const SkMatrix& matrix,
                         SkIPoint* margin, SkMask::CreateMode createMode) const;
 
@@ -155,16 +160,50 @@ bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r,
 
 #include "SkCanvas.h"
 
-static bool drawRectsIntoMask(const SkRect rects[], int count, SkMask* mask) {
-    rects[0].roundOut(&mask->fBounds);
+static bool prepare_to_draw_into_mask(const SkRect& bounds, SkMask* mask) {
+    SkASSERT(mask != NULL);
+
+    bounds.roundOut(&mask->fBounds);
     mask->fRowBytes = SkAlign4(mask->fBounds.width());
     mask->fFormat = SkMask::kA8_Format;
-    size_t size = mask->computeImageSize();
+    const size_t size = mask->computeImageSize();
     mask->fImage = SkMask::AllocImage(size);
     if (NULL == mask->fImage) {
         return false;
     }
+
+    // FIXME: use sk_calloc in AllocImage?
     sk_bzero(mask->fImage, size);
+    return true;
+}
+
+static bool draw_rrect_into_mask(const SkRRect rrect, SkMask* mask) {
+    if (!prepare_to_draw_into_mask(rrect.rect(), mask)) {
+        return false;
+    }
+
+    // FIXME: This code duplicates code in draw_rects_into_mask, below. Is there a
+    // clean way to share more code?
+    SkBitmap bitmap;
+    bitmap.setConfig(SkBitmap::kA8_Config,
+                     mask->fBounds.width(), mask->fBounds.height(),
+                     mask->fRowBytes);
+    bitmap.setPixels(mask->fImage);
+
+    SkCanvas canvas(bitmap);
+    canvas.translate(-SkIntToScalar(mask->fBounds.left()),
+                     -SkIntToScalar(mask->fBounds.top()));
+
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas.drawRRect(rrect, paint);
+    return true;
+}
+
+static bool draw_rects_into_mask(const SkRect rects[], int count, SkMask* mask) {
+    if (!prepare_to_draw_into_mask(rects[0], mask)) {
+        return false;
+    }
 
     SkBitmap bitmap;
     bitmap.setConfig(SkBitmap::kA8_Config,
@@ -197,6 +236,114 @@ static bool rect_exceeds(const SkRect& r, SkScalar v) {
            r.width() > v || r.height() > v;
 }
 
+SkMaskFilter::FilterReturn
+SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix,
+                                        const SkIRect& clipBounds,
+                                        NinePatch* patch) const {
+    SkASSERT(patch != NULL);
+    switch (rrect.getType()) {
+        case SkRRect::kUnknown_Type:
+            // Unknown should never be returned.
+            SkASSERT(false);
+            // Fall through.
+        case SkRRect::kEmpty_Type:
+            // Nothing to draw.
+            return kFalse_FilterReturn;
+
+        case SkRRect::kRect_Type:
+            // We should have caught this earlier.
+            SkASSERT(false);
+            // Fall through.
+        case SkRRect::kOval_Type:
+            // The nine patch special case does not handle ovals, and we
+            // already have code for rectangles.
+            return kUnimplemented_FilterReturn;
+
+        case SkRRect::kSimple_Type:
+            // Fall through.
+        case SkRRect::kComplex_Type:
+            // These can take advantage of this fast path.
+            break;
+    }
+
+    // TODO: report correct metrics for innerstyle, where we do not grow the
+    // total bounds, but we do need an inset the size of our blur-radius
+    if (SkBlurMaskFilter::kInner_BlurStyle == fBlurStyle) {
+        return kUnimplemented_FilterReturn;
+    }
+
+    // TODO: take clipBounds into account to limit our coordinates up front
+    // for now, just skip too-large src rects (to take the old code path).
+    if (rect_exceeds(rrect.rect(), SkIntToScalar(32767))) {
+        return kUnimplemented_FilterReturn;
+    }
+
+    SkIPoint margin;
+    SkMask  srcM, dstM;
+    rrect.rect().roundOut(&srcM.fBounds);
+    srcM.fImage = NULL;
+    srcM.fFormat = SkMask::kA8_Format;
+    srcM.fRowBytes = 0;
+
+    if (!this->filterMask(&dstM, srcM, matrix, &margin)) {
+        return kFalse_FilterReturn;
+    }
+
+    // Now figure out the appropriate width and height of the smaller round rectangle
+    // to stretch. It will take into account the larger radius per side as well as double
+    // the margin, to account for inner and outer blur.
+    const SkVector& UL = rrect.radii(SkRRect::kUpperLeft_Corner);
+    const SkVector& UR = rrect.radii(SkRRect::kUpperRight_Corner);
+    const SkVector& LR = rrect.radii(SkRRect::kLowerRight_Corner);
+    const SkVector& LL = rrect.radii(SkRRect::kLowerLeft_Corner);
+
+    const SkScalar leftUnstretched = SkTMax(UL.fX, LL.fX) + SkIntToScalar(2 * margin.fX);
+    const SkScalar rightUnstretched = SkTMax(UR.fX, LR.fX) + SkIntToScalar(2 * margin.fX);
+
+    // Extra space in the middle to ensure an unchanging piece for stretching. Use 3 to cover
+    // any fractional space on either side plus 1 for the part to stretch.
+    const SkScalar stretchSize = SkIntToScalar(3);
+
+    const SkScalar totalSmallWidth = leftUnstretched + rightUnstretched + stretchSize;
+    if (totalSmallWidth >= rrect.rect().width()) {
+        // There is no valid piece to stretch.
+        return kUnimplemented_FilterReturn;
+    }
+
+    const SkScalar topUnstretched = SkTMax(UL.fY, UR.fY) + SkIntToScalar(2 * margin.fY);
+    const SkScalar bottomUnstretched = SkTMax(LL.fY, LR.fY) + SkIntToScalar(2 * margin.fY);
+
+    const SkScalar totalSmallHeight = topUnstretched + bottomUnstretched + stretchSize;
+    if (totalSmallHeight >= rrect.rect().height()) {
+        // There is no valid piece to stretch.
+        return kUnimplemented_FilterReturn;
+    }
+
+    SkRect smallR = SkRect::MakeWH(totalSmallWidth, totalSmallHeight);
+
+    SkRRect smallRR;
+    SkVector radii[4];
+    radii[SkRRect::kUpperLeft_Corner] = UL;
+    radii[SkRRect::kUpperRight_Corner] = UR;
+    radii[SkRRect::kLowerRight_Corner] = LR;
+    radii[SkRRect::kLowerLeft_Corner] = LL;
+    smallRR.setRectRadii(smallR, radii);
+
+    if (!draw_rrect_into_mask(smallRR, &srcM)) {
+        return kFalse_FilterReturn;
+    }
+
+    if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
+        return kFalse_FilterReturn;
+    }
+
+    patch->fMask.fBounds.offsetTo(0, 0);
+    patch->fOuterRect = dstM.fBounds;
+    patch->fCenter.fX = SkScalarCeilToInt(leftUnstretched) + 1;
+    patch->fCenter.fY = SkScalarCeilToInt(topUnstretched) + 1;
+    return kTrue_FilterReturn;
+}
+
 #ifdef SK_IGNORE_FAST_RECT_BLUR
 SK_CONF_DECLARE( bool, c_analyticBlurNinepatch, "mask.filter.analyticNinePatch", false, "Use the faster analytic blur approach for ninepatch rects" );
 #else
@@ -303,7 +450,7 @@ SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
     }
 
     if (count > 1 || !c_analyticBlurNinepatch) {
-        if (!drawRectsIntoMask(smallR, count, &srcM)) {
+        if (!draw_rects_into_mask(smallR, count, &srcM)) {
             return kFalse_FilterReturn;
         }
 
index 77aa7a353ce74ff5b0562238842751f94f2a22ce..de71253d92c9102b4573f12c2b310e9a8903f9a4 100644 (file)
@@ -27,6 +27,7 @@
 #include "SkPDFTypes.h"
 #include "SkPDFUtils.h"
 #include "SkRect.h"
+#include "SkRRect.h"
 #include "SkString.h"
 #include "SkTextFormatParams.h"
 #include "SkTemplates.h"
@@ -967,6 +968,13 @@ void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& rect,
                           &content.entry()->fContent);
 }
 
+void SkPDFDevice::drawRRect(const SkDraw& draw, const SkRRect& rrect,
+                            const SkPaint& paint) {
+    SkPath  path;
+    path.addRRect(rrect);
+    this->drawPath(draw, path, paint, NULL, true);
+}
+
 void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
                            const SkPaint& paint, const SkMatrix* prePathMatrix,
                            bool pathIsMutable) {