use SkPath::isNestedRects() to apply blurred nine-patch
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 19 Nov 2012 16:45:14 +0000 (16:45 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 19 Nov 2012 16:45:14 +0000 (16:45 +0000)
Review URL: https://codereview.appspot.com/6855063

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

include/core/SkMaskFilter.h
src/core/SkMaskFilter.cpp
src/effects/SkBlurMaskFilter.cpp

index 6bdc533..3060e2f 100644 (file)
@@ -111,6 +111,12 @@ protected:
         kUnimplemented_FilterReturn
     };
 
+    struct NinePatch {
+        SkMask      fMask;      // fBounds must have [0,0] in its top-left
+        SkIRect     fOuterRect; // width/height must be >= fMask.fBounds'
+        SkIPoint    fCenter;    // identifies center row/col for stretching
+    };
+
     /**
      *  Override if your subclass can filter a rect, and return the answer as
      *  a ninepatch mask to be stretched over the returned outerRect. On success
@@ -126,10 +132,10 @@ protected:
      *  the caller will call mask.fBounds.centerX() and centerY() to find the
      *  strips that will be replicated.
      */
-    virtual FilterReturn filterRectToNine(const SkRect&, const SkMatrix&,
-                                          const SkIRect& clipBounds,
-                                          SkMask* ninePatchMask,
-                                          SkIRect* outerRect);
+    virtual FilterReturn filterRectsToNine(const SkRect[], int count,
+                                           const SkMatrix&,
+                                           const SkIRect& clipBounds,
+                                           NinePatch*);
 
 private:
     friend class SkDraw;
