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/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