From a8e33a92e27ca1523601226cad83c79a7e00c93b Mon Sep 17 00:00:00 2001 From: "scroggo@google.com" Date: Fri, 8 Nov 2013 18:02:53 +0000 Subject: [PATCH] Add ability to ninepatch blurred rounded rectangle 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 | 2 + include/core/SkMaskFilter.h | 15 +++ include/pdf/SkPDFDevice.h | 3 + src/core/SkBitmapDevice.cpp | 6 +- src/core/SkDraw.cpp | 46 +++++++++ src/core/SkMaskFilter.cpp | 27 ++++++ src/effects/SkBlurMaskFilter.cpp | 155 ++++++++++++++++++++++++++++++- src/pdf/SkPDFDevice.cpp | 8 ++ 8 files changed, 253 insertions(+), 9 deletions(-) diff --git a/include/core/SkDraw.h b/include/core/SkDraw.h index 9183094679..f7ae1c6208 100644 --- a/include/core/SkDraw.h +++ b/include/core/SkDraw.h @@ -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. diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h index 93e2d4bc33..f4448ddddb 100644 --- a/include/core/SkMaskFilter.h +++ b/include/core/SkMaskFilter.h @@ -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; }; diff --git a/include/pdf/SkPDFDevice.h b/include/pdf/SkPDFDevice.h index d8e1aa283f..8e1f41823f 100644 --- a/include/pdf/SkPDFDevice.h +++ b/include/pdf/SkPDFDevice.h @@ -33,6 +33,7 @@ class SkPDFObject; class SkPDFResourceDict; class SkPDFShader; class SkPDFStream; +class SkRRect; template 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; diff --git a/src/core/SkBitmapDevice.cpp b/src/core/SkBitmapDevice.cpp index eac21e2d48..b5ce122eb8 100644 --- a/src/core/SkBitmapDevice.cpp +++ b/src/core/SkBitmapDevice.cpp @@ -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, diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp index af4f1ca047..f282bf5b7b 100644 --- a/src/core/SkDraw.cpp +++ b/src/core/SkDraw.cpp @@ -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 { diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp index 9c367f946b..cd25716377 100644 --- a/src/core/SkMaskFilter.cpp +++ b/src/core/SkMaskFilter.cpp @@ -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 { diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index a2e8065284..17690236ff 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -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; } diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp index 77aa7a353c..de71253d92 100644 --- a/src/pdf/SkPDFDevice.cpp +++ b/src/pdf/SkPDFDevice.cpp @@ -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) { -- 2.34.1