add bitmap::eraseArea

BUG=
R=scroggo@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk@9815 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkBitmap.h b/include/core/SkBitmap.h
index 86e267c..887169c 100644
--- a/include/core/SkBitmap.h
+++ b/include/core/SkBitmap.h
@@ -386,28 +386,38 @@
     */
     void notifyPixelsChanged() const;
 
-    /** Initialize the bitmap's pixels with the specified color+alpha, automatically converting into the correct format
-        for the bitmap's config. If the config is kRGB_565_Config, then the alpha value is ignored.
-        If the config is kA8_Config, then the r,g,b parameters are ignored.
-    */
-    void eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const;
-    /** Initialize the bitmap's pixels with the specified color+alpha, automatically converting into the correct format
-        for the bitmap's config. If the config is kRGB_565_Config, then the alpha value is presumed
-        to be 0xFF. If the config is kA8_Config, then the r,g,b parameters are ignored and the
-        pixels are all set to 0xFF.
-    */
-    void eraseRGB(U8CPU r, U8CPU g, U8CPU b) const {
-        this->eraseARGB(0xFF, r, g, b);
-    }
-    /** Initialize the bitmap's pixels with the specified color, automatically converting into the correct format
-        for the bitmap's config. If the config is kRGB_565_Config, then the color's alpha value is presumed
-        to be 0xFF. If the config is kA8_Config, then only the color's alpha value is used.
-    */
+    /**
+     *  Fill the entire bitmap with the specified color.
+     *  If the bitmap's config does not support alpha (e.g. 565) then the alpha
+     *  of the color is ignored (treated as opaque). If the config only supports
+     *  alpha (e.g. A1 or A8) then the color's r,g,b components are ignored.
+     */
     void eraseColor(SkColor c) const {
         this->eraseARGB(SkColorGetA(c), SkColorGetR(c), SkColorGetG(c),
                         SkColorGetB(c));
     }
 
+    /**
+     *  Fill the entire bitmap with the specified color.
+     *  If the bitmap's config does not support alpha (e.g. 565) then the alpha
+     *  of the color is ignored (treated as opaque). If the config only supports
+     *  alpha (e.g. A1 or A8) then the color's r,g,b components are ignored.
+     */
+    void eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const;
+
+    // DEPRECATED -- call eraseColor or eraseARGB
+    void eraseRGB(U8CPU r, U8CPU g, U8CPU b) const {
+        this->eraseARGB(0xFF, r, g, b);
+    }
+
+    /**
+     *  Fill the specified area of this bitmap with the specified color.
+     *  If the bitmap's config does not support alpha (e.g. 565) then the alpha
+     *  of the color is ignored (treated as opaque). If the config only supports
+     *  alpha (e.g. A1 or A8) then the color's r,g,b components are ignored.
+     */
+    void eraseArea(const SkIRect& area, SkColor c) const;
+
     /** Scroll (a subset of) the contents of this bitmap by dx/dy. If there are
         no pixels allocated (i.e. getPixels() returns null) the method will
         still update the inval region (if present). If the bitmap is immutable,
@@ -665,6 +675,8 @@
     uint8_t     fFlags;
     uint8_t     fBytesPerPixel; // based on config
 
+    void internalErase(const SkIRect&, U8CPU a, U8CPU r, U8CPU g, U8CPU b)const;
+
     /* Internal computations for safe size.
     */
     static Sk64 ComputeSafeSize64(Config   config,
diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp
index f164ca5..07fbe12 100644
--- a/src/core/SkBitmap.cpp
+++ b/src/core/SkBitmap.cpp
@@ -739,11 +739,18 @@
     return SkToU16(pixel);
 }
 
