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
This commit is contained in:
epoger@google.com 2012-12-12 17:22:23 +00:00
parent ca47aae7ec
commit 31114c69f3
8 changed files with 431 additions and 0 deletions

View File

@ -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',

View File

@ -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',

View File

@ -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;
}
}

View File

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

View File

@ -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;
}

View File

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

View File

@ -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);
}

View File

@ -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 @@ namespace skiatest {
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 @@ namespace skiatest {
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;