now we trim the aaclip after building it, to ensure that it has tight bounds

around its (rle compressed) image.



git-svn-id: http://skia.googlecode.com/svn/trunk@2542 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
reed@google.com 2011-10-27 16:58:46 +00:00
parent 83a444602e
commit c904191995
4 changed files with 366 additions and 27 deletions

View File

@ -11,6 +11,34 @@
#include "SkCanvas.h"
#include "SkAAClip.h"
static void testop(const SkIRect& r0, const SkIRect& r1, SkRegion::Op op,
const SkIRect& expectedR) {
SkAAClip c0, c1, c2;
c0.setRect(r0);
c1.setRect(r1);
c2.op(c0, c1, op);
SkIRect r2 = c2.getBounds();
SkASSERT(r2 == expectedR);
}
static const struct {
SkIRect r0;
SkIRect r1;
SkRegion::Op op;
SkIRect expectedR;
} gRec[] = {
{{ 1, 2, 9, 3 }, { -3, 2, 5, 11 }, SkRegion::kDifference_Op, { 5, 2, 9, 3 }},
{{ 1, 10, 5, 13 }, { 1, 2, 5, 11 }, SkRegion::kDifference_Op, { 1, 11, 5, 13 }},
{{ 1, 10, 5, 13 }, { 1, 2, 5, 11 }, SkRegion::kReverseDifference_Op, { 1, 2, 5, 10 }},
};
static void testop() {
for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
testop(gRec[i].r0, gRec[i].r1, gRec[i].op, gRec[i].expectedR);
}
}
static void drawClip(SkCanvas* canvas, const SkAAClip& clip) {
SkMask mask;
SkBitmap bm;
@ -32,6 +60,7 @@ static void drawClip(SkCanvas* canvas, const SkAAClip& clip) {
class AAClipView : public SampleView {
public:
AAClipView() {
testop();
}
protected:

View File

@ -169,10 +169,12 @@ void SkAAClip::Iter::next() {
}
#ifdef SK_DEBUG
// assert we're exactly width-wide, and then return the number of bytes used
static size_t compute_row_length(const uint8_t row[], int width) {
const uint8_t* origRow = row;
while (width > 0) {
int n = row[0];
SkASSERT(n > 0);
SkASSERT(n <= width);
row += 2;
width -= n;
@ -194,40 +196,339 @@ void SkAAClip::validate() const {
const YOffset* yoff = head->yoffsets();
const YOffset* ystop = yoff + head->fRowCount;
const uint8_t* row = head->data();
SkASSERT(0 == yoff->fOffset);
// y values must be monotonic
int y = -1;
int32_t offset = -1;
size_t computedOffset = 0;
const int lastY = fBounds.height() - 1;
// Y and offset must be monotonic
int prevY = -1;
int32_t prevOffset = -1;
while (yoff < ystop) {
SkASSERT(y < yoff->fY);
y = yoff->fY;
SkASSERT(offset < (int32_t)yoff->fOffset);
offset = yoff->fOffset;
SkASSERT(yoff->fOffset == computedOffset);
yoff += 1;
SkASSERT(prevY < yoff->fY);
SkASSERT(yoff->fY <= lastY);
prevY = yoff->fY;
SkASSERT(prevOffset < (int32_t)yoff->fOffset);
prevOffset = yoff->fOffset;
const uint8_t* row = head->data() + yoff->fOffset;
size_t rowLength = compute_row_length(row, fBounds.width());
row += rowLength;
computedOffset += rowLength;
SkASSERT(yoff->fOffset + rowLength <= head->fDataSize);
yoff += 1;
}
SkASSERT(head->fDataSize == computedOffset);
// check the last entry;
--yoff;
SkASSERT(yoff->fY == fBounds.height() - 1);
SkASSERT(yoff->fY == lastY);
}
#endif
///////////////////////////////////////////////////////////////////////////////
static void count_left_right_zeros(const uint8_t* row, int width,
int* leftZ, int* riteZ) {
int zeros = 0;
do {
if (row[1]) {
break;
}
int n = row[0];
SkASSERT(n > 0);
SkASSERT(n <= width);
zeros += n;
row += 2;
width -= n;
} while (width > 0);
*leftZ = zeros;
zeros = 0;
while (width > 0) {
int n = row[0];
SkASSERT(n > 0);
if (0 == row[1]) {
zeros += n;
} else {
zeros = 0;
}
row += 2;
width -= n;
}
*riteZ = zeros;
}
#ifdef SK_DEBUG
static void test_count_left_right_zeros() {
static bool gOnce;
if (gOnce) {
return;
}
gOnce = true;
const uint8_t data0[] = { 0, 0, 10, 0xFF };
const uint8_t data1[] = { 0, 0, 5, 0xFF, 2, 0, 3, 0xFF };
const uint8_t data2[] = { 7, 0, 5, 0, 2, 0, 3, 0xFF };
const uint8_t data3[] = { 0, 5, 5, 0xFF, 2, 0, 3, 0 };
const uint8_t data4[] = { 2, 3, 2, 0, 5, 0xFF, 3, 0 };
const uint8_t data5[] = { 10, 0, 10, 0 };
const uint8_t data6[] = { 2, 2, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
const uint8_t* array[] = {
data0, data1, data2, data3, data4, data5, data6
};
for (size_t i = 0; i < SK_ARRAY_COUNT(array); ++i) {
const uint8_t* data = array[i];
const int expectedL = *data++;
const int expectedR = *data++;
int L = 12345, R = 12345;
count_left_right_zeros(data, 10, &L, &R);
SkASSERT(expectedL == L);
SkASSERT(expectedR == R);
}
}
#endif
// modify row in place, trimming off (zeros) from the left and right sides.
// return the number of bytes that were completely eliminated from the left
static int trim_row_left_right(uint8_t* row, int width, int leftZ, int riteZ) {
int trim = 0;
while (leftZ > 0) {
SkASSERT(0 == row[1]);
int n = row[0];
SkASSERT(n > 0);
SkASSERT(n <= width);
width -= n;
row += 2;
if (n > leftZ) {
row[-2] = n - leftZ;
break;
}
trim += 2;
leftZ -= n;
SkASSERT(leftZ >= 0);
}
if (riteZ) {
// walk row to the end, and then we'll back up to trim riteZ
while (width > 0) {
int n = row[0];
SkASSERT(n <= width);
width -= n;
row += 2;
}
// now skip whole runs of zeros
do {
row -= 2;
SkASSERT(0 == row[1]);
int n = row[0];
SkASSERT(n > 0);
if (n > riteZ) {
row[0] = n - riteZ;
break;
}
riteZ -= n;
SkASSERT(riteZ >= 0);
} while (riteZ > 0);
}
return trim;
}
#ifdef SK_DEBUG
// assert that this row is exactly this width
static int assert_row_width(const uint8_t* row, int width) {
while (width > 0) {
int n = row[0];
SkASSERT(n > 0);
SkASSERT(n <= width);
width -= n;
row += 2;
}
SkASSERT(0 == width);
}
static void test_trim_row_left_right() {
static bool gOnce;
if (gOnce) {
return;
}
gOnce = true;
uint8_t data0[] = { 0, 0, 0, 10, 10, 0xFF };
uint8_t data1[] = { 2, 0, 0, 10, 5, 0, 2, 0, 3, 0xFF };
uint8_t data2[] = { 5, 0, 2, 10, 5, 0, 2, 0, 3, 0xFF };
uint8_t data3[] = { 6, 0, 2, 10, 5, 0, 2, 0, 3, 0xFF };
uint8_t data4[] = { 0, 0, 0, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
uint8_t data5[] = { 1, 0, 0, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
uint8_t data6[] = { 0, 1, 0, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
uint8_t data7[] = { 1, 1, 0, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
uint8_t data8[] = { 2, 2, 2, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
uint8_t data9[] = { 5, 2, 4, 10, 2, 0, 2, 0, 2, 0, 2, 0xFF, 2, 0 };
uint8_t data10[] ={ 74, 0, 4, 150, 9, 0, 65, 0, 76, 0xFF };
uint8_t* array[] = {
data0, data1, data2, data3, data4,
data5, data6, data7, data8, data9,
data10
};
for (size_t i = 0; i < SK_ARRAY_COUNT(array); ++i) {
uint8_t* data = array[i];
const int trimL = *data++;
const int trimR = *data++;
const int expectedSkip = *data++;
const int origWidth = *data++;
assert_row_width(data, origWidth);
int skip = trim_row_left_right(data, origWidth, trimL, trimR);
SkASSERT(expectedSkip == skip);
int expectedWidth = origWidth - trimL - trimR;
assert_row_width(data + skip, expectedWidth);
}
}
#endif
bool SkAAClip::trimLeftRight() {
SkDEBUGCODE(test_trim_row_left_right();)
if (this->isEmpty()) {
return false;
}
AUTO_AACLIP_VALIDATE(*this);
const int width = fBounds.width();
RunHead* head = fRunHead;
YOffset* yoff = head->yoffsets();
YOffset* stop = yoff + head->fRowCount;
uint8_t* base = head->data();
int leftZeros = width;
int riteZeros = width;
while (yoff < stop) {
int L, R;
count_left_right_zeros(base + yoff->fOffset, width, &L, &R);
if (L < leftZeros) {
leftZeros = L;
}
if (R < riteZeros) {
riteZeros = R;
}
if (0 == (leftZeros | riteZeros)) {
// no trimming to do
return true;
}
yoff += 1;
}
SkASSERT(leftZeros || riteZeros);
if (width == (leftZeros + riteZeros)) {
return this->setEmpty();
}
this->validate();
fBounds.fLeft += leftZeros;
fBounds.fRight -= riteZeros;
SkASSERT(!fBounds.isEmpty());
// For now we don't realloc the storage (for time), we just shrink in place
// This means we don't have to do any memmoves either, since we can just
// play tricks with the yoff->fOffset for each row
yoff = head->yoffsets();
while (yoff < stop) {
uint8_t* row = base + yoff->fOffset;
SkDEBUGCODE((void)compute_row_length(row, width);)
yoff->fOffset += trim_row_left_right(row, width, leftZeros, riteZeros);
SkDEBUGCODE((void)compute_row_length(base + yoff->fOffset, width - leftZeros - riteZeros);)
yoff += 1;
}
return true;
}
static bool row_is_all_zeros(const uint8_t* row, int width) {
SkASSERT(width > 0);
do {
if (row[1]) {
return false;
}
int n = row[0];
SkASSERT(n <= width);
width -= n;
row += 2;
} while (width > 0);
SkASSERT(0 == width);
return true;
}
bool SkAAClip::trimTopBottom() {
if (this->isEmpty()) {
return false;
}
const int width = fBounds.width();
RunHead* head = fRunHead;
YOffset* yoff = head->yoffsets();
YOffset* stop = yoff + head->fRowCount;
const uint8_t* base = head->data();
// Look to trim away empty rows from the top.
//
int skip = 0;
while (yoff < stop) {
const uint8_t* data = base + yoff->fOffset;
if (!row_is_all_zeros(data, width)) {
break;
}
skip += 1;
yoff += 1;
}
SkASSERT(skip <= head->fRowCount);
if (skip == head->fRowCount) {
return this->setEmpty();
}
if (skip > 0) {
// adjust fRowCount and fBounds.fTop, and slide all the data up
// as we remove [skip] number of YOffset entries
yoff = head->yoffsets();
int dy = yoff[skip - 1].fY + 1;
for (int i = skip; i < head->fRowCount; ++i) {
SkASSERT(yoff[i].fY >= dy);
yoff[i].fY -= dy;
}
YOffset* dst = head->yoffsets();
size_t size = head->fRowCount * sizeof(YOffset) + head->fDataSize;
memmove(dst, dst + skip, size - skip * sizeof(YOffset));
fBounds.fTop += dy;
SkASSERT(!fBounds.isEmpty());
head->fRowCount -= skip;
SkASSERT(head->fRowCount > 0);
}
// Look to trim away empty rows from the bottom.
// We know that we have at least one non-zero row, so we can just walk
// backwards without checking for running past the start.
//
stop = yoff = head->yoffsets() + head->fRowCount;
do {
yoff -= 1;
} while (row_is_all_zeros(base + yoff->fOffset, width));
skip = stop - yoff - 1;
SkASSERT(skip >= 0 && skip < head->fRowCount);
if (skip > 0) {
// removing from the bottom is easier than from the top, as we don't
// have to adjust any of the Y values, we just have to trim the array
memmove(stop - skip, stop, head->fDataSize);
fBounds.fBottom = fBounds.fTop + yoff->fY + 1;
SkASSERT(!fBounds.isEmpty());
head->fRowCount -= skip;
SkASSERT(head->fRowCount > 0);
}
return true;
}
// can't validate before we're done, since trimming is part of the process of
// making us valid after the Builder. Since we build from top to bottom, its
// possible our fBounds.fBottom is bigger than our last scanline of data, so
// we trim fBounds.fBottom back up.
//
// TODO: look to trim our bounds on top, left, right.
// TODO: check for duplicates in X and Y to further compress our data
//
bool SkAAClip::trimBounds() {
@ -243,7 +544,9 @@ bool SkAAClip::trimBounds() {
SkASSERT(lastY.fY + 1 <= fBounds.height());
fBounds.fBottom = fBounds.fTop + lastY.fY + 1;
SkASSERT(lastY.fY + 1 == fBounds.height());
return true;
SkASSERT(!fBounds.isEmpty());
return this->trimTopBottom() && this->trimLeftRight();
}
///////////////////////////////////////////////////////////////////////////////
@ -563,13 +866,21 @@ public:
uint8_t* baseData = data;
row = fRows.begin();
SkDEBUGCODE(int prevY = row->fY - 1;)
while (row < stop) {
SkASSERT(prevY < row->fY); // must be monotonic
SkDEBUGCODE(prevY = row->fY);
yoffset->fY = row->fY - adjustY;
yoffset->fOffset = data - baseData;
yoffset += 1;
size_t n = row->fData->count();
memcpy(data, row->fData->begin(), n);
#ifdef SK_DEBUG
int bytesNeeded = compute_row_length(data, fBounds.width());
SkASSERT(bytesNeeded == n);
#endif
data += n;
row += 1;

View File

@ -81,6 +81,8 @@ private:
void freeRuns();
bool trimBounds();
bool trimTopBottom();
bool trimLeftRight();
friend class Builder;
class BuilderBlitter;

View File

@ -11,19 +11,16 @@
#include "SkRandom.h"
static const SkRegion::Op gRgnOps[] = {
// SkRegion::kDifference_Op,
SkRegion::kDifference_Op,
SkRegion::kIntersect_Op,
SkRegion::kUnion_Op,
SkRegion::kXOR_Op,
// SkRegion::kReverseDifference_Op,
SkRegion::kReverseDifference_Op,
SkRegion::kReplace_Op
};
static const char* gRgnOpNames[] = {
// "DIFF",
"SECT", "UNION", "XOR",
// "RDIFF",
"REPLACE"
"DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
};
static void imoveTo(SkPath& path, int x, int y) {
@ -104,7 +101,7 @@ static void rand_irect(SkIRect* r, int N, SkRandom& rand) {
static void test_irect(skiatest::Reporter* reporter) {
SkRandom rand;
for (int i = 0; i < 100; i++) {
for (int i = 0; i < 10000; i++) {
SkAAClip clip0, clip1;
SkRegion rgn0, rgn1;
SkIRect r0, r1;