-void SkBitmap::eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const {
+void SkBitmap::internalErase(const SkIRect& area,
+                             U8CPU a, U8CPU r, U8CPU g, U8CPU b) const {
+#ifdef SK_DEBUG
     SkDEBUGCODE(this->validate();)
+    SkASSERT(!area.isEmpty());
+    {
+        SkIRect total = { 0, 0, fWidth, fHeight };
+        SkASSERT(total.contains(area));
+    }
+#endif
 
-    if (0 == fWidth || 0 == fHeight ||
-            kNo_Config == fConfig || kIndex8_Config == fConfig) {
+    if (kNo_Config == fConfig || kIndex8_Config == fConfig) {
         return;
     }
 
@@ -753,8 +760,8 @@
         return;
     }
 
-    int height = fHeight;
-    const int width = fWidth;
+    int height = area.height();
+    const int width = area.width();
     const int rowBytes = fRowBytes;
 
     // make rgb premultiplied
@@ -766,18 +773,39 @@
 
     switch (fConfig) {
         case kA1_Config: {
-            uint8_t* p = (uint8_t*)fPixels;
-            const int count = (width + 7) >> 3;
+            uint8_t* p = this->getAddr1(area.fLeft, area.fTop);
+            const int left = area.fLeft >> 3;
+            const int right = area.fRight >> 3;
+            
+            int middle = right - left - 1;
+
+            uint8_t leftMask = 0xFF >> (area.fLeft & 7);
+            uint8_t rightMask = ~(0xFF >> (area.fRight & 7));
+            if (left == right) {
+                leftMask &= rightMask;
+                rightMask = 0;
+            }
+
             a = (a >> 7) ? 0xFF : 0;
-            SkASSERT(count <= rowBytes);
             while (--height >= 0) {
-                memset(p, a, count);
-                p += rowBytes;
+                uint8_t* startP = p;
+
+                *p = (*p & ~leftMask) | (a & leftMask);
+                p++;
+                if (middle > 0) {
+                    memset(p, a, middle);
+                    p += middle;
+                }
+                if (rightMask) {
+                    *p = (*p & ~rightMask) | (a & rightMask);
+                }
+                
+                p = startP + rowBytes;
             }
             break;
         }
         case kA8_Config: {
-            uint8_t* p = (uint8_t*)fPixels;
+            uint8_t* p = this->getAddr8(area.fLeft, area.fTop);
             while (--height >= 0) {
                 memset(p, a, width);
                 p += rowBytes;
@@ -786,7 +814,7 @@
         }
         case kARGB_4444_Config:
         case kRGB_565_Config: {
-            uint16_t* p = (uint16_t*)fPixels;
+            uint16_t* p = this->getAddr16(area.fLeft, area.fTop);;
             uint16_t v;
 
             if (kARGB_4444_Config == fConfig) {
@@ -803,7 +831,7 @@
             break;
         }
         case kARGB_8888_Config: {
-            uint32_t* p = (uint32_t*)fPixels;
+            uint32_t* p = this->getAddr32(area.fLeft, area.fTop);
             uint32_t  v = SkPackARGB32(a, r, g, b);
 
             while (--height >= 0) {
@@ -817,6 +845,21 @@
     this->notifyPixelsChanged();
 }
 
+void SkBitmap::eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const {
+    SkIRect area = { 0, 0, fWidth, fHeight };
+    if (!area.isEmpty()) {
+        this->internalErase(area, a, r, g, b);
+    }
+}
+
+void SkBitmap::eraseArea(const SkIRect& rect, SkColor c) const {
+    SkIRect area = { 0, 0, fWidth, fHeight };
+    if (area.intersect(rect)) {
+        this->internalErase(area, SkColorGetA(c), SkColorGetR(c),
+                            SkColorGetG(c), SkColorGetB(c));
+    }
+}
+
 //////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/tests/BitmapGetColorTest.cpp b/tests/BitmapGetColorTest.cpp
index 8ada98c..3363777 100644
--- a/tests/BitmapGetColorTest.cpp
+++ b/tests/BitmapGetColorTest.cpp
@@ -1,12 +1,75 @@
-
 /*
  * Copyright 2011 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+
 #include "Test.h"
 #include "SkBitmap.h"
+#include "SkRect.h"
+#include "SkRandom.h"
+
+static int nextRand(SkRandom& rand, int min, int max) {
+    return min + (int)rand.nextRangeU(0, max - min);
+}
+
+static void rand_irect(SkIRect* rect, int W, int H, SkRandom& rand) {
+    const int DX = W / 2;
+    const int DY = H / 2;
+
+    rect->fLeft   = nextRand(rand, -DX, W + DX);
+    rect->fTop    = nextRand(rand, -DY, H + DY);
+    rect->fRight  = nextRand(rand, -DX, W + DX);
+    rect->fBottom = nextRand(rand, -DY, H + DY);
+    rect->sort();
+}
+
+static void test_equal_A1_A8(skiatest::Reporter* reporter,
+                       const SkBitmap& bm1, const SkBitmap& bm8) {
+    SkASSERT(SkBitmap::kA1_Config == bm1.config());
+    SkASSERT(SkBitmap::kA8_Config == bm8.config());
+
+    REPORTER_ASSERT(reporter, bm1.width() == bm8.width());
+    REPORTER_ASSERT(reporter, bm1.height() == bm8.height());
+    for (int y = 0; y < bm1.height(); ++y) {
+        for (int x = 0; x < bm1.width(); ++x) {
+            int p1 = *bm1.getAddr1(x, y) & (1 << (7 - (x & 7)));
+            SkASSERT(SkIsPow2(p1));
+            p1 = p1 ? 0xFF : 0;
+
+            int p8 = *bm8.getAddr8(x, y);
+            SkASSERT(0 == p8 || 0xFF == p8);
+            
+            REPORTER_ASSERT(reporter, p1 == p8);
+        }
+    }
+}
+
+static void test_eraserect_A1(skiatest::Reporter* reporter) {
+    const int W = 43;
+    const int H = 13;
+
+    SkBitmap bm1, bm8;
+    
+    bm1.setConfig(SkBitmap::kA1_Config, W, H);
+    bm1.allocPixels();
+    bm8.setConfig(SkBitmap::kA8_Config, W, H);
+    bm8.allocPixels();
+
+    SkRandom rand;
+    for (int i = 0; i < 10000; ++i) {
+        SkIRect area;
+        rand_irect(&area, W, H, rand);
+        
+        bm1.eraseColor(0);
+        bm8.eraseColor(0);
+        
+        bm1.eraseArea(area, SK_ColorWHITE);
+        bm8.eraseArea(area, SK_ColorWHITE);
+        test_equal_A1_A8(reporter, bm1, bm8);
+    }
+}
 
 static void TestGetColor(skiatest::Reporter* reporter) {
     static const struct Rec {
@@ -25,16 +88,25 @@
         {   SkBitmap::kARGB_8888_Config,    0xFF224466,     0xFF224466  },
     };
 
+    // specify an area that doesn't touch (0,0) and may extend beyond the
+    // bitmap bounds (to test that we catch that in eraseArea
+    const SkColor initColor = 0xFF0000FF;
+    const SkIRect area = { 1, 1, 3, 3 };
+
     for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) {
         SkBitmap bm;
-        uint32_t storage[1];
-        bm.setConfig(gRec[i].fConfig, 1, 1);
+        uint32_t storage[4];
+        bm.setConfig(gRec[i].fConfig, 2, 2);
         bm.setPixels(storage);
-        bm.eraseColor(gRec[i].fInColor);
 
-        SkColor c = bm.getColor(0, 0);
+        bm.eraseColor(initColor);
+        bm.eraseArea(area, gRec[i].fInColor);
+
+        SkColor c = bm.getColor(1, 1);
         REPORTER_ASSERT(reporter, c == gRec[i].fOutColor);
     }
+    
+    test_eraserect_A1(reporter);
 }
 
 #include "TestClassDef.h"