index 90925f1..e027178 100644 (file)
@@ -66,9 +66,10 @@ static void dump(const SkMask& mask) {
 #endif
 
 static void draw_nine_clipped(const SkMask& mask, const SkIRect& outerR,
+                              const SkIPoint& center, bool fillCenter,
                               const SkIRect& clipR, SkBlitter* blitter) {
-    int cx = mask.fBounds.centerX();
-    int cy = mask.fBounds.centerY();
+    int cx = center.x();
+    int cy = center.y();
     SkMask m;
 
     // top-left
@@ -109,7 +110,9 @@ static void draw_nine_clipped(const SkMask& mask, const SkIRect& outerR,
                outerR.top() + cy - mask.fBounds.top(),
                outerR.right() + (cx + 1 - mask.fBounds.right()),
                outerR.bottom() + (cy + 1 - mask.fBounds.bottom()));
-    blitClippedRect(blitter, innerR, clipR);
+    if (fillCenter) {
+        blitClippedRect(blitter, innerR, clipR);
+    }
 
     const int innerW = innerR.width();
     size_t storageSize = (innerW + 1) * (sizeof(int16_t) + sizeof(uint8_t));
@@ -169,6 +172,7 @@ static void draw_nine_clipped(const SkMask& mask, const SkIRect& outerR,
 }
 
 static void draw_nine(const SkMask& mask, const SkIRect& outerR,
+                      const SkIPoint& center, bool fillCenter,
                       const SkRasterClip& clip, SkBounder* bounder,
                       SkBlitter* blitter) {
     // if we get here, we need to (possibly) resolve the clip and blitter
@@ -180,33 +184,45 @@ static void draw_nine(const SkMask& mask, const SkIRect& outerR,
     if (!clipper.done() && (!bounder || bounder->doIRect(outerR))) {
         const SkIRect& cr = clipper.rect();
         do {
-            draw_nine_clipped(mask, outerR, cr, blitter);
+            draw_nine_clipped(mask, outerR, center, fillCenter, cr, blitter);
             clipper.next();
         } while (!clipper.done());
     }
 }
 
+static int countNestedRects(const SkPath& path, SkRect rects[2]) {
+    if (path.isNestedRects(rects)) {
+        return 2;
+    }
+    return path.isRect(&rects[0]);
+}
+
 bool SkMaskFilter::filterPath(const SkPath& devPath, const SkMatrix& matrix,
                               const SkRasterClip& clip, SkBounder* bounder,
                               SkBlitter* blitter, SkPaint::Style style) {
-    SkRect rect;
-    if (!SK_IGNORE_FAST_BLURRECT &&
-                devPath.isRect(&rect) && SkPaint::kFill_Style == style) {
-        SkMask  mask;
-        SkIRect outerBounds;
-
-        mask.fImage = NULL;
-        switch (this->filterRectToNine(rect, matrix, clip.getBounds(), &mask,
-                                       &outerBounds)) {
+    SkRect rects[2];
+    int rectCount = 0;
+    if (!SK_IGNORE_FAST_BLURRECT && SkPaint::kFill_Style == style) {
+        rectCount = countNestedRects(devPath, rects);
+    }
+    if (rectCount > 0) {
+        NinePatch patch;
+
+        patch.fMask.fImage = NULL;
+        switch (this->filterRectsToNine(rects, rectCount, matrix,
+                                        clip.getBounds(), &patch)) {
             case kFalse_FilterReturn:
-                SkASSERT(!mask.fImage);
+                SkASSERT(NULL == patch.fMask.fImage);
                 return false;
+
             case kTrue_FilterReturn:
-                draw_nine(mask, outerBounds, clip, bounder, blitter);
-                SkMask::FreeImage(mask.fImage);
+                draw_nine(patch.fMask, patch.fOuterRect, patch.fCenter,
+                          1 == rectCount, clip, bounder, blitter);
+                SkMask::FreeImage(patch.fMask.fImage);
                 return true;
+
             case kUnimplemented_FilterReturn:
-                SkASSERT(!mask.fImage);
+                SkASSERT(NULL == patch.fMask.fImage);
                 // fall through
                 break;
         }
@@ -244,10 +260,8 @@ bool SkMaskFilter::filterPath(const SkPath& devPath, const SkMatrix& matrix,
 }
 
 SkMaskFilter::FilterReturn
-SkMaskFilter::filterRectToNine(const SkRect&, const SkMatrix&,
-                               const SkIRect& clipBounds,
-                               SkMask* ninePatchMask,
-                               SkIRect* outerRect) {
+SkMaskFilter::filterRectsToNine(const SkRect[], int count, const SkMatrix&,
+                                const SkIRect& clipBounds, NinePatch*) {
     return kUnimplemented_FilterReturn;
 }
 
index c42bd8c..467af23 100644 (file)
@@ -27,10 +27,9 @@ public:
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurMaskFilterImpl)
 
 protected:
-    virtual FilterReturn filterRectToNine(const SkRect&, const SkMatrix&,
-                                          const SkIRect& clipBounds,
-                                          SkMask* ninePatchMask,
-                                          SkIRect* outerRect) SK_OVERRIDE;
+    virtual FilterReturn filterRectsToNine(const SkRect[], int count, const SkMatrix&,
+                                           const SkIRect& clipBounds,
+                                           NinePatch*) SK_OVERRIDE;
 
 private:
     SkScalar                    fRadius;
@@ -104,8 +103,8 @@ bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
 
 #include "SkCanvas.h"
 
-static bool drawRectIntoMask(const SkRect& r, SkMask* mask) {
-    r.roundOut(&mask->fBounds);
+static bool drawRectsIntoMask(const SkRect rects[], int count, SkMask* mask) {
+    rects[0].roundOut(&mask->fBounds);
     mask->fRowBytes = SkAlign4(mask->fBounds.width());
     mask->fFormat = SkMask::kA8_Format;
     size_t size = mask->computeImageSize();
@@ -122,24 +121,37 @@ static bool drawRectIntoMask(const SkRect& r, SkMask* mask) {
     bitmap.setPixels(mask->fImage);
 
     SkCanvas canvas(bitmap);
-    canvas.translate(-SkScalarFloorToScalar(r.left()),
-                    -SkScalarFloorToScalar(r.top()));
+    canvas.translate(-SkIntToScalar(mask->fBounds.left()),
+                     -SkIntToScalar(mask->fBounds.top()));
 
     SkPaint paint;
     paint.setAntiAlias(true);
 
-    canvas.drawRect(r, paint);
+    if (1 == count) {
+        canvas.drawRect(rects[0], paint);
+    } else {
+        // todo: do I need a fast way to do this?
+        SkPath path;
+        path.addRect(rects[0]);
+        path.addRect(rects[1]);
+        path.setFillType(SkPath::kEvenOdd_FillType);
+        canvas.drawPath(path, paint);
+    }
     return true;
 }
 
 SkMaskFilter::FilterReturn
-SkBlurMaskFilterImpl::filterRectToNine(const SkRect& rect, const SkMatrix& matrix,
-                                       const SkIRect& clipBounds,
-                                       SkMask* ninePatchMask,
-                                       SkIRect* outerRect) {
+SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
+                                        const SkMatrix& matrix,
+                                        const SkIRect& clipBounds,
+                                        NinePatch* patch) {
+    if (count < 1 || count > 2) {
+        return kUnimplemented_FilterReturn;
+    }
+
     SkIPoint margin;
     SkMask  srcM, dstM;
-    rect.roundOut(&srcM.fBounds);
+    rects[0].roundOut(&srcM.fBounds);
     srcM.fImage = NULL;
     srcM.fFormat = SkMask::kA8_Format;
     srcM.fRowBytes = 0;
@@ -161,35 +173,56 @@ SkBlurMaskFilterImpl::filterRectToNine(const SkRect& rect, const SkMatrix& matri
      *  Thus, in this case, we inset by a total of 5 (on each side) beginning
      *  with our outer-rect (dstM.fBounds)
      */
-    SkRect smallR = rect;
-    {
-        // +3 is from +1 for each edge (to account for possible fractional pixel
-        // edges, and +1 to make room for a center rol/col.
-        int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 3;
-        int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 3;
-        // we want the inset amounts to be integral, so we don't change any
-        // fractional phase on the fRight or fBottom of our smallR.
-        SkScalar dx = SkIntToScalar(srcM.fBounds.width() - smallW);
-        SkScalar dy = SkIntToScalar(srcM.fBounds.height() - smallH);
-        if (dx < 0 || dy < 0) {
-            // we're too small, relative to our blur, to break into nine-patch,
-            // so we ask to have our normal filterMask() be called.
-            return kUnimplemented_FilterReturn;
-        }
-        SkASSERT(dx >= 0 && dy >= 0);
-        smallR.set(rect.left(), rect.top(), rect.right() - dx, rect.bottom() - dy);
-        SkASSERT(!smallR.isEmpty());
+    SkRect smallR[2];
+    SkIPoint center;
+
+    // +2 is from +1 for each edge (to account for possible fractional edges
+    int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2;
+    int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2;
+    SkIRect innerIR;
+
+    if (1 == count) {
+        innerIR = srcM.fBounds;
+        center.set(smallW, smallH);
+    } else {
+        SkASSERT(2 == count);
+        rects[1].roundIn(&innerIR);
+        center.set(smallW + (innerIR.left() - srcM.fBounds.left()),
+                   smallH + (innerIR.top() - srcM.fBounds.top()));
+    }
+
+    // +1 so we get a clean, stretchable, center row/col
+    smallW += 1;
+    smallH += 1;
+
+    // we want the inset amounts to be integral, so we don't change any
+    // fractional phase on the fRight or fBottom of our smallR.
+    const SkScalar dx = SkIntToScalar(innerIR.width() - smallW);
+    const SkScalar dy = SkIntToScalar(innerIR.height() - smallH);
+    if (dx < 0 || dy < 0) {
+        // we're too small, relative to our blur, to break into nine-patch,
+        // so we ask to have our normal filterMask() be called.
+        return kUnimplemented_FilterReturn;
+    }
+
+    smallR[0].set(rects[0].left(), rects[0].top(), rects[0].right() - dx, rects[0].bottom() - dy);
+    SkASSERT(!smallR[0].isEmpty());
+    if (2 == count) {
+        smallR[1].set(rects[1].left(), rects[1].top(),
+                      rects[1].right() - dx, rects[1].bottom() - dy);
+        SkASSERT(!smallR[1].isEmpty());
     }
 
-    if (!drawRectIntoMask(smallR, &srcM)) {
+    if (!drawRectsIntoMask(smallR, count, &srcM)) {
         return kFalse_FilterReturn;
     }
 
-    if (!this->filterMask(ninePatchMask, srcM, matrix, &margin)) {
+    if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
         return kFalse_FilterReturn;
     }
-    ninePatchMask->fBounds.offsetTo(0, 0);
-    *outerRect = dstM.fBounds;
+    patch->fMask.fBounds.offsetTo(0, 0);
+    patch->fOuterRect = dstM.fBounds;
+    patch->fCenter = center;
     return kTrue_FilterReturn;
 }