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:
parent
ca47aae7ec
commit
31114c69f3
@ -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',
|
||||
|
@ -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',
|
||||
|
69
src/utils/SkBitmapChecksummer.cpp
Normal file
69
src/utils/SkBitmapChecksummer.cpp
Normal 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(©Bitmap, SkBitmap::kARGB_8888_Config);
|
||||
const SkBitmapTransformer copyTransformer =
|
||||
SkBitmapTransformer(copyBitmap, kPixelFormat);
|
||||
if (copyTransformer.isValid(true)) {
|
||||
return Compute64Internal(copyBitmap, copyTransformer);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
37
src/utils/SkBitmapChecksummer.h
Normal file
37
src/utils/SkBitmapChecksummer.h
Normal 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
|
87
src/utils/SkBitmapTransformer.cpp
Normal file
87
src/utils/SkBitmapTransformer.cpp
Normal 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;
|
||||
}
|
104
src/utils/SkBitmapTransformer.h
Normal file
104
src/utils/SkBitmapTransformer.h
Normal 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
|
97
tests/BitmapTransformerTest.cpp
Normal file
97
tests/BitmapTransformerTest.cpp
Normal 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);
|
||||
}
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user