Create SkBitmapChecksummer and associated SkBitmapTransformer

As needed to start capturing gm image checksums.
Review URL: https://codereview.appspot.com/6920050

git-svn-id: http://skia.googlecode.com/svn/trunk@6759 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/tests.gyp b/gyp/tests.gyp
index 3353d9a..066435c 100644
--- a/gyp/tests.gyp
+++ b/gyp/tests.gyp
@@ -23,6 +23,7 @@
         '../tests/BitmapFactoryTest.cpp',
         '../tests/BitmapGetColorTest.cpp',
         '../tests/BitmapHeapTest.cpp',
+        '../tests/BitmapTransformerTest.cpp',
         '../tests/BitSetTest.cpp',
         '../tests/BlitRowTest.cpp',
         '../tests/BlurTest.cpp',
diff --git a/gyp/utils.gyp b/gyp/utils.gyp
index bea2d09..4e3f6d4 100644
--- a/gyp/utils.gyp
+++ b/gyp/utils.gyp
@@ -53,6 +53,10 @@
 
         '../src/utils/SkBase64.cpp',
         '../src/utils/SkBase64.h',
+        '../src/utils/SkBitmapChecksummer.cpp',
+        '../src/utils/SkBitmapChecksummer.h',
+        '../src/utils/SkBitmapTransformer.cpp',
+        '../src/utils/SkBitmapTransformer.h',
         '../src/utils/SkBitSet.cpp',
         '../src/utils/SkBitSet.h',
         '../src/utils/SkBoundaryPatch.cpp',
