add unittest for SkTreatAsSprite
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 2 Jan 2013 20:19:45 +0000 (20:19 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 2 Jan 2013 20:19:45 +0000 (20:19 +0000)
Review URL: https://codereview.appspot.com/7042044

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

src/core/SkMatrix.cpp
src/core/SkMatrixUtils.h [new file with mode: 0644]
tests/DrawBitmapRectTest.cpp

index 8dee0cb..4845f59 100644 (file)
@@ -1802,3 +1802,52 @@ void SkMatrix::toDumpString(SkString* str) const {
     SkFractToFloat(fMat[6]), SkFractToFloat(fMat[7]), SkFractToFloat(fMat[8]));
 #endif
 }
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkMatrixUtils.h"
+
+bool SkTreatAsSprite(const SkMatrix& mat, const SkRect& src,
+                     unsigned subpixelBits) {
+    if (mat.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
+        return false;
+    }
+    
+    // quick success check
+    if (!subpixelBits && !(mat.getType() & ~SkMatrix::kTranslate_Mask)) {
+        return true;
+    }
+    
+    // mapRect supports negative scales, so we eliminate those first
+    if (mat.getScaleX() < 0 || mat.getScaleY() < 0) {
+        return false;
+    }
+    
+    SkRect dst;
+    SkIRect isrc, idst;
+    
+    mat.mapRect(&dst, src);
+    
+    {
+        SkRect tmp = src;
+        tmp.offset(mat.getTranslateX(), mat.getTranslateY());
+        tmp.round(&isrc);
+    }
+    
+    if (subpixelBits) {
+        isrc.fLeft <<= subpixelBits;
+        isrc.fTop <<= subpixelBits;
+        isrc.fRight <<= subpixelBits;
+        isrc.fBottom <<= subpixelBits;
+        
+        const float scale = 1 << subpixelBits;
+        dst.fLeft *= scale;
+        dst.fTop *= scale;
+        dst.fRight *= scale;
+        dst.fBottom *= scale;
+    }
+    
+    dst.round(&idst);
+    return isrc == idst;
+}
+
diff --git a/src/core/SkMatrixUtils.h b/src/core/SkMatrixUtils.h
new file mode 100644 (file)
index 0000000..db7370c
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkMatrixUtils_DEFINED
+#define SkMatrixUtils_DEFINED
+
+#include "SkMatrix.h"
+
+/**
+ *  Number of subpixel bits used in skia's bilerp.
+ *  See SkBitmapProcState_procs.h and SkBitmapProcState_filter.h
+ */
+#define kSkSubPixelBitsForBilerp   4
+
+/**
+ *  Given a matrix and a src-rect, return true if the computed dst-rect would
+ *  align such that there is a 1-to-1 coorspondence between src and dst pixels.
+ *  This can be called by drawing code to see if drawBitmap can be turned into
+ *  drawSprite (which is faster).
+ *
+ *  The "closeness" test is based on the subpixelBits parameter. Pass 0 for
+ *  round-to-nearest behavior (e.g. nearest neighbor sampling). Pass the number
+ *  of subpixel-bits to simulate filtering.
+ */
+bool SkTreatAsSprite(const SkMatrix&, const SkRect& src, unsigned subpixelBits);
+    
+
+#endif
index 7dc51c0..0a8d730 100644 (file)
@@ -9,6 +9,90 @@
 #include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkShader.h"
+#include "SkRandom.h"
+#include "SkMatrixUtils.h"
+
+static void rand_matrix(SkMatrix* mat, SkRandom& rand, unsigned mask) {
+    mat->setIdentity();
+    if (mask & SkMatrix::kTranslate_Mask) {
+        mat->postTranslate(rand.nextSScalar1(), rand.nextSScalar1());
+    }
+    if (mask & SkMatrix::kScale_Mask) {
+        mat->postScale(rand.nextSScalar1(), rand.nextSScalar1());
+    }
+    if (mask & SkMatrix::kAffine_Mask) {
+        mat->postRotate(rand.nextSScalar1() * 360);
+    }
+    if (mask & SkMatrix::kPerspective_Mask) {
+        mat->setPerspX(rand.nextSScalar1());
+        mat->setPerspY(rand.nextSScalar1());
+    }
+}
+
+static void rand_rect(SkRect* r, SkRandom& rand) {
+    r->set(rand.nextSScalar1() * 1000, rand.nextSScalar1() * 1000,
+           rand.nextSScalar1() * 1000, rand.nextSScalar1() * 1000);
+    r->sort();
+}
+
+static void test_treatAsSprite(skiatest::Reporter* reporter) {
+    const unsigned bilerBits = kSkSubPixelBitsForBilerp;
+
+    SkMatrix mat;
+    SkRect r;
+    SkRandom rand;
+
+    // assert: translate-only no-filter can always be treated as sprite
+    for (int i = 0; i < 1000; ++i) {
+        rand_matrix(&mat, rand, SkMatrix::kTranslate_Mask);
+        for (int j = 0; j < 1000; ++j) {
+            rand_rect(&r, rand);
+            REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, r, 0));
+        }
+    }
+    
+    // assert: rotate/perspect is never treated as sprite
+    for (int i = 0; i < 1000; ++i) {
+        rand_matrix(&mat, rand, SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask);
+        for (int j = 0; j < 1000; ++j) {
+            rand_rect(&r, rand);
+            REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, r, 0));
+            REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, r, bilerBits));
+        }
+    }
+
+    r.set(10, 10, 500, 600);
+
+    const SkScalar tooMuchSubpixel = SkFloatToScalar(100.1f);
+    mat.setTranslate(tooMuchSubpixel, 0);
+    REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, r, bilerBits));
+    mat.setTranslate(0, tooMuchSubpixel);
+    REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, r, bilerBits));
+
+    const SkScalar tinySubPixel = SkFloatToScalar(100.02f);
+    mat.setTranslate(tinySubPixel, 0);
+    REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, r, bilerBits));
+    mat.setTranslate(0, tinySubPixel);
+    REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, r, bilerBits));
+    
+    const SkScalar twoThirds = SK_Scalar1 * 2 / 3;
+    const SkScalar bigScale = SkScalarDiv(r.width() + twoThirds, r.width());
+    mat.setScale(bigScale, bigScale);
+    REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, r, false));
+    REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, r, bilerBits));
+    
+    const SkScalar oneThird = SK_Scalar1 / 3;
+    const SkScalar smallScale = SkScalarDiv(r.width() + oneThird, r.width());
+    mat.setScale(smallScale, smallScale);
+    REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, r, false));
+    REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, r, bilerBits));
+    
+    const SkScalar oneFortyth = SK_Scalar1 / 40;
+    const SkScalar tinyScale = SkScalarDiv(r.width() + oneFortyth, r.width());
+    mat.setScale(tinyScale, tinyScale);
+    REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, r, false));
+    REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, r, bilerBits));
+}
 
 static void assert_ifDrawnTo(skiatest::Reporter* reporter,
                              const SkBitmap& bm, bool shouldBeDrawn) {
@@ -179,6 +263,8 @@ static void TestDrawBitmapRect(skiatest::Reporter* reporter) {
 
     test_nan_antihair(reporter);
     test_giantrepeat_crbug118018(reporter);
+
+    test_treatAsSprite(reporter);
 }
 
 #include "TestClassDef.h"