Use nine patch drawing for blurry round rectangles
Cherry pick of https://codereview.chromium.org/52703003/ in Skia.
Add SkRRect::transform.
Much like SkPath::transform, it transforms an SkRRect based on an
SkMatrix. Unlike SkPath::transform, it will fail for matrices that
contain perspective or skewing.
Cherry pick of https://codereview.chromium.org/48623006/ in Skia.
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.
BUG:11174385
Change-Id: I96e6bf50c1418f7df70681afee6e25be40615497
diff --git a/include/core/SkDraw.h b/include/core/SkDraw.h
index 66c82c9..a0f1dd9 100644
--- a/include/core/SkDraw.h
+++ b/include/core/SkDraw.h
@@ -24,6 +24,7 @@
class SkRasterClip;
struct SkDrawProcs;
struct SkRect;
+class SkRRect;
class SkDraw {
public:
@@ -34,6 +35,7 @@
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 fda1289..9a9dd33 100644
--- a/include/core/SkMaskFilter.h
+++ b/include/core/SkMaskFilter.h
@@ -20,6 +20,7 @@
class SkMatrix;
class SkPath;
class SkRasterClip;
+class SkRRect;
/** \class SkMaskFilter
@@ -160,6 +161,12 @@
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;
@@ -173,6 +180,14 @@
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/core/SkRRect.h b/include/core/SkRRect.h
index bce896a..6a28bd3 100644
--- a/include/core/SkRRect.h
+++ b/include/core/SkRRect.h
@@ -12,6 +12,7 @@
#include "SkPoint.h"
class SkPath;
+class SkMatrix;
// Path forward:
// core work
@@ -277,6 +278,17 @@
*/
uint32_t readFromMemory(const void* buffer);
+ /**
+ * Transform by the specified matrix, and put the result in dst.
+ *
+ * @param matrix SkMatrix specifying the transform. Must only contain
+ * scale and/or translate, or this call will fail.
+ * @param dst SkRRect to store the result. It is an error to use this,
+ * which would make this function no longer const.
+ * @return true on success, false on failure. If false, dst is unmodified.
+ */
+ bool transform(const SkMatrix& matrix, SkRRect* dst) const;
+
private:
SkRect fRect;
// Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[]
diff --git a/include/pdf/SkPDFDevice.h b/include/pdf/SkPDFDevice.h
index 53e9fc7..aae53f9 100644
--- a/include/pdf/SkPDFDevice.h
+++ b/include/pdf/SkPDFDevice.h
@@ -32,6 +32,7 @@
class SkPDFResourceDict;
class SkPDFShader;
class SkPDFStream;
+class SkRRect;
template <typename T> class SkTSet;
// Private classes.
@@ -84,6 +85,8 @@
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/SkDevice.cpp b/src/core/SkDevice.cpp
index 408cda2..9cec9bd 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -356,11 +356,7 @@
void SkDevice::drawRRect(const SkDraw& draw, const SkRRect& rrect, const SkPaint& paint) {
CHECK_FOR_NODRAW_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 SkDevice::drawPath(const SkDraw& draw, const SkPath& path,
diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp
index a9d5fbb..2d70774 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 @@
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) const {
SkDEBUGCODE(this->validate();)
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp
index 9c367f9..cd25716 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 @@
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 {
@@ -267,6 +288,12 @@
}
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 {
return kUnimplemented_FilterReturn;
diff --git a/src/core/SkRRect.cpp b/src/core/SkRRect.cpp
index 75af106..33b781f 100644
--- a/src/core/SkRRect.cpp
+++ b/src/core/SkRRect.cpp
@@ -6,6 +6,7 @@
*/
#include "SkRRect.h"
+#include "SkMatrix.h"
///////////////////////////////////////////////////////////////////////////////
@@ -254,6 +255,83 @@
fType = kComplex_Type;
}
+static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
+ const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
+ | SkMatrix::kPerspective_Mask);
+ return (matrix.getType() & m) == 0;
+}
+
+bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
+ if (NULL == dst) {
+ return false;
+ }
+
+ // Assert that the caller is not trying to do this in place, which
+ // would violate const-ness. Do not return false though, so that
+ // if they know what they're doing and want to violate it they can.
+ SkASSERT(dst != this);
+
+ if (matrix.isIdentity()) {
+ *dst = *this;
+ return true;
+ }
+
+ if (!matrix_only_scale_and_translate(matrix)) {
+ return false;
+ }
+
+ SkRect newRect;
+ if (!matrix.mapRect(&newRect, fRect)) {
+ return false;
+ }
+
+ // At this point, this is guaranteed to succeed, so we can modify dst.
+ dst->fRect = newRect;
+
+ // Now scale each corner
+ SkScalar xScale = matrix.getScaleX();
+ const bool flipX = xScale < 0;
+ if (flipX) {
+ xScale = -xScale;
+ }
+ SkScalar yScale = matrix.getScaleY();
+ const bool flipY = yScale < 0;
+ if (flipY) {
+ yScale = -yScale;
+ }
+
+ // Scale the radii without respecting the flip.
+ for (int i = 0; i < 4; ++i) {
+ dst->fRadii[i].fX = SkScalarMul(fRadii[i].fX, xScale);
+ dst->fRadii[i].fY = SkScalarMul(fRadii[i].fY, yScale);
+ }
+
+ // Now swap as necessary.
+ if (flipX) {
+ if (flipY) {
+ // Swap with opposite corners
+ SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
+ SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
+ } else {
+ // Only swap in x
+ SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
+ SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
+ }
+ } else if (flipY) {
+ // Only swap in y
+ SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
+ SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
+ }
+
+ // Since the only transforms that were allowed are scale and translate, the type
+ // remains unchanged.
+ dst->fType = fType;
+
+ SkDEBUGCODE(dst->validate();)
+
+ return true;
+}
+
///////////////////////////////////////////////////////////////////////////////
void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index b54c330..75606ca 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"
@@ -53,6 +54,10 @@
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;
@@ -166,16 +171,50 @@
#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 drawRectsIntoMask, 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 drawRectsIntoMask(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,
@@ -208,6 +247,114 @@
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
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 73edef9..cdc058c 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"
@@ -960,6 +961,13 @@
&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) {
diff --git a/tests/RoundRectTest.cpp b/tests/RoundRectTest.cpp
index 93f5e7d..e2965d5 100644
--- a/tests/RoundRectTest.cpp
+++ b/tests/RoundRectTest.cpp
@@ -6,10 +6,11 @@
*/
#include "Test.h"
+#include "SkMatrix.h"
#include "SkRRect.h"
-static const SkScalar kWidth = 100.0f;
-static const SkScalar kHeight = 100.0f;
+static const SkScalar kWidth = SkFloatToScalar(100.0f);
+static const SkScalar kHeight = SkFloatToScalar(100.0f);
static void test_inset(skiatest::Reporter* reporter) {
SkRRect rr, rr2;
@@ -451,6 +452,206 @@
}
}
+// Called for a matrix that should cause SkRRect::transform to fail.
+static void assert_transform_failure(skiatest::Reporter* reporter, const SkRRect& orig,
+ const SkMatrix& matrix) {
+ // The test depends on the fact that the original is not empty.
+ SkASSERT(!orig.isEmpty());
+ SkRRect dst;
+ dst.setEmpty();
+
+ const SkRRect copyOfDst = dst;
+ const SkRRect copyOfOrig = orig;
+ bool success = orig.transform(matrix, &dst);
+ // This transform should fail.
+ REPORTER_ASSERT(reporter, !success);
+ // Since the transform failed, dst should be unchanged.
+ REPORTER_ASSERT(reporter, copyOfDst == dst);
+ // original should not be modified.
+ REPORTER_ASSERT(reporter, copyOfOrig == orig);
+ REPORTER_ASSERT(reporter, orig != dst);
+}
+
+#define GET_RADII \
+ const SkVector& origUL = orig.radii(SkRRect::kUpperLeft_Corner); \
+ const SkVector& origUR = orig.radii(SkRRect::kUpperRight_Corner); \
+ const SkVector& origLR = orig.radii(SkRRect::kLowerRight_Corner); \
+ const SkVector& origLL = orig.radii(SkRRect::kLowerLeft_Corner); \
+ const SkVector& dstUL = dst.radii(SkRRect::kUpperLeft_Corner); \
+ const SkVector& dstUR = dst.radii(SkRRect::kUpperRight_Corner); \
+ const SkVector& dstLR = dst.radii(SkRRect::kLowerRight_Corner); \
+ const SkVector& dstLL = dst.radii(SkRRect::kLowerLeft_Corner)
+
+// Called to test various transforms on a single SkRRect.
+static void test_transform_helper(skiatest::Reporter* reporter, const SkRRect& orig) {
+ SkRRect dst;
+ dst.setEmpty();
+
+ // The identity matrix will duplicate the rrect.
+ bool success = orig.transform(SkMatrix::I(), &dst);
+ REPORTER_ASSERT(reporter, success);
+ REPORTER_ASSERT(reporter, orig == dst);
+
+ // Skew and Perspective make transform fail.
+ SkMatrix matrix;
+ matrix.reset();
+ matrix.setSkewX(SkIntToScalar(2));
+ assert_transform_failure(reporter, orig, matrix);
+
+ matrix.reset();
+ matrix.setSkewY(SkIntToScalar(3));
+ assert_transform_failure(reporter, orig, matrix);
+
+ matrix.reset();
+ matrix.setPerspX(SkScalarToPersp(SkIntToScalar(4)));
+ assert_transform_failure(reporter, orig, matrix);
+
+ matrix.reset();
+ matrix.setPerspY(SkScalarToPersp(SkIntToScalar(5)));
+ assert_transform_failure(reporter, orig, matrix);
+
+ // Rotation fails.
+ matrix.reset();
+ matrix.setRotate(SkIntToScalar(90));
+ assert_transform_failure(reporter, orig, matrix);
+ matrix.setRotate(SkIntToScalar(37));
+ assert_transform_failure(reporter, orig, matrix);
+
+ // Translate will keep the rect moved, but otherwise the same.
+ matrix.reset();
+ SkScalar translateX = SkIntToScalar(32);
+ SkScalar translateY = SkIntToScalar(15);
+ matrix.setTranslateX(translateX);
+ matrix.setTranslateY(translateY);
+ dst.setEmpty();
+ success = orig.transform(matrix, &dst);
+ REPORTER_ASSERT(reporter, success);
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter,
+ orig.radii((SkRRect::Corner) i) == dst.radii((SkRRect::Corner) i));
+ }
+ REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
+ REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
+ REPORTER_ASSERT(reporter, dst.rect().left() == orig.rect().left() + translateX);
+ REPORTER_ASSERT(reporter, dst.rect().top() == orig.rect().top() + translateY);
+
+ // Keeping the translation, but adding skew will make transform fail.
+ matrix.setSkewY(SkIntToScalar(7));
+ assert_transform_failure(reporter, orig, matrix);
+
+ // Scaling in -x will flip the round rect horizontally.
+ matrix.reset();
+ matrix.setScaleX(SkIntToScalar(-1));
+ dst.setEmpty();
+ success = orig.transform(matrix, &dst);
+ REPORTER_ASSERT(reporter, success);
+ {
+ GET_RADII;
+ // Radii have swapped in x.
+ REPORTER_ASSERT(reporter, origUL == dstUR);
+ REPORTER_ASSERT(reporter, origUR == dstUL);
+ REPORTER_ASSERT(reporter, origLR == dstLL);
+ REPORTER_ASSERT(reporter, origLL == dstLR);
+ }
+ // Width and height remain the same.
+ REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
+ REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
+ // Right and left have swapped (sort of)
+ REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left());
+ // Top has stayed the same.
+ REPORTER_ASSERT(reporter, orig.rect().top() == dst.rect().top());
+
+ // Keeping the scale, but adding a persp will make transform fail.
+ matrix.setPerspX(SkScalarToPersp(SkIntToScalar(7)));
+ assert_transform_failure(reporter, orig, matrix);
+
+ // Scaling in -y will flip the round rect vertically.
+ matrix.reset();
+ matrix.setScaleY(SkIntToScalar(-1));
+ dst.setEmpty();
+ success = orig.transform(matrix, &dst);
+ REPORTER_ASSERT(reporter, success);
+ {
+ GET_RADII;
+ // Radii have swapped in y.
+ REPORTER_ASSERT(reporter, origUL == dstLL);
+ REPORTER_ASSERT(reporter, origUR == dstLR);
+ REPORTER_ASSERT(reporter, origLR == dstUR);
+ REPORTER_ASSERT(reporter, origLL == dstUL);
+ }
+ // Width and height remain the same.
+ REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
+ REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
+ // Top and bottom have swapped (sort of)
+ REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom());
+ // Left has stayed the same.
+ REPORTER_ASSERT(reporter, orig.rect().left() == dst.rect().left());
+
+ // Scaling in -x and -y will swap in both directions.
+ matrix.reset();
+ matrix.setScaleY(SkIntToScalar(-1));
+ matrix.setScaleX(SkIntToScalar(-1));
+ dst.setEmpty();
+ success = orig.transform(matrix, &dst);
+ REPORTER_ASSERT(reporter, success);
+ {
+ GET_RADII;
+ REPORTER_ASSERT(reporter, origUL == dstLR);
+ REPORTER_ASSERT(reporter, origUR == dstLL);
+ REPORTER_ASSERT(reporter, origLR == dstUL);
+ REPORTER_ASSERT(reporter, origLL == dstUR);
+ }
+ // Width and height remain the same.
+ REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
+ REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
+ REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom());
+ REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left());
+
+ // Scale in both directions.
+ SkScalar xScale = SkIntToScalar(3);
+ SkScalar yScale = SkFloatToScalar(3.2f);
+ matrix.reset();
+ matrix.setScaleX(xScale);
+ matrix.setScaleY(yScale);
+ dst.setEmpty();
+ success = orig.transform(matrix, &dst);
+ REPORTER_ASSERT(reporter, success);
+ // Radii are scaled.
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fX,
+ SkScalarMul(orig.radii((SkRRect::Corner) i).fX, xScale)));
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fY,
+ SkScalarMul(orig.radii((SkRRect::Corner) i).fY, yScale)));
+ }
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().width(),
+ SkScalarMul(orig.rect().width(), xScale)));
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().height(),
+ SkScalarMul(orig.rect().height(), yScale)));
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().left(),
+ SkScalarMul(orig.rect().left(), xScale)));
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().top(),
+ SkScalarMul(orig.rect().top(), yScale)));
+}
+
+static void test_round_rect_transform(skiatest::Reporter* reporter) {
+ SkRRect rrect;
+ {
+ SkRect r = { 0, 0, kWidth, kHeight };
+ rrect.setRectXY(r, SkIntToScalar(4), SkIntToScalar(7));
+ test_transform_helper(reporter, rrect);
+ }
+ {
+ SkRect r = { SkIntToScalar(5), SkIntToScalar(15),
+ SkIntToScalar(27), SkIntToScalar(34) };
+ SkVector radii[4] = { { 0, SkIntToScalar(1) },
+ { SkIntToScalar(2), SkIntToScalar(3) },
+ { SkIntToScalar(4), SkIntToScalar(5) },
+ { SkIntToScalar(6), SkIntToScalar(7) } };
+ rrect.setRectRadii(r, radii);
+ test_transform_helper(reporter, rrect);
+ }
+}
+
static void TestRoundRect(skiatest::Reporter* reporter) {
test_round_rect_basic(reporter);
test_round_rect_rects(reporter);
@@ -459,6 +660,7 @@
test_round_rect_iffy_parameters(reporter);
test_inset(reporter);
test_round_rect_contains_rect(reporter);
+ test_round_rect_transform(reporter);
}
#include "TestClassDef.h"