From: epoger@google.com Date: Wed, 12 Dec 2012 17:22:23 +0000 (+0000) Subject: Create SkBitmapChecksummer and associated SkBitmapTransformer X-Git-Tag: submit/tizen/20180928.044319~14086 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=31114c69f39befd50d2b755b7d0dd1cda2c6d2ab;p=platform%2Fupstream%2FlibSkiaSharp.git 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 3353d9a227..066435caf6 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 bea2d097f0..4e3f6d45e5 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 0000000000..bb9fc8d974 --- /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(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 0000000000..63ac726c2e --- /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 0000000000..c8356d4238 --- /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(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(static_cast(fBitmap.getPixels())); + char *dstBytes = static_cast(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 0000000000..70971ac625 --- /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 0000000000..17d0a00da8 --- /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(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(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 e8a2f253b0..03194907f5 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 @@ 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;