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(©Bitmap, 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;