diff --git a/src/utils/SkBitmapChecksummer.cpp b/src/utils/SkBitmapChecksummer.cpp
new file mode 100644
index 0000000..bb9fc8d
--- /dev/null
+++ b/src/utils/SkBitmapChecksummer.cpp
@@ -0,0 +1,69 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBitmapChecksummer.h"
+#include "SkBitmapTransformer.h"
+#include "SkCityHash.h"
+#include "SkEndian.h"
+
+/**
+ * Write an integer value into a bytebuffer in little-endian order.
+ */
+static void write_int_to_buffer(int val, char* buf) {
+    val = SkEndian_SwapLE32(val);
+    for (int byte=0; byte<4; byte++) {
+        *buf++ = (char)(val & 0xff);
+        val = val >> 8;
+    }
+}
+
+/*static*/ uint64_t SkBitmapChecksummer::Compute64Internal(
+        const SkBitmap& bitmap, const SkBitmapTransformer& transformer) {
+    int pixelBufferSize = transformer.bytesNeededTotal();
+    int totalBufferSize = pixelBufferSize + 8; // leave room for x/y dimensions
+
+    SkAutoMalloc bufferManager(totalBufferSize);
+    char *bufferStart = static_cast<char *>(bufferManager.get());
+    char *bufPtr = bufferStart;
+    // start with the x/y dimensions
+    write_int_to_buffer(bitmap.width(), bufPtr);
+    bufPtr += 4;
+    write_int_to_buffer(bitmap.height(), bufPtr);
+    bufPtr += 4;
+
+    // add all the pixel data
+    if (!transformer.copyBitmapToPixelBuffer(bufPtr, pixelBufferSize)) {
+        return 0;
+    }
+    return SkCityHash::Compute64(bufferStart, totalBufferSize);
+}
+
+/*static*/ uint64_t SkBitmapChecksummer::Compute64(const SkBitmap& bitmap) {
+    const SkBitmapTransformer::PixelFormat kPixelFormat =
+        SkBitmapTransformer::kARGB_8888_Premul_PixelFormat;
+
+    // First, try to transform the existing bitmap.
+    const SkBitmapTransformer transformer =
+        SkBitmapTransformer(bitmap, kPixelFormat);
+    if (transformer.isValid(false)) {
+        return Compute64Internal(bitmap, transformer);
+    }
+
+    // Hmm, that didn't work. Maybe if we create a new
+    // kARGB_8888_Config version of the bitmap it will work better?
+    SkBitmap copyBitmap;
+    bitmap.copyTo(&copyBitmap, SkBitmap::kARGB_8888_Config);
+    const SkBitmapTransformer copyTransformer =
+        SkBitmapTransformer(copyBitmap, kPixelFormat);
+    if (copyTransformer.isValid(true)) {
+        return Compute64Internal(copyBitmap, copyTransformer);
+    } else {
+        return 0;
+    }
+}
diff --git a/src/utils/SkBitmapChecksummer.h b/src/utils/SkBitmapChecksummer.h
new file mode 100644
index 0000000..63ac726
--- /dev/null
+++ b/src/utils/SkBitmapChecksummer.h
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBitmapChecksummer_DEFINED
+#define SkBitmapChecksummer_DEFINED
+
+#include "SkBitmap.h"
+#include "SkBitmapTransformer.h"
+
+/**
+ * Static class that can generate checksums from SkBitmaps.
+ */
+class SkBitmapChecksummer {
+public:
+    /**
+     * Returns a 64-bit checksum of the pixels in this bitmap.
+     *
+     * If this is unable to compute the checksum for some reason,
+     * it returns 0.
+     *
+     * Note: depending on the bitmap config, we may need to create an
+     * intermediate SkBitmap and copy the pixels over to it... so in some
+     * cases, performance and memory usage can suffer.
+     */
+    static uint64_t Compute64(const SkBitmap& bitmap);
+
+private:
+    static uint64_t Compute64Internal(const SkBitmap& bitmap,
+                                      const SkBitmapTransformer& transformer);
+};
+
+#endif
diff --git a/src/utils/SkBitmapTransformer.cpp b/src/utils/SkBitmapTransformer.cpp
new file mode 100644
index 0000000..c8356d4
--- /dev/null
+++ b/src/utils/SkBitmapTransformer.cpp
@@ -0,0 +1,87 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBitmapTransformer.h"
+#include "SkColorPriv.h"
+#include "SkTypes.h"
+
+bool SkBitmapTransformer::isValid(bool logReason) const {
+    bool retval = true;
+
+    switch(fPixelFormat) {
+    case kARGB_8888_Premul_PixelFormat:
+        break;
+    default:
+        if (logReason) {
+            SkDEBUGF(("PixelFormat %d not supported\n", fPixelFormat));
+        }
+        retval = false;
+    }
+
+    SkBitmap::Config bitmapConfig = fBitmap.config();
+    switch(bitmapConfig) {
+    case SkBitmap::kARGB_8888_Config:
+        break;
+    default:
+        if (logReason) {
+            SkDEBUGF(("SkBitmap::Config %d not supported\n", bitmapConfig));
+        }
+        retval = false;
+    }
+
+    return retval;
+}
+
+/**
+ * Transform from kARGB_8888_Config to kARGB_8888_Premul_PixelFormat.
+ *
+ * Similar to the various scanline transformers in
+ * src/images/transform_scanline.h .
+ */
+static void transform_scanline(const char* SK_RESTRICT src, int width,
+                               char* SK_RESTRICT dst) {
+    const SkPMColor* SK_RESTRICT srcP = reinterpret_cast<const SkPMColor*>(src);
+    for (int i = 0; i < width; i++) {
+        SkPMColor c = *srcP++;
+        unsigned a = SkGetPackedA32(c);
+        unsigned r = SkGetPackedR32(c);
+        unsigned g = SkGetPackedG32(c);
+        unsigned b = SkGetPackedB32(c);
+        *dst++ = a;
+        *dst++ = r;
+        *dst++ = g;
+        *dst++ = b;
+    }
+}
+
+bool SkBitmapTransformer::copyBitmapToPixelBuffer(void *dstBuffer,
+                                                  size_t dstBufferSize) const {
+    if (!this->isValid(true)) {
+        return false;
+    }
+    size_t bytesNeeded = this->bytesNeededTotal();
+    if (dstBufferSize < bytesNeeded) {
+        SkDEBUGF(("dstBufferSize %d must be >= %d\n", dstBufferSize, bytesNeeded));
+        return false;
+    }
+
+    fBitmap.lockPixels();
+    int width = fBitmap.width();
+    size_t srcRowBytes = fBitmap.rowBytes();
+    size_t dstRowBytes = this->bytesNeededPerRow();
+    const char *srcBytes = const_cast<const char *>(static_cast<char*>(fBitmap.getPixels()));
+    char *dstBytes = static_cast<char *>(dstBuffer);
+    for (int y = 0; y < fBitmap.height(); y++) {
+        transform_scanline(srcBytes, width, dstBytes);
+        srcBytes += srcRowBytes;
+        dstBytes += dstRowBytes;
+    }
+    fBitmap.unlockPixels();
+    return true;
+}
diff --git a/src/utils/SkBitmapTransformer.h b/src/utils/SkBitmapTransformer.h
new file mode 100644
index 0000000..70971ac
--- /dev/null
+++ b/src/utils/SkBitmapTransformer.h
@@ -0,0 +1,104 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBitmapTransformer_DEFINED
+#define SkBitmapTransformer_DEFINED
+
+#include "SkBitmap.h"
+
+/**
+ * Class that can copy pixel data out of an SkBitmap, transforming it
+ * into the appropriate PixelFormat.
+ *
+ * As noted in https://codereview.appspot.com/6849119/#msg6 and
+ * https://codereview.appspot.com/6900047 , at some point we might want
+ * to make this more general purpose:
+ * - support more PixelFormats
+ * - use existing SkCanvas::Config8888 enum instead of new PixelFormat enum
+ * - add method to copy pixel data for a single row, instead of the whole bitmap
+ * - add methods to copy pixel data INTO an SkBitmap
+ *
+ * That would allow us to replace SkCopyConfig8888ToBitmap() in
+ * src/core/SkConfig8888.h , as well as the transformations used by
+ * src/images/SkImageDecoder_libpng.cpp , with this common code.
+ *
+ * But for now, we want something more narrowly targeted, just
+ * supplying what is needed by SkBitmapChecksummer.
+ */
+class SkBitmapTransformer {
+public:
+    enum PixelFormat {
+        // 32 bits per pixel, ARGB byte order, with the alpha-channel
+        // value premultiplied into the R/G/B channel values.
+        kARGB_8888_Premul_PixelFormat,
+
+        // marks the end of the list
+        kLast_PixelFormat = kARGB_8888_Premul_PixelFormat,
+    };
+
+    /**
+     * Creates an SkBitmapTransformer instance that can transform between
+     * the given bitmap and a pixel buffer with given pixelFormat.
+     *
+     * Call IsValid() before using, to confirm that this particular
+     * bitmap/pixelFormat combination is supported!
+     */
+    SkBitmapTransformer(const SkBitmap& bitmap, PixelFormat pixelFormat) :
+        fBitmap(bitmap), fPixelFormat(pixelFormat) {}
+
+    /**
+     * Returns true iff we can convert between fBitmap and fPixelFormat.
+     * If this returns false, the return values of any other methods will
+     * be meaningless!
+     *
+     * @param logReason whether to log the reason why this combination
+     *                  is unsupported (only applies in debug mode)
+     */
+    bool isValid(bool logReason=false) const;
+
+    /**
+     * Returns the number of bytes needed to store a single row of the
+     * bitmap's pixels if converted to pixelFormat.
+     */
+    size_t bytesNeededPerRow() const {
+        // This is hard-coded for the single supported PixelFormat.
+        return fBitmap.width() * 4;
+    }
+
+    /**
+     * Returns the number of bytes needed to store the entire bitmap
+     * if converted to pixelFormat, ASSUMING that it is written
+     * out as a single contiguous blob of pixels (no leftover bytes
+     * at the end of each row).
+     */
+    size_t bytesNeededTotal() const {
+        return this->bytesNeededPerRow() * fBitmap.height();
+    }
+
+    /**
+     * Writes the entire bitmap into dstBuffer, using the already-specified
+     * pixelFormat. Returns true if successful.
+     *
+     * dstBufferSize is the maximum allowable bytes to write into dstBuffer;
+     * if that is not large enough to hold the entire bitmap, then this
+     * will fail immediately and return false.
+     * We force the caller to pass this in to avoid buffer overruns in
+     * unanticipated cases.
+     *
+     * All pixels for all rows will be written into dstBuffer as a
+     * single contiguous blob (no skipped pixels at the end of each
+     * row).
+     */
+    bool copyBitmapToPixelBuffer (void *dstBuffer, size_t dstBufferSize) const;
+
+private:
+    const SkBitmap& fBitmap;
+    const PixelFormat fPixelFormat;
+};
+
+#endif
diff --git a/tests/BitmapTransformerTest.cpp b/tests/BitmapTransformerTest.cpp
new file mode 100644
index 0000000..17d0a00
--- /dev/null
+++ b/tests/BitmapTransformerTest.cpp
@@ -0,0 +1,97 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Tests for SkBitmapTransformer.h and SkBitmapTransformer.cpp
+ */
+
+#include "Test.h"
+#include "SkBitmap.h"
+#include "SkBitmapTransformer.h"
+
+namespace skiatest {
+    class BitmapTransformerTestClass : public Test {
+    public:
+        static Test* Factory(void*) {return SkNEW(BitmapTransformerTestClass); }
+    protected:
+        virtual void onGetName(SkString* name) { name->set("BitmapTransformer"); }
+        virtual void onRun(Reporter* reporter) {
+            this->fReporter = reporter;
+            RunTest();
+        }
+    private:
+        void RunTest() {
+            SkBitmap bitmap;
+            SkBitmap::Config supportedConfig = SkBitmap::kARGB_8888_Config;
+            SkBitmap::Config unsupportedConfig = SkBitmap::kARGB_4444_Config;
+            SkBitmapTransformer::PixelFormat supportedPixelFormat =
+                SkBitmapTransformer::kARGB_8888_Premul_PixelFormat;
+            const int kWidth = 55;
+            const int kHeight = 333;
+
+            // Transformations that we know are unsupported:
+            {
+                bitmap.setConfig(unsupportedConfig, kWidth, kHeight);
+                SkBitmapTransformer transformer = SkBitmapTransformer(bitmap, supportedPixelFormat);
+                REPORTER_ASSERT(fReporter, !transformer.isValid());
+            }
+
+            // Valid transformations:
+            {
+                // Bytes we expect to get:
+                const int kWidth = 3;
+                const int kHeight = 5;
+                const char comparisonBuffer[] = {
+                    // kHeight rows, each with kWidth pixels, premultiplied ARGB for each pixel
+                    0xff,0xff,0x00,0x00, 0xff,0xff,0x00,0x00, 0xff,0xff,0x00,0x00, // red
+                    0xff,0x00,0xff,0x00, 0xff,0x00,0xff,0x00, 0xff,0x00,0xff,0x00, // green
+                    0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, // blue
+                    0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, // blue
+                    0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, // blue
+                };
+
+                // A bitmap that should generate the above bytes:
+                bitmap.setConfig(supportedConfig, kWidth, kHeight);
+                REPORTER_ASSERT(fReporter, bitmap.allocPixels());
+                bitmap.setIsOpaque(true);
+                bitmap.eraseColor(SK_ColorBLUE);
+                bitmap.lockPixels();
+                // Change rows [0,1] from blue to [red,green].
+                SkColor oldColor = SK_ColorBLUE;
+                SkColor newColors[] = {SK_ColorRED, SK_ColorGREEN};
+                for (int y = 0; y <= 1; y++) {
+                    for (int x = 0; x < kWidth; x++) {
+                        REPORTER_ASSERT(fReporter, bitmap.getColor(x, y) == oldColor);
+                        SkPMColor* pixel = static_cast<SkPMColor *>(bitmap.getAddr(x, y));
+                        *pixel = SkPreMultiplyColor(newColors[y]);
+                        REPORTER_ASSERT(fReporter, bitmap.getColor(x, y) == newColors[y]);
+                    }
+                }
+                bitmap.unlockPixels();
+
+                // Transform the bitmap and confirm we got the expected results.
+                SkBitmapTransformer transformer = SkBitmapTransformer(bitmap, supportedPixelFormat);
+                REPORTER_ASSERT(fReporter, transformer.isValid());
+                REPORTER_ASSERT(fReporter, transformer.bytesNeededPerRow() == kWidth * 4);
+                REPORTER_ASSERT(fReporter, transformer.bytesNeededTotal() == kWidth * kHeight * 4);
+                int bufferSize = transformer.bytesNeededTotal();
+                SkAutoMalloc pixelBufferManager(bufferSize);
+                char *pixelBuffer = static_cast<char *>(pixelBufferManager.get());
+                REPORTER_ASSERT(fReporter,
+                                transformer.copyBitmapToPixelBuffer(pixelBuffer, bufferSize));
+                REPORTER_ASSERT(fReporter, bufferSize == sizeof(comparisonBuffer));
+                REPORTER_ASSERT(fReporter, memcmp(pixelBuffer, comparisonBuffer, bufferSize) == 0);
+            }
+
+        }
+
+        Reporter* fReporter;
+    };
+
+    static TestRegistry gReg(BitmapTransformerTestClass::Factory);
+}
diff --git a/tests/ChecksumTest.cpp b/tests/ChecksumTest.cpp
index e8a2f25..0319490 100644
--- a/tests/ChecksumTest.cpp
+++ b/tests/ChecksumTest.cpp
@@ -6,8 +6,12 @@
  * found in the LICENSE file.
  */
 #include "Test.h"
