staticint ComputeRowSizeForWidth(int width) { // 2 bytes per segment, where each segment can store up to 255 for count int segments = 0; while (width > 0) {
segments += 1; int n = std::min(width, 255);
width -= n;
} return segments * 2; // each segment is row[0] + row[1] (n + alpha)
}
static Iter Iterate(const SkAAClip& clip) { const RunHead* head = clip.fRunHead; if (!clip.fRunHead) { // A null run head is an empty clip, so return aan already finished iterator. return Iter();
}
SkIRect fBounds; struct Row { int fY; int fWidth;
SkTDArray<uint8_t>* fData;
};
SkTDArray<Row> fRows;
Row* fCurrRow; int fPrevY; int fWidth; int fMinY;
void addColumn(int x, int y, U8CPU alpha, int height) {
SkASSERT(fBounds.contains(x, y + height - 1));
this->addRun(x, y, alpha, 1);
this->flushRowH(fCurrRow);
y -= fBounds.fTop;
SkASSERT(y == fCurrRow->fY);
fCurrRow->fY = y + height - 1;
}
void addRectRun(int x, int y, int width, int height) {
SkASSERT(fBounds.contains(x + width - 1, y + height - 1));
this->addRun(x, y, 0xFF, width);
// we assum the rect must be all we'll see for these scanlines // so we ensure our row goes all the way to our right
this->flushRowH(fCurrRow);
y -= fBounds.fTop;
SkASSERT(y == fCurrRow->fY);
fCurrRow->fY = y + height - 1;
}
void addAntiRectRun(int x, int y, int width, int height,
SkAlpha leftAlpha, SkAlpha rightAlpha) { // According to SkBlitter.cpp, no matter whether leftAlpha is 0 or positive, // we should always consider [x, x+1] as the left-most column and [x+1, x+1+width] // as the rect with full alpha.
SkASSERT(fBounds.contains(x + width + (rightAlpha > 0 ? 1 : 0),
y + height - 1));
SkASSERT(width >= 0);
// Conceptually we're always adding 3 runs, but we should // merge or omit them if possible. if (leftAlpha == 0xFF) {
width++;
} elseif (leftAlpha > 0) {
this->addRun(x++, y, leftAlpha, 1);
} else { // leftAlpha is 0, ignore the left column
x++;
} if (rightAlpha == 0xFF) {
width++;
} if (width > 0) {
this->addRun(x, y, 0xFF, width);
} if (rightAlpha > 0 && rightAlpha < 255) {
this->addRun(x + width, y, rightAlpha, 1);
}
// if we never called addRun, we might not have a fCurrRow yet if (fCurrRow) { // we assume the rect must be all we'll see for these scanlines // so we ensure our row goes all the way to our right
this->flushRowH(fCurrRow);
y -= fBounds.fTop;
SkASSERT(y == fCurrRow->fY);
fCurrRow->fY = y + height - 1;
}
}
void SkAAClip::Builder::operateY(const SkAAClip& A, const SkAAClip& B, SkClipOp op) { staticconst AlphaProc kDiff = [](U8CPU a, U8CPU b) { return SkMulDiv255Round(a, 0xFF - b); }; staticconst AlphaProc kIntersect = [](U8CPU a, U8CPU b) { return SkMulDiv255Round(a, b); };
AlphaProc proc = (op == SkClipOp::kDifference) ? kDiff : kIntersect;
Iter iterA = RunHead::Iterate(A);
Iter iterB = RunHead::Iterate(B);
SkASSERT(!iterA.done()); int topA = iterA.top(); int botA = iterA.bottom();
SkASSERT(!iterB.done()); int topB = iterB.top(); int botB = iterB.bottom();
auto advanceIter = [](Iter& iter, int& iterTop, int& iterBot, int bot) { if (bot == iterBot) {
iter.next();
iterTop = iterBot;
SkASSERT(iterBot == iter.top());
iterBot = iter.bottom();
}
};
/** Must evaluate clips in scan-line order, so don't want to allow blitV(), but an AAClip can be clipped down to a single pixel wide, so we must support it (given AntiRect semantics: minimum width is 2). Instead we'll rely on the runtime asserts to guarantee Y monotonicity; any failure cases that misses may have minor artifacts.
*/ void blitV(int x, int y, int height, SkAlpha alpha) override { if (height == 1) { // We're still in scan-line order if height is 1 // This is useful for Analytic AA const SkAlpha alphas[2] = {alpha, 0}; const int16_t runs[2] = {1, 0};
this->blitAntiH(x, y, alphas, runs);
} else {
this->recordMinY(y);
fBuilder->addColumn(x, y, alpha, height);
fLastY = y + height - 1;
}
}
void blitRect(int x, int y, int width, int height) override {
this->recordMinY(y);
this->checkForYGap(y);
fBuilder->addRectRun(x, y, width, height);
fLastY = y + height - 1;
}
void blitAntiRect(int x, int y, int width, int height,
SkAlpha leftAlpha, SkAlpha rightAlpha) override {
this->recordMinY(y);
this->checkForYGap(y);
fBuilder->addAntiRectRun(x, y, width, height, leftAlpha, rightAlpha);
fLastY = y + height - 1;
}
void blitH(int x, int y, int width) override {
this->recordMinY(y);
this->checkForYGap(y);
fBuilder->addRun(x, y, 0xFF, width);
}
void blitAntiH(int x, int y, const SkAlpha alpha[], const int16_t runs[]) override {
this->recordMinY(y);
this->checkForYGap(y); for (;;) { int count = *runs; if (count <= 0) { return;
}
// The supersampler's buffer can be the width of the device, so // we may have to trim the run to our bounds. Previously, we assert that // the extra spans are always alpha==0. // However, the analytic AA is too sensitive to precision errors // so it may have extra spans with very tiny alpha because after several // arithmatic operations, the edge may bleed the path boundary a little bit. // Therefore, instead of always asserting alpha==0, we assert alpha < 0x10. int localX = x; int localCount = count; if (x < fLeft) {
SkASSERT(0x10 > *alpha); int gap = fLeft - x;
SkASSERT(gap <= count);
localX += gap;
localCount -= gap;
} int right = x + count; if (right > fRight) {
SkASSERT(0x10 > *alpha);
localCount -= right - fRight;
SkASSERT(localCount >= 0);
}
if (localCount) {
fBuilder->addRun(localX, y, *alpha, localCount);
} // Next run
runs += count;
alpha += count;
x += count;
}
}
private:
Builder* fBuilder; int fLeft; // cache of builder's bounds' left edge int fRight; int fMinY;
/* * We track this, in case the scan converter skipped some number of * scanlines at the (relative to the bounds it was given). This allows * the builder, during its finish, to trip its bounds down to the "real" * top.
*/ void recordMinY(int y) { if (y < fMinY) {
fMinY = y;
}
}
void unexpected() {
SK_ABORT("---- did not expect to get called here");
}
};
Iter iter = RunHead::Iterate(*this);
uint8_t* dst = mask->image(); constint width = fBounds.width();
int y = fBounds.fTop; while (!iter.done()) { do {
expandRowToMask(dst, iter.data(), width);
dst += mask->fRowBytes;
} while (++y < iter.bottom());
iter.next();
}
}
// Y and offset must be monotonic int prevY = -1;
int32_t prevOffset = -1; while (yoff < ystop) {
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());
SkASSERT(yoff->fOffset + rowLength <= head->fDataSize);
yoff += 1;
} // check the last entry;
--yoff;
SkASSERT(yoff->fY == lastY);
}
staticvoid dump_one_row(const uint8_t* SK_RESTRICT row, int width, int leading_num) { if (leading_num) {
SkDebugf( "%03d ", leading_num );
} while (width > 0) { int n = row[0]; int val = row[1]; char out = '.'; if (val == 0xff) {
out = '*';
} elseif (val > 0) {
out = '+';
} for (int i = 0 ; i < n ; i++) {
SkDebugf( "%c", out );
}
row += 2;
width -= n;
}
SkDebugf( "\n" );
}
void SkAAClip::debug(bool compress_y) const {
Iter iter = RunHead::Iterate(*this); constint width = fBounds.width();
int y = fBounds.fTop; while (!iter.done()) { if (compress_y) {
dump_one_row(iter.data(), width, iter.bottom() - iter.top() + 1);
} else { do {
dump_one_row(iter.data(), width, 0);
} while (++y < iter.bottom());
}
iter.next();
}
} #endif
// Count the number of zeros on the left and right edges of the passed in // RLE row. If 'row' is all zeros return 'width' in both variables. staticvoid 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;
if (0 == width) { // this line is completely empty return 'width' in both variables
*riteZ = *leftZ; return;
}
// 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 staticint 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;
}
bool SkAAClip::trimLeftRight() { if (this->isEmpty()) { returnfalse;
}
// After this loop, 'leftZeros' & 'rightZeros' will contain the minimum // number of zeros on the left and right of the clip. This information // can be used to shrink the bounding box. int leftZeros = width; int riteZeros = width; while (yoff < stop) { int L, R;
count_left_right_zeros(base + yoff->fOffset, width, &L, &R);
SkASSERT(L + R < width || (L == width && R == width)); if (L < leftZeros) {
leftZeros = L;
} if (R < riteZeros) {
riteZeros = R;
} if (0 == (leftZeros | riteZeros)) { // no trimming to do returntrue;
}
yoff += 1;
}
// 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;
} returntrue;
}
staticbool row_is_all_zeros(const uint8_t* row, int width) {
SkASSERT(width > 0); do { if (row[1]) { returnfalse;
} int n = row[0];
SkASSERT(n <= width);
width -= n;
row += 2;
} while (width > 0);
SkASSERT(0 == width); returntrue;
}
bool SkAAClip::trimTopBottom() { if (this->isEmpty()) { returnfalse;
}
// 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));
this->validate(); // need to reset this after the memmove
base = head->data();
}
// 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 = SkToInt(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);
// 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: check for duplicates in X and Y to further compress our data // bool SkAAClip::trimBounds() { if (this->isEmpty()) { returnfalse;
}
const RunHead* head = fRunHead; const YOffset* yoff = head->yoffsets();
auto appendXRun = [&xArray](uint8_t value, int count) {
SkASSERT(count >= 0); while (count > 0) { int n = count; if (n > 255) {
n = 255;
}
uint8_t* data = xArray.append(2);
data[0] = n;
data[1] = value;
count -= n;
}
};
SkRegion::Iterator iter(rgn); int prevRight = 0; int prevBot = 0;
YOffset* currY = nullptr;
for (; !iter.done(); iter.next()) { const SkIRect& r = iter.rect();
SkASSERT(bounds.contains(r));
int bot = r.fBottom - offsetY;
SkASSERT(bot >= prevBot); if (bot > prevBot) { if (currY) { // flush current row
appendXRun(0, bounds.width() - prevRight);
} // did we introduce an empty-gap from the prev row? int top = r.fTop - offsetY; if (top > prevBot) {
currY = yArray.append();
currY->fY = top - 1;
currY->fOffset = xArray.size();
appendXRun(0, bounds.width());
} // create a new record for this Y value
currY = yArray.append();
currY->fY = bot - 1;
currY->fOffset = xArray.size();
prevRight = 0;
prevBot = bot;
}
int x = r.fLeft - offsetX;
appendXRun(0, x - prevRight);
int w = r.fRight - r.fLeft;
appendXRun(0xFF, w);
prevRight = x + w;
SkASSERT(prevRight <= bounds.width());
} // flush last row
appendXRun(0, bounds.width() - prevRight);
// now pack everything into a RunHead
RunHead* head = RunHead::Alloc(yArray.size(), xArray.size_bytes());
memcpy(head->yoffsets(), yArray.begin(), yArray.size_bytes());
memcpy(head->data(), xArray.begin(), xArray.size_bytes());
SkIRect ibounds; // Since we assert that the BuilderBlitter will never blit outside the intersection // of clip and ibounds, we create the builder with the snug bounds. if (path.isInverseFillType()) {
ibounds = clip;
} else {
path.getBounds().roundOut(&ibounds); // Since clip is already validated with isEmpty, it is safe to use isEmpty64 // for ibounds as it will be intersected with clip after. if (ibounds.isEmpty64() || !ibounds.intersect(clip)) { return this->setEmpty();
}
}
bool SkAAClip::op(const SkIRect& rect, SkClipOp op) { // It can be expensive to build a local aaclip before applying the op, so // we first see if we can restrict the bounds of new rect to our current // bounds, or note that the new rect subsumes our current clip.
SkIRect pixelBounds = fBounds; if (!pixelBounds.intersect(rect)) { // No change or clip becomes empty depending on 'op' switch(op) { case SkClipOp::kDifference: return !this->isEmpty(); case SkClipOp::kIntersect: return this->setEmpty();
}
SkUNREACHABLE;
} elseif (pixelBounds == fBounds) { // Wholly inside 'rect', so clip becomes empty or remains unchanged switch(op) { case SkClipOp::kDifference: return this->setEmpty(); case SkClipOp::kIntersect: return !this->isEmpty();
}
SkUNREACHABLE;
} elseif (op == SkClipOp::kIntersect && this->quickContains(pixelBounds)) { // We become just the remaining rectangle return this->setRect(pixelBounds);
} else {
SkAAClip clip;
clip.setRect(rect); return this->op(clip, op);
}
}
bool SkAAClip::op(const SkRect& rect, SkClipOp op, bool doAA) { if (!doAA) { return this->op(rect.round(), op);
} else { // Tighten bounds for "path" aaclip of the rect
SkIRect pixelBounds = fBounds; if (!pixelBounds.intersect(rect.roundOut())) { // No change or clip becomes empty depending on 'op' switch(op) { case SkClipOp::kDifference: return !this->isEmpty(); case SkClipOp::kIntersect: return this->setEmpty();
}
SkUNREACHABLE;
} elseif (rect.contains(SkRect::Make(fBounds))) { // Wholly inside 'rect', so clip becomes empty or remains unchanged switch(op) { case SkClipOp::kDifference: return this->setEmpty(); case SkClipOp::kIntersect: return !this->isEmpty();
}
SkUNREACHABLE;
} elseif (op == SkClipOp::kIntersect && this->quickContains(pixelBounds)) { // We become just the rect intersected with pixel bounds (preserving fractional coords // for AA edges). return this->setPath(SkPath::Rect(rect), pixelBounds, /*doAA=*/true);
} else {
SkAAClip rectClip;
rectClip.setPath(SkPath::Rect(rect),
op == SkClipOp::kDifference ? fBounds : pixelBounds, /*doAA=*/true); return this->op(rectClip, op);
}
}
}
const uint8_t* SkAAClip::findX(const uint8_t data[], int x, int* initialCount) const {
SkASSERT(x >= fBounds.fLeft && x < fBounds.fRight);
x -= fBounds.x();
// first skip up to X for (;;) { int n = data[0]; if (x < n) { if (initialCount) {
*initialCount = n - x;
} break;
}
data += 2;
x -= n;
} return data;
}
bool SkAAClip::quickContains(int left, int top, int right, int bottom) const { if (this->isEmpty()) { returnfalse;
} if (!fBounds.contains(SkIRect{left, top, right, bottom})) { returnfalse;
}
int lastY SK_INIT_TO_AVOID_WARNING; const uint8_t* row = this->findRow(top, &lastY); if (lastY < bottom) { returnfalse;
} // now just need to check in X int count;
row = this->findX(row, left, &count);
int rectWidth = right - left; while (0xFF == row[1]) { if (count >= rectWidth) { returntrue;
}
rectWidth -= count;
row += 2;
count = row[0];
} returnfalse;
}
staticvoid expandToRuns(const uint8_t* SK_RESTRICT data, int initialCount, int width,
int16_t* SK_RESTRICT runs, SkAlpha* SK_RESTRICT aa) { // we don't read our initial n from data, since the caller may have had to // clip it, hence the initialCount parameter. int n = initialCount; for (;;) { if (n > width) {
n = width;
}
SkASSERT(n > 0);
runs[0] = n;
runs += n;
aa[0] = data[1];
aa += n;
data += 2;
width -= n; if (0 == width) { break;
} // load the next count
n = data[0];
}
runs[0] = 0; // sentinel
}
void SkAAClipBlitter::ensureRunsAndAA() { if (nullptr == fScanlineScratch) { // add 1 so we can store the terminating run count of 0 int count = fAAClipBounds.width() + 1; // we use this either for fRuns + fAA, or a scaline of a mask // which may be as deep as 32bits
fScanlineScratch = sk_malloc_throw(count * sizeof(SkPMColor));
fRuns = (int16_t*)fScanlineScratch;
fAA = (SkAlpha*)(fRuns + count);
}
}
void SkAAClipBlitter::blitH(int x, int y, int width) {
SkASSERT(width > 0);
SkASSERT(fAAClipBounds.contains(x, y));
SkASSERT(fAAClipBounds.contains(x + width - 1, y));
void SkAAClipBlitter::blitV(int x, int y, int height, SkAlpha alpha) { if (fAAClip->quickContains(x, y, x + 1, y + height)) {
fBlitter->blitV(x, y, height, alpha); return;
}
for (;;) { int lastY SK_INIT_TO_AVOID_WARNING; const uint8_t* row = fAAClip->findRow(y, &lastY); int dy = lastY - y + 1; if (dy > height) {
dy = height;
}
height -= dy;
row = fAAClip->findX(row, x);
SkAlpha newAlpha = SkMulDiv255Round(alpha, row[1]); if (newAlpha) {
fBlitter->blitV(x, y, dy, newAlpha);
}
SkASSERT(height >= 0); if (height <= 0) { break;
}
y = lastY + 1;
}
}
void SkAAClipBlitter::blitRect(int x, int y, int width, int height) { if (fAAClip->quickContains(x, y, x + width, y + height)) {
fBlitter->blitRect(x, y, width, height); return;
}
while (--height >= 0) {
this->blitH(x, y, width);
y += 1;
}
}
typedefvoid (*MergeAAProc)(constvoid* src, int width, const uint8_t* row, int initialRowCount, void* dst);
int n = std::min(rowN, srcN); unsigned rowA = row[1]; if (0xFF == rowA) {
small_memcpy(dst, src, n * sizeof(T));
} elseif (0 == rowA) {
small_bzero(dst, n * sizeof(T));
} else { for (int i = 0; i < n; ++i) {
dst[i] = mergeOne(src[i], rowA);
}
}
if (0 == (srcN -= n)) { break;
}
src += n;
dst += n;
SkASSERT(rowN == n);
row += 2;
rowN = row[0];
}
}
static MergeAAProc find_merge_aa_proc(SkMask::Format format) { switch (format) { case SkMask::kBW_Format:
SkDEBUGFAIL("unsupported"); return nullptr; case SkMask::kA8_Format: case SkMask::k3D_Format: return mergeT<uint8_t> ; case SkMask::kLCD16_Format: return mergeT<uint16_t>; default:
SkDEBUGFAIL("unsupported"); return nullptr;
}
}
static U8CPU bit2byte(int bitInAByte) {
SkASSERT(bitInAByte <= 0xFF); // negation turns any non-zero into 0xFFFFFF??, so we just shift down // some value >= 8 to get a full FF value return -bitInAByte >> 8;
}
int y = clip.fTop; constint stopY = y + clip.height();
do { int localStopY SK_INIT_TO_AVOID_WARNING; const uint8_t* row = fAAClip->findRow(y, &localStopY); // findRow returns last Y, not stop, so we add 1
localStopY = std::min(localStopY + 1, stopY);
int initialCount;
row = fAAClip->findX(row, clip.fLeft, &initialCount); do {
mergeProc(src, width, row, initialCount, rowMask.image());
rowMask.bounds().fTop = y;
rowMask.bounds().fBottom = y + 1;
fBlitter->blitMask(rowMask, rowMask.fBounds);
src = (constvoid*)((constchar*)src + srcRB);
} while (++y < localStopY);
} while (y < stopY);
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.36 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.