Add safe size/copy functions to Skia.

    This patch adds four methods to SkBitmap. There are two functions to return
    "safe size", defined as the number of pixels from the value returned by
    getPixels() to the end of the allocated buffer.

    There is one version of fillPixels() to copy the bitmap instance into an
    external buffer (with specified size, and using specified stride), and another
    fillPixels() to copy from an external buffer to the instance bitmap. In the
    latter case the specified height, width and pixel format must match that used by
    the bitmap instance, although the specified stride may be any value at least as
    large as the minimum stride for the specified geometry. It is assumed that the
    external buffer is of size at least (height - 1)*stride + width *
    bytesPerPixel.

    Both fillPixels() functions return false if the copy is not possible with the
    specified parameters.

    Review URL: http://codereview.appspot.com/2837041/

git-svn-id: http://skia.googlecode.com/svn/trunk@625 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/tests/BitmapCopyTest.cpp b/tests/BitmapCopyTest.cpp
index be3850e..7af8ac9 100644
--- a/tests/BitmapCopyTest.cpp
+++ b/tests/BitmapCopyTest.cpp
@@ -81,6 +81,151 @@
     const char*         fValid;
 };
 
+// Utility functions for copyPixelsTo()/copyPixelsFrom() tests.
+// getPixel()
+// setPixel()
+// getSkConfigName()
+// struct Coordinates
+// reportCopyVerification()
+// writeCoordPixels()
+
+// Utility function to read the value of a given pixel in bm. All
+// values converted to uint32_t for simplification of comparisons.
+uint32_t getPixel(int x, int y, const SkBitmap& bm) {
+    uint32_t val = 0;
+    uint16_t val16;
+    uint8_t val8, shift;
+    SkAutoLockPixels lock(bm);
+    const void* rawAddr = bm.getAddr(x,y);
+
+    switch (bm.getConfig()) {
+        case SkBitmap::kARGB_8888_Config:
+            memcpy(&val, rawAddr, sizeof(uint32_t));
+            break;
+        case SkBitmap::kARGB_4444_Config:
+        case SkBitmap::kRGB_565_Config:
+            memcpy(&val16, rawAddr, sizeof(uint16_t));
+            val = val16;
+            break;
+        case SkBitmap::kA8_Config:
+        case SkBitmap::kIndex8_Config:
+            memcpy(&val8, rawAddr, sizeof(uint8_t));
+            val = val8;
+            break;
+        case SkBitmap::kA1_Config:
+            memcpy(&val8, rawAddr, sizeof(uint8_t));
+            shift = x % 8;
+            val = (val8 >> shift) & 0x1 ;
+            break;
+        default:
+            break;
+    }
+    return val;
+}
+
+// Utility function to set value of any pixel in bm.
+// bm.getConfig() specifies what format 'val' must be
+// converted to, but at present uint32_t can handle all formats.
+void setPixel(int x, int y, uint32_t val, SkBitmap& bm) {
+    uint16_t val16;
+    uint8_t val8, shift;
+    SkAutoLockPixels lock(bm);
+    void* rawAddr = bm.getAddr(x,y);
+
+    switch (bm.getConfig()) {
+        case SkBitmap::kARGB_8888_Config:
+            memcpy(rawAddr, &val, sizeof(uint32_t));
+            break;
+        case SkBitmap::kARGB_4444_Config:
+        case SkBitmap::kRGB_565_Config:
+            val16 = val & 0xFFFF;
+            memcpy(rawAddr, &val16, sizeof(uint16_t));
+            break;
+        case SkBitmap::kA8_Config:
+        case SkBitmap::kIndex8_Config:
+            val8 = val & 0xFF;
+            memcpy(rawAddr, &val8, sizeof(uint8_t));
+            break;
+        case SkBitmap::kA1_Config:
+            shift = x % 8; // We assume we're in the right byte.
+            memcpy(&val8, rawAddr, sizeof(uint8_t));
+            if (val & 0x1) // Turn bit on.
+                val8 |= (0x1 << shift);
+            else // Turn bit off.
+                val8 &= ~(0x1 << shift);
+            memcpy(rawAddr, &val8, sizeof(uint8_t));
+            break;
+        default:
+            // Ignore.
+            break;
+    }
+}
+
+// Utility to return string containing name of each format, to
+// simplify diagnostic output.
+const char* getSkConfigName(const SkBitmap& bm) {
+    switch (bm.getConfig()) {
+        case SkBitmap::kNo_Config: return "SkBitmap::kNo_Config";
+        case SkBitmap::kA1_Config: return "SkBitmap::kA1_Config";
+        case SkBitmap::kA8_Config: return "SkBitmap::kA8_Config";
+        case SkBitmap::kIndex8_Config: return "SkBitmap::kIndex8_Config";
+        case SkBitmap::kRGB_565_Config: return "SkBitmap::kRGB_565_Config";
+        case SkBitmap::kARGB_4444_Config: return "SkBitmap::kARGB_4444_Config";
+        case SkBitmap::kARGB_8888_Config: return "SkBitmap::kARGB_8888_Config";
+        case SkBitmap::kRLE_Index8_Config:
+            return "SkBitmap::kRLE_Index8_Config,";
+        default: return "Unknown SkBitmap configuration.";
+    }
+}
+
+// Helper struct to contain pixel locations, while avoiding need for STL.
+struct Coordinates {
+
+    const int length;
+    SkIPoint* const data;
+
+    explicit Coordinates(int _length): length(_length)
+                                     , data(new SkIPoint[length]) { }
+
+    ~Coordinates(){
+        delete [] data;
+    }
+
+    SkIPoint* operator[](int i) const {
+        // Use with care, no bounds checking.
+        return data + i;
+    }
+};
+
+// A function to verify that two bitmaps contain the same pixel values
+// at all coordinates indicated by coords. Simplifies verification of
+// copied bitmaps.
+void reportCopyVerification(const SkBitmap& bm1, const SkBitmap& bm2,
+                            Coordinates& coords,
+                            const char* msg,
+                            skiatest::Reporter* reporter){
+    bool success = true;
+
+    // Confirm all pixels in the list match.
+    for (int i = 0; i < coords.length; ++i)
+        success = success &&
+                  (getPixel(coords[i]->fX, coords[i]->fY, bm1) ==
+                   getPixel(coords[i]->fX, coords[i]->fY, bm2));
+
+    if (!success) {
+        SkString str;
+        str.printf("%s [config = %s]",
+                   msg, getSkConfigName(bm1));
+        reporter->reportFailed(str);
+    }
+}
+
+// Writes unique pixel values at locations specified by coords.
+void writeCoordPixels(SkBitmap& bm, const Coordinates& coords) {
+    for (int i = 0; i < coords.length; ++i)
+        setPixel(coords[i]->fX, coords[i]->fY, i, bm);
+}
+
 static void TestBitmapCopy(skiatest::Reporter* reporter) {
     static const Pair gPairs[] = {
         { SkBitmap::kNo_Config,         "00000000"  },
@@ -94,6 +239,10 @@
  //       { SkBitmap::kRLE_Index8_Config, "00101111"  }
     };
 
+    static const bool isExtracted[] = {
+        false, true
+    };
+
     const int W = 20;
     const int H = 33;
 
@@ -176,7 +325,257 @@
                 REPORTER_ASSERT(reporter, dst.width() == 0);
                 REPORTER_ASSERT(reporter, dst.height() == 0);
             }
