skia2/tests/BitmapCopyTest.cpp
caryclark@google.com 543c719a6d Revert r3036. This change breaks loading images as
described in Chromium issue 114107. The Chromium break
may be fixed by WebKit patch 78239, at which time
this patch can be reapplied.
Review URL: https://codereview.appspot.com/5675077

git-svn-id: http://skia.googlecode.com/svn/trunk@3219 2bbb7eff-a529-9590-31e7-b0007b416f81
2012-02-17 14:42:22 +00:00

606 lines
24 KiB
C++

/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "Test.h"
#include "SkBitmap.h"
#include "SkRect.h"
static const char* boolStr(bool value) {
return value ? "true" : "false";
}
// these are in the same order as the SkBitmap::Config enum
static const char* gConfigName[] = {
"None", "A1", "A8", "Index8", "565", "4444", "8888", "RLE_Index8"
};
static void report_opaqueness(skiatest::Reporter* reporter, const SkBitmap& src,
const SkBitmap& dst) {
SkString str;
str.printf("src %s opaque:%d, dst %s opaque:%d",
gConfigName[src.config()], src.isOpaque(),
gConfigName[dst.config()], dst.isOpaque());
reporter->reportFailed(str);
}
static bool canHaveAlpha(SkBitmap::Config config) {
return config != SkBitmap::kRGB_565_Config;
}
// copyTo() should preserve isOpaque when it makes sense
static void test_isOpaque(skiatest::Reporter* reporter, const SkBitmap& src,
SkBitmap::Config dstConfig) {
SkBitmap bitmap(src);
SkBitmap dst;
// we need the lock so that we get a valid colorTable (when available)
SkAutoLockPixels alp(bitmap);
SkColorTable* ctable = bitmap.getColorTable();
unsigned ctableFlags = ctable ? ctable->getFlags() : 0;
if (canHaveAlpha(bitmap.config()) && canHaveAlpha(dstConfig)) {
bitmap.setIsOpaque(false);
if (ctable) {
ctable->setFlags(ctableFlags & ~SkColorTable::kColorsAreOpaque_Flag);
}
REPORTER_ASSERT(reporter, bitmap.copyTo(&dst, dstConfig));
REPORTER_ASSERT(reporter, dst.config() == dstConfig);
if (bitmap.isOpaque() != dst.isOpaque()) {
report_opaqueness(reporter, bitmap, dst);
}
}
bitmap.setIsOpaque(true);
if (ctable) {
ctable->setFlags(ctableFlags | SkColorTable::kColorsAreOpaque_Flag);
}
REPORTER_ASSERT(reporter, bitmap.copyTo(&dst, dstConfig));
REPORTER_ASSERT(reporter, dst.config() == dstConfig);
if (bitmap.isOpaque() != dst.isOpaque()) {
report_opaqueness(reporter, bitmap, dst);
}
if (ctable) {
ctable->setFlags(ctableFlags);
}
}
static void init_src(const SkBitmap& bitmap, const SkColorTable* ct) {
SkAutoLockPixels lock(bitmap);
if (bitmap.getPixels()) {
if (ct) {
sk_bzero(bitmap.getPixels(), bitmap.getSize());
} else {
bitmap.eraseColor(SK_ColorWHITE);
}
}
}
SkColorTable* init_ctable() {
static const SkColor colors[] = {
SK_ColorBLACK, SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE
};
return new SkColorTable(colors, SK_ARRAY_COUNT(colors));
}
struct Pair {
SkBitmap::Config fConfig;
const char* fValid;
};
// Utility functions for copyPixelsTo()/copyPixelsFrom() tests.
// getPixel()
// setPixel()
// getSkConfigName()
// struct Coordinates
// reportCopyVerification()
// writeCoordPixels()
// Utility function to read the value of a given pixel in bm. All
// values converted to uint32_t for simplification of comparisons.
uint32_t getPixel(int x, int y, const SkBitmap& bm) {
uint32_t val = 0;
uint16_t val16;
uint8_t val8, shift;
SkAutoLockPixels lock(bm);
const void* rawAddr = bm.getAddr(x,y);
switch (bm.getConfig()) {
case SkBitmap::kARGB_8888_Config:
memcpy(&val, rawAddr, sizeof(uint32_t));
break;
case SkBitmap::kARGB_4444_Config:
case SkBitmap::kRGB_565_Config:
memcpy(&val16, rawAddr, sizeof(uint16_t));
val = val16;
break;
case SkBitmap::kA8_Config:
case SkBitmap::kIndex8_Config:
memcpy(&val8, rawAddr, sizeof(uint8_t));
val = val8;
break;
case SkBitmap::kA1_Config:
memcpy(&val8, rawAddr, sizeof(uint8_t));
shift = x % 8;
val = (val8 >> shift) & 0x1 ;
break;
default:
break;
}
return val;
}
// Utility function to set value of any pixel in bm.
// bm.getConfig() specifies what format 'val' must be
// converted to, but at present uint32_t can handle all formats.
void setPixel(int x, int y, uint32_t val, SkBitmap& bm) {
uint16_t val16;
uint8_t val8, shift;
SkAutoLockPixels lock(bm);
void* rawAddr = bm.getAddr(x,y);
switch (bm.getConfig()) {
case SkBitmap::kARGB_8888_Config:
memcpy(rawAddr, &val, sizeof(uint32_t));
break;
case SkBitmap::kARGB_4444_Config:
case SkBitmap::kRGB_565_Config:
val16 = val & 0xFFFF;
memcpy(rawAddr, &val16, sizeof(uint16_t));
break;
case SkBitmap::kA8_Config:
case SkBitmap::kIndex8_Config:
val8 = val & 0xFF;
memcpy(rawAddr, &val8, sizeof(uint8_t));
break;
case SkBitmap::kA1_Config:
shift = x % 8; // We assume we're in the right byte.
memcpy(&val8, rawAddr, sizeof(uint8_t));
if (val & 0x1) // Turn bit on.
val8 |= (0x1 << shift);
else // Turn bit off.
val8 &= ~(0x1 << shift);
memcpy(rawAddr, &val8, sizeof(uint8_t));
break;
default:
// Ignore.
break;
}
}
// Utility to return string containing name of each format, to
// simplify diagnostic output.
const char* getSkConfigName(const SkBitmap& bm) {
switch (bm.getConfig()) {
case SkBitmap::kNo_Config: return "SkBitmap::kNo_Config";
case SkBitmap::kA1_Config: return "SkBitmap::kA1_Config";
case SkBitmap::kA8_Config: return "SkBitmap::kA8_Config";
case SkBitmap::kIndex8_Config: return "SkBitmap::kIndex8_Config";
case SkBitmap::kRGB_565_Config: return "SkBitmap::kRGB_565_Config";
case SkBitmap::kARGB_4444_Config: return "SkBitmap::kARGB_4444_Config";
case SkBitmap::kARGB_8888_Config: return "SkBitmap::kARGB_8888_Config";
case SkBitmap::kRLE_Index8_Config:
return "SkBitmap::kRLE_Index8_Config,";
default: return "Unknown SkBitmap configuration.";
}
}
// Helper struct to contain pixel locations, while avoiding need for STL.
struct Coordinates {
const int length;
SkIPoint* const data;
explicit Coordinates(int _length): length(_length)
, data(new SkIPoint[length]) { }
~Coordinates(){
delete [] data;
}
SkIPoint* operator[](int i) const {
// Use with care, no bounds checking.
return data + i;
}
};
// A function to verify that two bitmaps contain the same pixel values
// at all coordinates indicated by coords. Simplifies verification of
// copied bitmaps.
void reportCopyVerification(const SkBitmap& bm1, const SkBitmap& bm2,
Coordinates& coords,
const char* msg,
skiatest::Reporter* reporter){
bool success = true;
// Confirm all pixels in the list match.
for (int i = 0; i < coords.length; ++i)
success = success &&
(getPixel(coords[i]->fX, coords[i]->fY, bm1) ==
getPixel(coords[i]->fX, coords[i]->fY, bm2));
if (!success) {
SkString str;
str.printf("%s [config = %s]",
msg, getSkConfigName(bm1));
reporter->reportFailed(str);
}
}
// Writes unique pixel values at locations specified by coords.
void writeCoordPixels(SkBitmap& bm, const Coordinates& coords) {
for (int i = 0; i < coords.length; ++i)
setPixel(coords[i]->fX, coords[i]->fY, i, bm);
}
static void TestBitmapCopy(skiatest::Reporter* reporter) {
static const Pair gPairs[] = {
{ SkBitmap::kNo_Config, "00000000" },
{ SkBitmap::kA1_Config, "01000000" },
{ SkBitmap::kA8_Config, "00101110" },
{ SkBitmap::kIndex8_Config, "00111110" },
{ SkBitmap::kRGB_565_Config, "00101110" },
{ SkBitmap::kARGB_4444_Config, "00101110" },
{ SkBitmap::kARGB_8888_Config, "00101110" },
// TODO: create valid RLE bitmap to test with
// { SkBitmap::kRLE_Index8_Config, "00101111" }
};
static const bool isExtracted[] = {
false, true
};
const int W = 20;
const int H = 33;
for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) {
for (size_t j = 0; j < SK_ARRAY_COUNT(gPairs); j++) {
SkBitmap src, dst;
SkColorTable* ct = NULL;
src.setConfig(gPairs[i].fConfig, W, H);
if (SkBitmap::kIndex8_Config == src.config() ||
SkBitmap::kRLE_Index8_Config == src.config()) {
ct = init_ctable();
}
src.allocPixels(ct);
SkSafeUnref(ct);
init_src(src, ct);
bool success = src.copyTo(&dst, gPairs[j].fConfig);
bool expected = gPairs[i].fValid[j] != '0';
if (success != expected) {
SkString str;
str.printf("SkBitmap::copyTo from %s to %s. expected %s returned %s",
gConfigName[i], gConfigName[j], boolStr(expected),
boolStr(success));
reporter->reportFailed(str);
}
bool canSucceed = src.canCopyTo(gPairs[j].fConfig);
if (success != canSucceed) {
SkString str;
str.printf("SkBitmap::copyTo from %s to %s. returned %s canCopyTo %s",
gConfigName[i], gConfigName[j], boolStr(success),
boolStr(canSucceed));
reporter->reportFailed(str);
}
if (success) {
REPORTER_ASSERT(reporter, src.width() == dst.width());
REPORTER_ASSERT(reporter, src.height() == dst.height());
REPORTER_ASSERT(reporter, dst.config() == gPairs[j].fConfig);
test_isOpaque(reporter, src, dst.config());
if (src.config() == dst.config()) {
SkAutoLockPixels srcLock(src);
SkAutoLockPixels dstLock(dst);
REPORTER_ASSERT(reporter, src.readyToDraw());
REPORTER_ASSERT(reporter, dst.readyToDraw());
const char* srcP = (const char*)src.getAddr(0, 0);
const char* dstP = (const char*)dst.getAddr(0, 0);
REPORTER_ASSERT(reporter, srcP != dstP);
REPORTER_ASSERT(reporter, !memcmp(srcP, dstP,
src.getSize()));
}
// test extractSubset
{
SkBitmap bitmap(src);
SkBitmap subset;
SkIRect r;
r.set(1, 1, 2, 2);
bitmap.setIsVolatile(true);
if (bitmap.extractSubset(&subset, r)) {
REPORTER_ASSERT(reporter, subset.width() == 1);
REPORTER_ASSERT(reporter, subset.height() == 1);
REPORTER_ASSERT(reporter,
subset.isVolatile() == true);
SkBitmap copy;
REPORTER_ASSERT(reporter,
subset.copyTo(&copy, subset.config()));
REPORTER_ASSERT(reporter, copy.width() == 1);
REPORTER_ASSERT(reporter, copy.height() == 1);
REPORTER_ASSERT(reporter, copy.rowBytes() <= 4);
SkAutoLockPixels alp0(subset);
SkAutoLockPixels alp1(copy);
// they should both have, or both not-have, a colortable
bool hasCT = subset.getColorTable() != NULL;
REPORTER_ASSERT(reporter,
(copy.getColorTable() != NULL) == hasCT);
}
bitmap.setIsVolatile(false);
if (bitmap.extractSubset(&subset, r)) {
REPORTER_ASSERT(reporter,
subset.isVolatile() == false);
}
}
} else {
// dst should be unchanged from its initial state
REPORTER_ASSERT(reporter, dst.config() == SkBitmap::kNo_Config);
REPORTER_ASSERT(reporter, dst.width() == 0);
REPORTER_ASSERT(reporter, dst.height() == 0);
}
} // for (size_t j = ...
// Tests for getSafeSize(), getSafeSize64(), copyPixelsTo(),
// copyPixelsFrom().
//
for (size_t copyCase = 0; copyCase < SK_ARRAY_COUNT(isExtracted);
++copyCase) {
// Test copying to/from external buffer.
// Note: the tests below have hard-coded values ---
// Please take care if modifying.
if (gPairs[i].fConfig != SkBitmap::kRLE_Index8_Config) {
// Tests for getSafeSize64().
// Test with a very large configuration without pixel buffer
// attached.
SkBitmap tstSafeSize;
tstSafeSize.setConfig(gPairs[i].fConfig, 100000000U,
100000000U);
Sk64 safeSize = tstSafeSize.getSafeSize64();
if (safeSize.isNeg()) {
SkString str;
str.printf("getSafeSize64() negative: %s",
getSkConfigName(tstSafeSize));
reporter->reportFailed(str);
}
bool sizeFail = false;
// Compare against hand-computed values.
switch (gPairs[i].fConfig) {
case SkBitmap::kNo_Config:
break;
case SkBitmap::kA1_Config:
if (safeSize.fHi != 0x470DE ||
safeSize.fLo != 0x4DF82000)
sizeFail = true;
break;
case SkBitmap::kA8_Config:
case SkBitmap::kIndex8_Config:
if (safeSize.fHi != 0x2386F2 ||
safeSize.fLo != 0x6FC10000)
sizeFail = true;
break;
case SkBitmap::kRGB_565_Config:
case SkBitmap::kARGB_4444_Config:
if (safeSize.fHi != 0x470DE4 ||
safeSize.fLo != 0xDF820000)
sizeFail = true;
break;
case SkBitmap::kARGB_8888_Config:
if (safeSize.fHi != 0x8E1BC9 ||
safeSize.fLo != 0xBF040000)
sizeFail = true;
break;
case SkBitmap::kRLE_Index8_Config:
break;
default:
break;
}
if (sizeFail) {
SkString str;
str.printf("getSafeSize64() wrong size: %s",
getSkConfigName(tstSafeSize));
reporter->reportFailed(str);
}
size_t subW, subH;
// Set sizes to be height = 2 to force the last row of the
// source to be used, thus verifying correct operation if
// the bitmap is an extracted subset.
if (gPairs[i].fConfig == SkBitmap::kA1_Config) {
// If one-bit per pixel, use 9 pixels to force more than
// one byte per row.
subW = 9;
subH = 2;
} else {
// All other configurations are at least one byte per pixel,
// and different configs will test copying different numbers
// of bytes.
subW = subH = 2;
}
// Create bitmap to act as source for copies and subsets.
SkBitmap src, subset;
SkColorTable* ct = NULL;
if (isExtracted[copyCase]) { // A larger image to extract from.
src.setConfig(gPairs[i].fConfig, 2 * subW + 1, subH);
} else // Tests expect a 2x2 bitmap, so make smaller.
src.setConfig(gPairs[i].fConfig, subW, subH);
if (SkBitmap::kIndex8_Config == src.config() ||
SkBitmap::kRLE_Index8_Config == src.config()) {
ct = init_ctable();
}
src.allocPixels(ct);
SkSafeUnref(ct);
// Either copy src or extract into 'subset', which is used
// for subsequent calls to copyPixelsTo/From.
bool srcReady = false;
if (isExtracted[copyCase]) {
// The extractedSubset() test case allows us to test copy-
// ing when src and dst mave possibly different strides.
SkIRect r;
if (gPairs[i].fConfig == SkBitmap::kA1_Config)
// This config seems to need byte-alignment of
// extracted subset bits.
r.set(0, 0, subW, subH);
else
r.set(1, 0, 1 + subW, subH); // 2x2 extracted bitmap
srcReady = src.extractSubset(&subset, r);
} else {
srcReady = src.copyTo(&subset, src.getConfig());
}
// Not all configurations will generate a valid 'subset'.
if (srcReady) {
// Allocate our target buffer 'buf' for all copies.
// To simplify verifying correctness of copies attach
// buf to a SkBitmap, but copies are done using the
// raw buffer pointer.
const uint32_t bufSize = subH *
SkBitmap::ComputeRowBytes(src.getConfig(), subW) * 2;
uint8_t* buf = new uint8_t[bufSize];
SkBitmap bufBm; // Attach buf to this bitmap.
bool successExpected;
// Set up values for each pixel being copied.
Coordinates coords(subW * subH);
for (size_t x = 0; x < subW; ++x)
for (size_t y = 0; y < subH; ++y)
{
int index = y * subW + x;
SkASSERT(index < coords.length);
coords[index]->fX = x;
coords[index]->fY = y;
}
writeCoordPixels(subset, coords);
// Test #1 ////////////////////////////////////////////
// Before/after comparisons easier if we attach buf
// to an appropriately configured SkBitmap.
memset(buf, 0xFF, bufSize);
// Config with stride greater than src but that fits in buf.
bufBm.setConfig(gPairs[i].fConfig, subW, subH,
SkBitmap::ComputeRowBytes(subset.getConfig(), subW)
* 2);
bufBm.setPixels(buf);
successExpected = false;
// Then attempt to copy with a stride that is too large
// to fit in the buffer.
REPORTER_ASSERT(reporter,
subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes() * 3)
== successExpected);
if (successExpected)
reportCopyVerification(subset, bufBm, coords,
"copyPixelsTo(buf, bufSize, 1.5*maxRowBytes)",
reporter);
// Test #2 ////////////////////////////////////////////
// This test should always succeed, but in the case
// of extracted bitmaps only because we handle the
// issue of getSafeSize(). Without getSafeSize()
// buffer overrun/read would occur.
memset(buf, 0xFF, bufSize);
bufBm.setConfig(gPairs[i].fConfig, subW, subH,
subset.rowBytes());
bufBm.setPixels(buf);
successExpected = subset.getSafeSize() <= bufSize;
REPORTER_ASSERT(reporter,
subset.copyPixelsTo(buf, bufSize) ==
successExpected);
if (successExpected)
reportCopyVerification(subset, bufBm, coords,
"copyPixelsTo(buf, bufSize)", reporter);
// Test #3 ////////////////////////////////////////////
// Copy with different stride between src and dst.
memset(buf, 0xFF, bufSize);
bufBm.setConfig(gPairs[i].fConfig, subW, subH,
subset.rowBytes()+1);
bufBm.setPixels(buf);
successExpected = true; // Should always work.
REPORTER_ASSERT(reporter,
subset.copyPixelsTo(buf, bufSize,
subset.rowBytes()+1) == successExpected);
if (successExpected)
reportCopyVerification(subset, bufBm, coords,
"copyPixelsTo(buf, bufSize, rowBytes+1)", reporter);
// Test #4 ////////////////////////////////////////////
// Test copy with stride too small.
memset(buf, 0xFF, bufSize);
bufBm.setConfig(gPairs[i].fConfig, subW, subH);
bufBm.setPixels(buf);
successExpected = false;
// Request copy with stride too small.
REPORTER_ASSERT(reporter,
subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes()-1)
== successExpected);
if (successExpected)
reportCopyVerification(subset, bufBm, coords,
"copyPixelsTo(buf, bufSize, rowBytes()-1)", reporter);
#if 0 // copyPixelsFrom is gone
// Test #5 ////////////////////////////////////////////
// Tests the case where the source stride is too small
// for the source configuration.
memset(buf, 0xFF, bufSize);
bufBm.setConfig(gPairs[i].fConfig, subW, subH);
bufBm.setPixels(buf);
writeCoordPixels(bufBm, coords);
REPORTER_ASSERT(reporter,
subset.copyPixelsFrom(buf, bufSize, 1) == false);
// Test #6 ///////////////////////////////////////////
// Tests basic copy from an external buffer to the bitmap.
// If the bitmap is "extracted", this also tests the case
// where the source stride is different from the dest.
// stride.
// We've made the buffer large enough to always succeed.
bufBm.setConfig(gPairs[i].fConfig, subW, subH);
bufBm.setPixels(buf);
writeCoordPixels(bufBm, coords);
REPORTER_ASSERT(reporter,
subset.copyPixelsFrom(buf, bufSize, bufBm.rowBytes()) ==
true);
reportCopyVerification(bufBm, subset, coords,
"copyPixelsFrom(buf, bufSize)",
reporter);
// Test #7 ////////////////////////////////////////////
// Tests the case where the source buffer is too small
// for the transfer.
REPORTER_ASSERT(reporter,
subset.copyPixelsFrom(buf, 1, subset.rowBytes()) ==
false);
delete [] buf;
#endif
}
}
} // for (size_t copyCase ...
}
}
#include "TestClassDef.h"
DEFINE_TESTCLASS("BitmapCopy", TestBitmapCopyClass, TestBitmapCopy)