+
+#include "SkBitmap.h"
+#include "SkBitmapChecksummer.h"
 #include "SkChecksum.h"
 #include "SkCityHash.h"
+#include "SkColor.h"
 
 // Word size that is large enough to hold results of any checksum type.
 typedef uint64_t checksum_result;
@@ -103,6 +107,15 @@
             return result;
         }
 
+        // Fill in bitmap with test data.
+        void CreateTestBitmap(SkBitmap &bitmap, SkBitmap::Config config, int width, int height,
+                              SkColor color) {
+            bitmap.setConfig(config, width, height);
+            REPORTER_ASSERT(fReporter, bitmap.allocPixels());
+            bitmap.setIsOpaque(true);
+            bitmap.eraseColor(color);
+        }
+
         void RunTest() {
             // Test self-consistency of checksum algorithms.
             fWhichAlgorithm = kSkChecksum;
@@ -143,6 +156,25 @@
                 GetTestDataChecksum(128) == GetTestDataChecksum(256));
             REPORTER_ASSERT(fReporter,
                 GetTestDataChecksum(132) == GetTestDataChecksum(260));
+
+            // Test SkBitmapChecksummer
+            SkBitmap bitmap;
+            // initial test case
+            CreateTestBitmap(bitmap, SkBitmap::kARGB_8888_Config, 333, 555, SK_ColorBLUE);
+            REPORTER_ASSERT(fReporter,
+                            SkBitmapChecksummer::Compute64(bitmap) == 0x18f9df68b1b02f38ULL);
+            // same pixel data but different dimensions should yield a different checksum
+            CreateTestBitmap(bitmap, SkBitmap::kARGB_8888_Config, 555, 333, SK_ColorBLUE);
+            REPORTER_ASSERT(fReporter,
+                            SkBitmapChecksummer::Compute64(bitmap) == 0x6b0298183f786c8eULL);
+            // same dimensions but different color should yield a different checksum
+            CreateTestBitmap(bitmap, SkBitmap::kARGB_8888_Config, 555, 333, SK_ColorGREEN);
+            REPORTER_ASSERT(fReporter,
+                            SkBitmapChecksummer::Compute64(bitmap) == 0xc6b4b3f6fadaaf37ULL);
+            // same pixel colors in a different config should yield the same checksum
+            CreateTestBitmap(bitmap, SkBitmap::kARGB_4444_Config, 555, 333, SK_ColorGREEN);
+            REPORTER_ASSERT(fReporter,
+                            SkBitmapChecksummer::Compute64(bitmap) == 0xc6b4b3f6fadaaf37ULL);
         }
 
         Reporter* fReporter;