-        }
+        } // for (size_t j = ...
+
+        // Tests for getSafeSize(), getSafeSize64(), copyPixelsTo(),
+        // copyPixelsFrom().
+        //
+        for (size_t copyCase = 0; copyCase < SK_ARRAY_COUNT(isExtracted);
+             ++copyCase) {
+            // Test copying to/from external buffer.
+            // Note: the tests below have hard-coded values ---
+            //       Please take care if modifying.
+            if (gPairs[i].fConfig != SkBitmap::kRLE_Index8_Config) {
+
+                // Tests for getSafeSize64().
+                // Test with a very large configuration without pixel buffer
+                // attached.
+                SkBitmap tstSafeSize;
+                tstSafeSize.setConfig(gPairs[i].fConfig, 100000000U,
+                                      100000000U);
+                Sk64 safeSize = tstSafeSize.getSafeSize64();
+                if (safeSize.isNeg()) {
+                    SkString str;
+                    str.printf("getSafeSize64() negative: %s",
+                        getSkConfigName(tstSafeSize));
+                    reporter->reportFailed(str);
+                }
+                bool sizeFail = false;
+                // Compare against hand-computed values.
+                switch (gPairs[i].fConfig) {
+                    case SkBitmap::kNo_Config:
+                        break;
+
+                    case SkBitmap::kA1_Config:
+                        if (safeSize.fHi != 0x470DE ||
+                            safeSize.fLo != 0x4DF82000)
+                            sizeFail = true;
+                        break;
+
+                    case SkBitmap::kA8_Config:
+                    case SkBitmap::kIndex8_Config:
+                        if (safeSize.fHi != 0x2386F2 ||
+                            safeSize.fLo != 0x6FC10000)
+                            sizeFail = true;
+                        break;
+
+                    case SkBitmap::kRGB_565_Config:
+                    case SkBitmap::kARGB_4444_Config:
+                        if (safeSize.fHi != 0x470DE4 ||
+                            safeSize.fLo != 0xDF820000)
+                            sizeFail = true;
+                        break;
+
+                    case SkBitmap::kARGB_8888_Config:
+                        if (safeSize.fHi != 0x8E1BC9 ||
+                            safeSize.fLo != 0xBF040000)
+                            sizeFail = true;
+                        break;
+
+                    case SkBitmap::kRLE_Index8_Config:
+                        break;
+
+                    default:
+                        break;
+                }
+                if (sizeFail) {
+                    SkString str;
+                    str.printf("getSafeSize64() wrong size: %s",
+                        getSkConfigName(tstSafeSize));
+                    reporter->reportFailed(str);
+                }
+
+                size_t subW, subH;
+                // Set sizes to be height = 2 to force the last row of the
+                // source to be used, thus verifying correct operation if
+                // the bitmap is an extracted subset.
+                if (gPairs[i].fConfig == SkBitmap::kA1_Config) {
+                    // If one-bit per pixel, use 9 pixels to force more than
+                    // one byte per row.
+                    subW = 9;
+                    subH = 2;
+                } else {
+                    // All other configurations are at least one byte per pixel,
+                    // and different configs will test copying different numbers
+                    // of bytes.
+                    subW = subH = 2;
+                }
+
+                // Create bitmap to act as source for copies and subsets.
+                SkBitmap src, subset;
+                SkColorTable* ct = NULL;
+                if (isExtracted[copyCase]) { // A larger image to extract from.
+                    src.setConfig(gPairs[i].fConfig, 2 * subW + 1, subH);
+                } else // Tests expect a 2x2 bitmap, so make smaller.
+                    src.setConfig(gPairs[i].fConfig, subW, subH);
+                if (SkBitmap::kIndex8_Config == src.config() ||
+                        SkBitmap::kRLE_Index8_Config == src.config()) {
+                    ct = init_ctable();
+                }
+
+                src.allocPixels(ct);
+                SkSafeUnref(ct);
+
+                // Either copy src or extract into 'subset', which is used
+                // for subsequent calls to copyPixelsTo/From.
+                bool srcReady = false;
+                if (isExtracted[copyCase]) {
+                    // The extractedSubset() test case allows us to test copy-
+                    // ing when src and dst mave possibly different strides.
+                    SkIRect r;
+                    if (gPairs[i].fConfig == SkBitmap::kA1_Config)
+                        // This config seems to need byte-alignment of
+                        // extracted subset bits.
+                        r.set(0, 0, subW, subH);
+                    else
+                        r.set(1, 0, 1 + subW, subH); // 2x2 extracted bitmap
+
+                    srcReady = src.extractSubset(&subset, r);
+                } else {
+                    srcReady = src.copyTo(&subset, src.getConfig());
+                }
+
+                // Not all configurations will generate a valid 'subset'.
+                if (srcReady) {
+
+                    // Allocate our target buffer 'buf' for all copies.
+                    // To simplify verifying correctness of copies attach
+                    // buf to a SkBitmap, but copies are done using the
+                    // raw buffer pointer.
+                    const uint32_t bufSize = subH *
+                        SkBitmap::ComputeRowBytes(src.getConfig(), subW) * 2;
+                    uint8_t* buf = new uint8_t[bufSize];
+
+                    SkBitmap bufBm; // Attach buf to this bitmap.
+                    bool successExpected;
+
+                    // Set up values for each pixel being copied.
+                    Coordinates coords(subW * subH);
+                    for (size_t x = 0; x < subW; ++x)
+                        for (size_t y = 0; y < subH; ++y)
+                        {
+                            size_t index = y * subW + x;
+                            SkASSERT(index < coords.length);
+                            coords[index]->fX = x;
+                            coords[index]->fY = y;
+                        }
+
+                    writeCoordPixels(subset, coords);
+
+                    // Test #1 ////////////////////////////////////////////
+
+                    // Before/after comparisons easier if we attach buf
+                    // to an appropriately configured SkBitmap.
+                    memset(buf, 0xFF, bufSize);
+                    // Config with stride greater than src but that fits in buf.
+                    bufBm.setConfig(gPairs[i].fConfig, subW, subH,
+                        SkBitmap::ComputeRowBytes(subset.getConfig(), subW)
+                                                  * 2);
+                    bufBm.setPixels(buf);
+                    successExpected = false;
+                    // Then attempt to copy with a stride that is too large
+                    // to fit in the buffer.
+                    REPORTER_ASSERT(reporter,
+                        subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes() * 3)
+                        == successExpected);
+
+                    if (successExpected)
+                        reportCopyVerification(subset, bufBm, coords,
+                            "copyPixelsTo(buf, bufSize, 1.5*maxRowBytes)",
+                            reporter);
+
+                    // Test #2 ////////////////////////////////////////////
+                    // This test should always succeed, but in the case
+                    // of extracted bitmaps only because we handle the
+                    // issue of getSafeSize(). Without getSafeSize()
+                    // buffer overrun/read would occur.
+                    memset(buf, 0xFF, bufSize);
+                    bufBm.setConfig(gPairs[i].fConfig, subW, subH,
+                                    subset.rowBytes());
+                    bufBm.setPixels(buf);
+                    successExpected = subset.getSafeSize() <= bufSize;
+                    REPORTER_ASSERT(reporter,
+                        subset.copyPixelsTo(buf, bufSize) ==
+                            successExpected);
+                    if (successExpected)
+                        reportCopyVerification(subset, bufBm, coords,
+                        "copyPixelsTo(buf, bufSize)", reporter);
+
+                    // Test #3 ////////////////////////////////////////////
+                    // Copy with different stride between src and dst.
+                    memset(buf, 0xFF, bufSize);
+                    bufBm.setConfig(gPairs[i].fConfig, subW, subH,
+                                    subset.rowBytes()+1);
+                    bufBm.setPixels(buf);
+                    successExpected = true; // Should always work.
+                    REPORTER_ASSERT(reporter,
+                            subset.copyPixelsTo(buf, bufSize,
+                                subset.rowBytes()+1) == successExpected);
+                    if (successExpected)
+                        reportCopyVerification(subset, bufBm, coords,
+                        "copyPixelsTo(buf, bufSize, rowBytes+1)", reporter);
+
+                    // Test #4 ////////////////////////////////////////////
+                    // Test copy with stride too small.
+                    memset(buf, 0xFF, bufSize);
+                    bufBm.setConfig(gPairs[i].fConfig, subW, subH);
+                    bufBm.setPixels(buf);
+                    successExpected = false;
+                    // Request copy with stride too small.
+                    REPORTER_ASSERT(reporter,
+                        subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes()-1)
+                            == successExpected);
+                    if (successExpected)
+                        reportCopyVerification(subset, bufBm, coords,
+                        "copyPixelsTo(buf, bufSize, rowBytes()-1)", reporter);
+
+                    // Test #5 ////////////////////////////////////////////
+                    // Tests the case where the source stride is too small
+                    // for the source configuration.
+                    memset(buf, 0xFF, bufSize);
+                    bufBm.setConfig(gPairs[i].fConfig, subW, subH);
+                    bufBm.setPixels(buf);
+                    writeCoordPixels(bufBm, coords);
+                    REPORTER_ASSERT(reporter,
+                        subset.copyPixelsFrom(buf, bufSize, 1) == false);
+
+                    // Test #6 ////////////////////////////////////////////
+                    // Tests basic copy from an external buffer to the bitmap.
+                    // If the bitmap is "extracted", this also tests the case
+                    // where the source stride is different from the dest.
+                    // stride.
+                    // We've made the buffer large enough to always succeed.
+                    bufBm.setConfig(gPairs[i].fConfig, subW, subH);
+                    bufBm.setPixels(buf);
+                    writeCoordPixels(bufBm, coords);
+                    REPORTER_ASSERT(reporter,
+                        subset.copyPixelsFrom(buf, bufSize, bufBm.rowBytes()) ==
+                            true);
+                    reportCopyVerification(bufBm, subset, coords,
+                        "copyPixelsFrom(buf, bufSize)",
+                        reporter);
+
+                    // Test #7 ////////////////////////////////////////////
+                    // Tests the case where the source buffer is too small
+                    // for the transfer.
+                    REPORTER_ASSERT(reporter,
+                        subset.copyPixelsFrom(buf, 1, subset.rowBytes()) ==
+                            false);
+
+                    delete [] buf;
+                }
+            }
+        } // for (size_t copyCase ...
     }
 }