/*
|
* 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 "SkAAClip.h"
|
|
#include "SkBlitter.h"
|
#include "SkColorData.h"
|
#include "SkMacros.h"
|
#include "SkPath.h"
|
#include "SkRectPriv.h"
|
#include "SkScan.h"
|
#include "SkTo.h"
|
#include "SkUTF.h"
|
#include <atomic>
|
#include <utility>
|
|
class AutoAAClipValidate {
|
public:
|
AutoAAClipValidate(const SkAAClip& clip) : fClip(clip) {
|
fClip.validate();
|
}
|
~AutoAAClipValidate() {
|
fClip.validate();
|
}
|
private:
|
const SkAAClip& fClip;
|
};
|
|
#ifdef SK_DEBUG
|
#define AUTO_AACLIP_VALIDATE(clip) AutoAAClipValidate acv(clip)
|
#else
|
#define AUTO_AACLIP_VALIDATE(clip)
|
#endif
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#define kMaxInt32 0x7FFFFFFF
|
|
#ifdef SK_DEBUG
|
static inline bool x_in_rect(int x, const SkIRect& rect) {
|
return (unsigned)(x - rect.fLeft) < (unsigned)rect.width();
|
}
|
#endif
|
|
static inline bool y_in_rect(int y, const SkIRect& rect) {
|
return (unsigned)(y - rect.fTop) < (unsigned)rect.height();
|
}
|
|
/*
|
* Data runs are packed [count, alpha]
|
*/
|
|
struct SkAAClip::YOffset {
|
int32_t fY;
|
uint32_t fOffset;
|
};
|
|
struct SkAAClip::RunHead {
|
std::atomic<int32_t> fRefCnt;
|
int32_t fRowCount;
|
size_t fDataSize;
|
|
YOffset* yoffsets() {
|
return (YOffset*)((char*)this + sizeof(RunHead));
|
}
|
const YOffset* yoffsets() const {
|
return (const YOffset*)((const char*)this + sizeof(RunHead));
|
}
|
uint8_t* data() {
|
return (uint8_t*)(this->yoffsets() + fRowCount);
|
}
|
const uint8_t* data() const {
|
return (const uint8_t*)(this->yoffsets() + fRowCount);
|
}
|
|
static RunHead* Alloc(int rowCount, size_t dataSize) {
|
size_t size = sizeof(RunHead) + rowCount * sizeof(YOffset) + dataSize;
|
RunHead* head = (RunHead*)sk_malloc_throw(size);
|
head->fRefCnt.store(1);
|
head->fRowCount = rowCount;
|
head->fDataSize = dataSize;
|
return head;
|
}
|
|
static int 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 = SkMin32(width, 255);
|
width -= n;
|
}
|
return segments * 2; // each segment is row[0] + row[1] (n + alpha)
|
}
|
|
static RunHead* AllocRect(const SkIRect& bounds) {
|
SkASSERT(!bounds.isEmpty());
|
int width = bounds.width();
|
size_t rowSize = ComputeRowSizeForWidth(width);
|
RunHead* head = RunHead::Alloc(1, rowSize);
|
YOffset* yoff = head->yoffsets();
|
yoff->fY = bounds.height() - 1;
|
yoff->fOffset = 0;
|
uint8_t* row = head->data();
|
while (width > 0) {
|
int n = SkMin32(width, 255);
|
row[0] = n;
|
row[1] = 0xFF;
|
width -= n;
|
row += 2;
|
}
|
return head;
|
}
|
};
|
|
class SkAAClip::Iter {
|
public:
|
Iter(const SkAAClip&);
|
|
bool done() const { return fDone; }
|
int top() const { return fTop; }
|
int bottom() const { return fBottom; }
|
const uint8_t* data() const { return fData; }
|
void next();
|
|
private:
|
const YOffset* fCurrYOff;
|
const YOffset* fStopYOff;
|
const uint8_t* fData;
|
|
int fTop, fBottom;
|
bool fDone;
|
};
|
|
SkAAClip::Iter::Iter(const SkAAClip& clip) {
|
if (clip.isEmpty()) {
|
fDone = true;
|
fTop = fBottom = clip.fBounds.fBottom;
|
fData = nullptr;
|
fCurrYOff = nullptr;
|
fStopYOff = nullptr;
|
return;
|
}
|
|
const RunHead* head = clip.fRunHead;
|
fCurrYOff = head->yoffsets();
|
fStopYOff = fCurrYOff + head->fRowCount;
|
fData = head->data() + fCurrYOff->fOffset;
|
|
// setup first value
|
fTop = clip.fBounds.fTop;
|
fBottom = clip.fBounds.fTop + fCurrYOff->fY + 1;
|
fDone = false;
|
}
|
|
void SkAAClip::Iter::next() {
|
if (!fDone) {
|
const YOffset* prev = fCurrYOff;
|
const YOffset* curr = prev + 1;
|
SkASSERT(curr <= fStopYOff);
|
|
fTop = fBottom;
|
if (curr >= fStopYOff) {
|
fDone = true;
|
fBottom = kMaxInt32;
|
fData = nullptr;
|
} else {
|
fBottom += curr->fY - prev->fY;
|
fData += curr->fOffset - prev->fOffset;
|
fCurrYOff = curr;
|
}
|
}
|
}
|
|
#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;
|
}
|
SkASSERT(0 == width);
|
return row - origRow;
|
}
|
|
void SkAAClip::validate() const {
|
if (nullptr == fRunHead) {
|
SkASSERT(fBounds.isEmpty());
|
return;
|
}
|
SkASSERT(!fBounds.isEmpty());
|
|
const RunHead* head = fRunHead;
|
SkASSERT(head->fRefCnt.load() > 0);
|
SkASSERT(head->fRowCount > 0);
|
|
const YOffset* yoff = head->yoffsets();
|
const YOffset* ystop = yoff + head->fRowCount;
|
const int lastY = fBounds.height() - 1;
|
|
// 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);
|
}
|
|
static void 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 = '*';
|
} else if (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(*this);
|
const int 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.
|
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;
|
|
if (0 == width) {
|
// this line is completely empty return 'width' in both variables
|
*riteZ = *leftZ;
|
return;
|
}
|
|
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;
|
}
|
|
// 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;
|
}
|
|
bool SkAAClip::trimLeftRight() {
|
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();
|
|
// 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
|
return true;
|
}
|
yoff += 1;
|
}
|
|
SkASSERT(leftZeros || riteZeros);
|
if (width == leftZeros) {
|
SkASSERT(width == 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;
|
}
|
|
this->validate();
|
|
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);
|
|
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);
|
|
fBounds.fBottom = fBounds.fTop + yoff->fY + 1;
|
SkASSERT(!fBounds.isEmpty());
|
head->fRowCount -= skip;
|
SkASSERT(head->fRowCount > 0);
|
}
|
this->validate();
|
|
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: check for duplicates in X and Y to further compress our data
|
//
|
bool SkAAClip::trimBounds() {
|
if (this->isEmpty()) {
|
return false;
|
}
|
|
const RunHead* head = fRunHead;
|
const YOffset* yoff = head->yoffsets();
|
|
SkASSERT(head->fRowCount > 0);
|
const YOffset& lastY = yoff[head->fRowCount - 1];
|
SkASSERT(lastY.fY + 1 <= fBounds.height());
|
fBounds.fBottom = fBounds.fTop + lastY.fY + 1;
|
SkASSERT(lastY.fY + 1 == fBounds.height());
|
SkASSERT(!fBounds.isEmpty());
|
|
return this->trimTopBottom() && this->trimLeftRight();
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void SkAAClip::freeRuns() {
|
if (fRunHead) {
|
SkASSERT(fRunHead->fRefCnt.load() >= 1);
|
if (1 == fRunHead->fRefCnt--) {
|
sk_free(fRunHead);
|
}
|
}
|
}
|
|
SkAAClip::SkAAClip() {
|
fBounds.setEmpty();
|
fRunHead = nullptr;
|
}
|
|
SkAAClip::SkAAClip(const SkAAClip& src) {
|
SkDEBUGCODE(fBounds.setEmpty();) // need this for validate
|
fRunHead = nullptr;
|
*this = src;
|
}
|
|
SkAAClip::~SkAAClip() {
|
this->freeRuns();
|
}
|
|
SkAAClip& SkAAClip::operator=(const SkAAClip& src) {
|
AUTO_AACLIP_VALIDATE(*this);
|
src.validate();
|
|
if (this != &src) {
|
this->freeRuns();
|
fBounds = src.fBounds;
|
fRunHead = src.fRunHead;
|
if (fRunHead) {
|
fRunHead->fRefCnt++;
|
}
|
}
|
return *this;
|
}
|
|
bool operator==(const SkAAClip& a, const SkAAClip& b) {
|
a.validate();
|
b.validate();
|
|
if (&a == &b) {
|
return true;
|
}
|
if (a.fBounds != b.fBounds) {
|
return false;
|
}
|
|
const SkAAClip::RunHead* ah = a.fRunHead;
|
const SkAAClip::RunHead* bh = b.fRunHead;
|
|
// this catches empties and rects being equal
|
if (ah == bh) {
|
return true;
|
}
|
|
// now we insist that both are complex (but different ptrs)
|
if (!a.fRunHead || !b.fRunHead) {
|
return false;
|
}
|
|
return ah->fRowCount == bh->fRowCount &&
|
ah->fDataSize == bh->fDataSize &&
|
!memcmp(ah->data(), bh->data(), ah->fDataSize);
|
}
|
|
void SkAAClip::swap(SkAAClip& other) {
|
AUTO_AACLIP_VALIDATE(*this);
|
other.validate();
|
|
using std::swap;
|
swap(fBounds, other.fBounds);
|
swap(fRunHead, other.fRunHead);
|
}
|
|
bool SkAAClip::set(const SkAAClip& src) {
|
*this = src;
|
return !this->isEmpty();
|
}
|
|
bool SkAAClip::setEmpty() {
|
this->freeRuns();
|
fBounds.setEmpty();
|
fRunHead = nullptr;
|
return false;
|
}
|
|
bool SkAAClip::setRect(const SkIRect& bounds) {
|
if (bounds.isEmpty()) {
|
return this->setEmpty();
|
}
|
|
AUTO_AACLIP_VALIDATE(*this);
|
|
#if 0
|
SkRect r;
|
r.set(bounds);
|
SkPath path;
|
path.addRect(r);
|
return this->setPath(path);
|
#else
|
this->freeRuns();
|
fBounds = bounds;
|
fRunHead = RunHead::AllocRect(bounds);
|
SkASSERT(!this->isEmpty());
|
return true;
|
#endif
|
}
|
|
bool SkAAClip::isRect() const {
|
if (this->isEmpty()) {
|
return false;
|
}
|
|
const RunHead* head = fRunHead;
|
if (head->fRowCount != 1) {
|
return false;
|
}
|
const YOffset* yoff = head->yoffsets();
|
if (yoff->fY != fBounds.fBottom - 1) {
|
return false;
|
}
|
|
const uint8_t* row = head->data() + yoff->fOffset;
|
int width = fBounds.width();
|
do {
|
if (row[1] != 0xFF) {
|
return false;
|
}
|
int n = row[0];
|
SkASSERT(n <= width);
|
width -= n;
|
row += 2;
|
} while (width > 0);
|
return true;
|
}
|
|
bool SkAAClip::setRect(const SkRect& r, bool doAA) {
|
if (r.isEmpty()) {
|
return this->setEmpty();
|
}
|
|
AUTO_AACLIP_VALIDATE(*this);
|
|
// TODO: special case this
|
|
SkPath path;
|
path.addRect(r);
|
return this->setPath(path, nullptr, doAA);
|
}
|
|
static void append_run(SkTDArray<uint8_t>& array, uint8_t value, int count) {
|
SkASSERT(count >= 0);
|
while (count > 0) {
|
int n = count;
|
if (n > 255) {
|
n = 255;
|
}
|
uint8_t* data = array.append(2);
|
data[0] = n;
|
data[1] = value;
|
count -= n;
|
}
|
}
|
|
bool SkAAClip::setRegion(const SkRegion& rgn) {
|
if (rgn.isEmpty()) {
|
return this->setEmpty();
|
}
|
if (rgn.isRect()) {
|
return this->setRect(rgn.getBounds());
|
}
|
|
#if 0
|
SkAAClip clip;
|
SkRegion::Iterator iter(rgn);
|
for (; !iter.done(); iter.next()) {
|
clip.op(iter.rect(), SkRegion::kUnion_Op);
|
}
|
this->swap(clip);
|
return !this->isEmpty();
|
#else
|
const SkIRect& bounds = rgn.getBounds();
|
const int offsetX = bounds.fLeft;
|
const int offsetY = bounds.fTop;
|
|
SkTDArray<YOffset> yArray;
|
SkTDArray<uint8_t> xArray;
|
|
yArray.setReserve(SkMin32(bounds.height(), 1024));
|
xArray.setReserve(SkMin32(bounds.width(), 512) * 128);
|
|
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
|
append_run(xArray, 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.count();
|
append_run(xArray, 0, bounds.width());
|
}
|
// create a new record for this Y value
|
currY = yArray.append();
|
currY->fY = bot - 1;
|
currY->fOffset = xArray.count();
|
prevRight = 0;
|
prevBot = bot;
|
}
|
|
int x = r.fLeft - offsetX;
|
append_run(xArray, 0, x - prevRight);
|
|
int w = r.fRight - r.fLeft;
|
append_run(xArray, 0xFF, w);
|
prevRight = x + w;
|
SkASSERT(prevRight <= bounds.width());
|
}
|
// flush last row
|
append_run(xArray, 0, bounds.width() - prevRight);
|
|
// now pack everything into a RunHead
|
RunHead* head = RunHead::Alloc(yArray.count(), xArray.bytes());
|
memcpy(head->yoffsets(), yArray.begin(), yArray.bytes());
|
memcpy(head->data(), xArray.begin(), xArray.bytes());
|
|
this->setEmpty();
|
fBounds = bounds;
|
fRunHead = head;
|
this->validate();
|
return true;
|
#endif
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
const uint8_t* SkAAClip::findRow(int y, int* lastYForRow) const {
|
SkASSERT(fRunHead);
|
|
if (!y_in_rect(y, fBounds)) {
|
return nullptr;
|
}
|
y -= fBounds.y(); // our yoffs values are relative to the top
|
|
const YOffset* yoff = fRunHead->yoffsets();
|
while (yoff->fY < y) {
|
yoff += 1;
|
SkASSERT(yoff - fRunHead->yoffsets() < fRunHead->fRowCount);
|
}
|
|
if (lastYForRow) {
|
*lastYForRow = fBounds.y() + yoff->fY;
|
}
|
return fRunHead->data() + yoff->fOffset;
|
}
|
|
const uint8_t* SkAAClip::findX(const uint8_t data[], int x, int* initialCount) const {
|
SkASSERT(x_in_rect(x, fBounds));
|
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()) {
|
return false;
|
}
|
if (!fBounds.contains(left, top, right, bottom)) {
|
return false;
|
}
|
#if 0
|
if (this->isRect()) {
|
return true;
|
}
|
#endif
|
|
int lastY SK_INIT_TO_AVOID_WARNING;
|
const uint8_t* row = this->findRow(top, &lastY);
|
if (lastY < bottom) {
|
return false;
|
}
|
// now just need to check in X
|
int count;
|
row = this->findX(row, left, &count);
|
#if 0
|
return count >= (right - left) && 0xFF == row[1];
|
#else
|
int rectWidth = right - left;
|
while (0xFF == row[1]) {
|
if (count >= rectWidth) {
|
return true;
|
}
|
rectWidth -= count;
|
row += 2;
|
count = row[0];
|
}
|
return false;
|
#endif
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
class SkAAClip::Builder {
|
SkIRect fBounds;
|
struct Row {
|
int fY;
|
int fWidth;
|
SkTDArray<uint8_t>* fData;
|
};
|
SkTDArray<Row> fRows;
|
Row* fCurrRow;
|
int fPrevY;
|
int fWidth;
|
int fMinY;
|
|
public:
|
Builder(const SkIRect& bounds) : fBounds(bounds) {
|
fPrevY = -1;
|
fWidth = bounds.width();
|
fCurrRow = nullptr;
|
fMinY = bounds.fTop;
|
}
|
|
~Builder() {
|
Row* row = fRows.begin();
|
Row* stop = fRows.end();
|
while (row < stop) {
|
delete row->fData;
|
row += 1;
|
}
|
}
|
|
const SkIRect& getBounds() const { return fBounds; }
|
|
void addRun(int x, int y, U8CPU alpha, int count) {
|
SkASSERT(count > 0);
|
SkASSERT(fBounds.contains(x, y));
|
SkASSERT(fBounds.contains(x + count - 1, y));
|
|
x -= fBounds.left();
|
y -= fBounds.top();
|
|
Row* row = fCurrRow;
|
if (y != fPrevY) {
|
SkASSERT(y > fPrevY);
|
fPrevY = y;
|
row = this->flushRow(true);
|
row->fY = y;
|
row->fWidth = 0;
|
SkASSERT(row->fData);
|
SkASSERT(0 == row->fData->count());
|
fCurrRow = row;
|
}
|
|
SkASSERT(row->fWidth <= x);
|
SkASSERT(row->fWidth < fBounds.width());
|
|
SkTDArray<uint8_t>& data = *row->fData;
|
|
int gap = x - row->fWidth;
|
if (gap) {
|
AppendRun(data, 0, gap);
|
row->fWidth += gap;
|
SkASSERT(row->fWidth < fBounds.width());
|
}
|
|
AppendRun(data, alpha, count);
|
row->fWidth += count;
|
SkASSERT(row->fWidth <= fBounds.width());
|
}
|
|
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++;
|
} else if (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;
|
}
|
}
|
|
bool finish(SkAAClip* target) {
|
this->flushRow(false);
|
|
const Row* row = fRows.begin();
|
const Row* stop = fRows.end();
|
|
size_t dataSize = 0;
|
while (row < stop) {
|
dataSize += row->fData->count();
|
row += 1;
|
}
|
|
if (0 == dataSize) {
|
return target->setEmpty();
|
}
|
|
SkASSERT(fMinY >= fBounds.fTop);
|
SkASSERT(fMinY < fBounds.fBottom);
|
int adjustY = fMinY - fBounds.fTop;
|
fBounds.fTop = fMinY;
|
|
RunHead* head = RunHead::Alloc(fRows.count(), dataSize);
|
YOffset* yoffset = head->yoffsets();
|
uint8_t* data = head->data();
|
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 = SkToU32(data - baseData);
|
yoffset += 1;
|
|
size_t n = row->fData->count();
|
memcpy(data, row->fData->begin(), n);
|
#ifdef SK_DEBUG
|
size_t bytesNeeded = compute_row_length(data, fBounds.width());
|
SkASSERT(bytesNeeded == n);
|
#endif
|
data += n;
|
|
row += 1;
|
}
|
|
target->freeRuns();
|
target->fBounds = fBounds;
|
target->fRunHead = head;
|
return target->trimBounds();
|
}
|
|
void dump() {
|
this->validate();
|
int y;
|
for (y = 0; y < fRows.count(); ++y) {
|
const Row& row = fRows[y];
|
SkDebugf("Y:%3d W:%3d", row.fY, row.fWidth);
|
const SkTDArray<uint8_t>& data = *row.fData;
|
int count = data.count();
|
SkASSERT(!(count & 1));
|
const uint8_t* ptr = data.begin();
|
for (int x = 0; x < count; x += 2) {
|
SkDebugf(" [%3d:%02X]", ptr[0], ptr[1]);
|
ptr += 2;
|
}
|
SkDebugf("\n");
|
}
|
}
|
|
void validate() {
|
#ifdef SK_DEBUG
|
int prevY = -1;
|
for (int i = 0; i < fRows.count(); ++i) {
|
const Row& row = fRows[i];
|
SkASSERT(prevY < row.fY);
|
SkASSERT(fWidth == row.fWidth);
|
int count = row.fData->count();
|
const uint8_t* ptr = row.fData->begin();
|
SkASSERT(!(count & 1));
|
int w = 0;
|
for (int x = 0; x < count; x += 2) {
|
int n = ptr[0];
|
SkASSERT(n > 0);
|
w += n;
|
SkASSERT(w <= fWidth);
|
ptr += 2;
|
}
|
SkASSERT(w == fWidth);
|
prevY = row.fY;
|
}
|
#endif
|
}
|
|
// only called by BuilderBlitter
|
void setMinY(int y) {
|
fMinY = y;
|
}
|
|
private:
|
void flushRowH(Row* row) {
|
// flush current row if needed
|
if (row->fWidth < fWidth) {
|
AppendRun(*row->fData, 0, fWidth - row->fWidth);
|
row->fWidth = fWidth;
|
}
|
}
|
|
Row* flushRow(bool readyForAnother) {
|
Row* next = nullptr;
|
int count = fRows.count();
|
if (count > 0) {
|
this->flushRowH(&fRows[count - 1]);
|
}
|
if (count > 1) {
|
// are our last two runs the same?
|
Row* prev = &fRows[count - 2];
|
Row* curr = &fRows[count - 1];
|
SkASSERT(prev->fWidth == fWidth);
|
SkASSERT(curr->fWidth == fWidth);
|
if (*prev->fData == *curr->fData) {
|
prev->fY = curr->fY;
|
if (readyForAnother) {
|
curr->fData->rewind();
|
next = curr;
|
} else {
|
delete curr->fData;
|
fRows.removeShuffle(count - 1);
|
}
|
} else {
|
if (readyForAnother) {
|
next = fRows.append();
|
next->fData = new SkTDArray<uint8_t>;
|
}
|
}
|
} else {
|
if (readyForAnother) {
|
next = fRows.append();
|
next->fData = new SkTDArray<uint8_t>;
|
}
|
}
|
return next;
|
}
|
|
static void AppendRun(SkTDArray<uint8_t>& data, U8CPU alpha, int count) {
|
do {
|
int n = count;
|
if (n > 255) {
|
n = 255;
|
}
|
uint8_t* ptr = data.append(2);
|
ptr[0] = n;
|
ptr[1] = alpha;
|
count -= n;
|
} while (count > 0);
|
}
|
};
|
|
class SkAAClip::BuilderBlitter : public SkBlitter {
|
int fLastY;
|
|
/*
|
If we see a gap of 1 or more empty scanlines while building in Y-order,
|
we inject an explicit empty scanline (alpha==0)
|
|
See AAClipTest.cpp : test_path_with_hole()
|
*/
|
void checkForYGap(int y) {
|
SkASSERT(y >= fLastY);
|
if (fLastY > -SK_MaxS32) {
|
int gap = y - fLastY;
|
if (gap > 1) {
|
fBuilder->addRun(fLeft, y - 1, 0, fRight - fLeft);
|
}
|
}
|
fLastY = y;
|
}
|
|
public:
|
|
BuilderBlitter(Builder* builder) {
|
fBuilder = builder;
|
fLeft = builder->getBounds().fLeft;
|
fRight = builder->getBounds().fRight;
|
fMinY = SK_MaxS32;
|
fLastY = -SK_MaxS32; // sentinel
|
}
|
|
void finish() {
|
if (fMinY < SK_MaxS32) {
|
fBuilder->setMinY(fMinY);
|
}
|
}
|
|
/**
|
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;
|
}
|
|
virtual 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 blitMask(const SkMask&, const SkIRect& clip) override
|
{ unexpected(); }
|
|
const SkPixmap* justAnOpaqueColor(uint32_t*) override {
|
return nullptr;
|
}
|
|
void blitH(int x, int y, int width) override {
|
this->recordMinY(y);
|
this->checkForYGap(y);
|
fBuilder->addRun(x, y, 0xFF, width);
|
}
|
|
virtual 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");
|
}
|
};
|
|
bool SkAAClip::setPath(const SkPath& path, const SkRegion* clip, bool doAA) {
|
AUTO_AACLIP_VALIDATE(*this);
|
|
if (clip && clip->isEmpty()) {
|
return this->setEmpty();
|
}
|
|
SkIRect ibounds;
|
path.getBounds().roundOut(&ibounds);
|
|
SkRegion tmpClip;
|
if (nullptr == clip) {
|
tmpClip.setRect(ibounds);
|
clip = &tmpClip;
|
}
|
|
// Since we assert that the BuilderBlitter will never blit outside the intersection
|
// of clip and ibounds, we create this snugClip to be that intersection and send it
|
// to the scan-converter.
|
SkRegion snugClip(*clip);
|
|
if (path.isInverseFillType()) {
|
ibounds = clip->getBounds();
|
} else {
|
if (ibounds.isEmpty() || !ibounds.intersect(clip->getBounds())) {
|
return this->setEmpty();
|
}
|
snugClip.op(ibounds, SkRegion::kIntersect_Op);
|
}
|
|
Builder builder(ibounds);
|
BuilderBlitter blitter(&builder);
|
|
if (doAA) {
|
SkScan::AntiFillPath(path, snugClip, &blitter, true);
|
} else {
|
SkScan::FillPath(path, snugClip, &blitter);
|
}
|
|
blitter.finish();
|
return builder.finish(this);
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
typedef void (*RowProc)(SkAAClip::Builder&, int bottom,
|
const uint8_t* rowA, const SkIRect& rectA,
|
const uint8_t* rowB, const SkIRect& rectB);
|
|
typedef U8CPU (*AlphaProc)(U8CPU alphaA, U8CPU alphaB);
|
|
static U8CPU sectAlphaProc(U8CPU alphaA, U8CPU alphaB) {
|
// Multiply
|
return SkMulDiv255Round(alphaA, alphaB);
|
}
|
|
static U8CPU unionAlphaProc(U8CPU alphaA, U8CPU alphaB) {
|
// SrcOver
|
return alphaA + alphaB - SkMulDiv255Round(alphaA, alphaB);
|
}
|
|
static U8CPU diffAlphaProc(U8CPU alphaA, U8CPU alphaB) {
|
// SrcOut
|
return SkMulDiv255Round(alphaA, 0xFF - alphaB);
|
}
|
|
static U8CPU xorAlphaProc(U8CPU alphaA, U8CPU alphaB) {
|
// XOR
|
return alphaA + alphaB - 2 * SkMulDiv255Round(alphaA, alphaB);
|
}
|
|
static AlphaProc find_alpha_proc(SkRegion::Op op) {
|
switch (op) {
|
case SkRegion::kIntersect_Op:
|
return sectAlphaProc;
|
case SkRegion::kDifference_Op:
|
return diffAlphaProc;
|
case SkRegion::kUnion_Op:
|
return unionAlphaProc;
|
case SkRegion::kXOR_Op:
|
return xorAlphaProc;
|
default:
|
SkDEBUGFAIL("unexpected region op");
|
return sectAlphaProc;
|
}
|
}
|
|
class RowIter {
|
public:
|
RowIter(const uint8_t* row, const SkIRect& bounds) {
|
fRow = row;
|
fLeft = bounds.fLeft;
|
fBoundsRight = bounds.fRight;
|
if (row) {
|
fRight = bounds.fLeft + row[0];
|
SkASSERT(fRight <= fBoundsRight);
|
fAlpha = row[1];
|
fDone = false;
|
} else {
|
fDone = true;
|
fRight = kMaxInt32;
|
fAlpha = 0;
|
}
|
}
|
|
bool done() const { return fDone; }
|
int left() const { return fLeft; }
|
int right() const { return fRight; }
|
U8CPU alpha() const { return fAlpha; }
|
void next() {
|
if (!fDone) {
|
fLeft = fRight;
|
if (fRight == fBoundsRight) {
|
fDone = true;
|
fRight = kMaxInt32;
|
fAlpha = 0;
|
} else {
|
fRow += 2;
|
fRight += fRow[0];
|
fAlpha = fRow[1];
|
SkASSERT(fRight <= fBoundsRight);
|
}
|
}
|
}
|
|
private:
|
const uint8_t* fRow;
|
int fLeft;
|
int fRight;
|
int fBoundsRight;
|
bool fDone;
|
uint8_t fAlpha;
|
};
|
|
static void adjust_row(RowIter& iter, int& leftA, int& riteA, int rite) {
|
if (rite == riteA) {
|
iter.next();
|
leftA = iter.left();
|
riteA = iter.right();
|
}
|
}
|
|
#if 0 // UNUSED
|
static bool intersect(int& min, int& max, int boundsMin, int boundsMax) {
|
SkASSERT(min < max);
|
SkASSERT(boundsMin < boundsMax);
|
if (min >= boundsMax || max <= boundsMin) {
|
return false;
|
}
|
if (min < boundsMin) {
|
min = boundsMin;
|
}
|
if (max > boundsMax) {
|
max = boundsMax;
|
}
|
return true;
|
}
|
#endif
|
|
static void operatorX(SkAAClip::Builder& builder, int lastY,
|
RowIter& iterA, RowIter& iterB,
|
AlphaProc proc, const SkIRect& bounds) {
|
int leftA = iterA.left();
|
int riteA = iterA.right();
|
int leftB = iterB.left();
|
int riteB = iterB.right();
|
|
int prevRite = bounds.fLeft;
|
|
do {
|
U8CPU alphaA = 0;
|
U8CPU alphaB = 0;
|
int left, rite;
|
|
if (leftA < leftB) {
|
left = leftA;
|
alphaA = iterA.alpha();
|
if (riteA <= leftB) {
|
rite = riteA;
|
} else {
|
rite = leftA = leftB;
|
}
|
} else if (leftB < leftA) {
|
left = leftB;
|
alphaB = iterB.alpha();
|
if (riteB <= leftA) {
|
rite = riteB;
|
} else {
|
rite = leftB = leftA;
|
}
|
} else {
|
left = leftA; // or leftB, since leftA == leftB
|
rite = leftA = leftB = SkMin32(riteA, riteB);
|
alphaA = iterA.alpha();
|
alphaB = iterB.alpha();
|
}
|
|
if (left >= bounds.fRight) {
|
break;
|
}
|
if (rite > bounds.fRight) {
|
rite = bounds.fRight;
|
}
|
|
if (left >= bounds.fLeft) {
|
SkASSERT(rite > left);
|
builder.addRun(left, lastY, proc(alphaA, alphaB), rite - left);
|
prevRite = rite;
|
}
|
|
adjust_row(iterA, leftA, riteA, rite);
|
adjust_row(iterB, leftB, riteB, rite);
|
} while (!iterA.done() || !iterB.done());
|
|
if (prevRite < bounds.fRight) {
|
builder.addRun(prevRite, lastY, 0, bounds.fRight - prevRite);
|
}
|
}
|
|
static void adjust_iter(SkAAClip::Iter& iter, int& topA, int& botA, int bot) {
|
if (bot == botA) {
|
iter.next();
|
topA = botA;
|
SkASSERT(botA == iter.top());
|
botA = iter.bottom();
|
}
|
}
|
|
static void operateY(SkAAClip::Builder& builder, const SkAAClip& A,
|
const SkAAClip& B, SkRegion::Op op) {
|
AlphaProc proc = find_alpha_proc(op);
|
const SkIRect& bounds = builder.getBounds();
|
|
SkAAClip::Iter iterA(A);
|
SkAAClip::Iter iterB(B);
|
|
SkASSERT(!iterA.done());
|
int topA = iterA.top();
|
int botA = iterA.bottom();
|
SkASSERT(!iterB.done());
|
int topB = iterB.top();
|
int botB = iterB.bottom();
|
|
do {
|
const uint8_t* rowA = nullptr;
|
const uint8_t* rowB = nullptr;
|
int top, bot;
|
|
if (topA < topB) {
|
top = topA;
|
rowA = iterA.data();
|
if (botA <= topB) {
|
bot = botA;
|
} else {
|
bot = topA = topB;
|
}
|
|
} else if (topB < topA) {
|
top = topB;
|
rowB = iterB.data();
|
if (botB <= topA) {
|
bot = botB;
|
} else {
|
bot = topB = topA;
|
}
|
} else {
|
top = topA; // or topB, since topA == topB
|
bot = topA = topB = SkMin32(botA, botB);
|
rowA = iterA.data();
|
rowB = iterB.data();
|
}
|
|
if (top >= bounds.fBottom) {
|
break;
|
}
|
|
if (bot > bounds.fBottom) {
|
bot = bounds.fBottom;
|
}
|
SkASSERT(top < bot);
|
|
if (!rowA && !rowB) {
|
builder.addRun(bounds.fLeft, bot - 1, 0, bounds.width());
|
} else if (top >= bounds.fTop) {
|
SkASSERT(bot <= bounds.fBottom);
|
RowIter rowIterA(rowA, rowA ? A.getBounds() : bounds);
|
RowIter rowIterB(rowB, rowB ? B.getBounds() : bounds);
|
operatorX(builder, bot - 1, rowIterA, rowIterB, proc, bounds);
|
}
|
|
adjust_iter(iterA, topA, botA, bot);
|
adjust_iter(iterB, topB, botB, bot);
|
} while (!iterA.done() || !iterB.done());
|
}
|
|
bool SkAAClip::op(const SkAAClip& clipAOrig, const SkAAClip& clipBOrig,
|
SkRegion::Op op) {
|
AUTO_AACLIP_VALIDATE(*this);
|
|
if (SkRegion::kReplace_Op == op) {
|
return this->set(clipBOrig);
|
}
|
|
const SkAAClip* clipA = &clipAOrig;
|
const SkAAClip* clipB = &clipBOrig;
|
|
if (SkRegion::kReverseDifference_Op == op) {
|
using std::swap;
|
swap(clipA, clipB);
|
op = SkRegion::kDifference_Op;
|
}
|
|
bool a_empty = clipA->isEmpty();
|
bool b_empty = clipB->isEmpty();
|
|
SkIRect bounds;
|
switch (op) {
|
case SkRegion::kDifference_Op:
|
if (a_empty) {
|
return this->setEmpty();
|
}
|
if (b_empty || !SkIRect::Intersects(clipA->fBounds, clipB->fBounds)) {
|
return this->set(*clipA);
|
}
|
bounds = clipA->fBounds;
|
break;
|
|
case SkRegion::kIntersect_Op:
|
if ((a_empty | b_empty) || !bounds.intersect(clipA->fBounds,
|
clipB->fBounds)) {
|
return this->setEmpty();
|
}
|
break;
|
|
case SkRegion::kUnion_Op:
|
case SkRegion::kXOR_Op:
|
if (a_empty) {
|
return this->set(*clipB);
|
}
|
if (b_empty) {
|
return this->set(*clipA);
|
}
|
bounds = clipA->fBounds;
|
bounds.join(clipB->fBounds);
|
break;
|
|
default:
|
SkDEBUGFAIL("unknown region op");
|
return !this->isEmpty();
|
}
|
|
SkASSERT(SkIRect::Intersects(bounds, clipB->fBounds));
|
SkASSERT(SkIRect::Intersects(bounds, clipB->fBounds));
|
|
Builder builder(bounds);
|
operateY(builder, *clipA, *clipB, op);
|
|
return builder.finish(this);
|
}
|
|
/*
|
* 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.
|
*/
|
|
bool SkAAClip::op(const SkIRect& rOrig, SkRegion::Op op) {
|
SkIRect rStorage;
|
const SkIRect* r = &rOrig;
|
|
switch (op) {
|
case SkRegion::kIntersect_Op:
|
if (!rStorage.intersect(rOrig, fBounds)) {
|
// no overlap, so we're empty
|
return this->setEmpty();
|
}
|
if (rStorage == fBounds) {
|
// we were wholly inside the rect, no change
|
return !this->isEmpty();
|
}
|
if (this->quickContains(rStorage)) {
|
// the intersection is wholly inside us, we're a rect
|
return this->setRect(rStorage);
|
}
|
r = &rStorage; // use the intersected bounds
|
break;
|
case SkRegion::kDifference_Op:
|
break;
|
case SkRegion::kUnion_Op:
|
if (rOrig.contains(fBounds)) {
|
return this->setRect(rOrig);
|
}
|
break;
|
default:
|
break;
|
}
|
|
SkAAClip clip;
|
clip.setRect(*r);
|
return this->op(*this, clip, op);
|
}
|
|
bool SkAAClip::op(const SkRect& rOrig, SkRegion::Op op, bool doAA) {
|
SkRect rStorage, boundsStorage;
|
const SkRect* r = &rOrig;
|
|
boundsStorage.set(fBounds);
|
switch (op) {
|
case SkRegion::kIntersect_Op:
|
case SkRegion::kDifference_Op:
|
if (!rStorage.intersect(rOrig, boundsStorage)) {
|
if (SkRegion::kIntersect_Op == op) {
|
return this->setEmpty();
|
} else { // kDifference
|
return !this->isEmpty();
|
}
|
}
|
r = &rStorage; // use the intersected bounds
|
break;
|
case SkRegion::kUnion_Op:
|
if (rOrig.contains(boundsStorage)) {
|
return this->setRect(rOrig);
|
}
|
break;
|
default:
|
break;
|
}
|
|
SkAAClip clip;
|
clip.setRect(*r, doAA);
|
return this->op(*this, clip, op);
|
}
|
|
bool SkAAClip::op(const SkAAClip& clip, SkRegion::Op op) {
|
return this->op(*this, clip, op);
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
bool SkAAClip::translate(int dx, int dy, SkAAClip* dst) const {
|
if (nullptr == dst) {
|
return !this->isEmpty();
|
}
|
|
if (this->isEmpty()) {
|
return dst->setEmpty();
|
}
|
|
if (this != dst) {
|
fRunHead->fRefCnt++;
|
dst->freeRuns();
|
dst->fRunHead = fRunHead;
|
dst->fBounds = fBounds;
|
}
|
dst->fBounds.offset(dx, dy);
|
return true;
|
}
|
|
static void expand_row_to_mask(uint8_t* SK_RESTRICT mask,
|
const uint8_t* SK_RESTRICT row,
|
int width) {
|
while (width > 0) {
|
int n = row[0];
|
SkASSERT(width >= n);
|
memset(mask, row[1], n);
|
mask += n;
|
row += 2;
|
width -= n;
|
}
|
SkASSERT(0 == width);
|
}
|
|
void SkAAClip::copyToMask(SkMask* mask) const {
|
mask->fFormat = SkMask::kA8_Format;
|
if (this->isEmpty()) {
|
mask->fBounds.setEmpty();
|
mask->fImage = nullptr;
|
mask->fRowBytes = 0;
|
return;
|
}
|
|
mask->fBounds = fBounds;
|
mask->fRowBytes = fBounds.width();
|
size_t size = mask->computeImageSize();
|
mask->fImage = SkMask::AllocImage(size);
|
|
Iter iter(*this);
|
uint8_t* dst = mask->fImage;
|
const int width = fBounds.width();
|
|
int y = fBounds.fTop;
|
while (!iter.done()) {
|
do {
|
expand_row_to_mask(dst, iter.data(), width);
|
dst += mask->fRowBytes;
|
} while (++y < iter.bottom());
|
iter.next();
|
}
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
|
static void 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
|
}
|
|
SkAAClipBlitter::~SkAAClipBlitter() {
|
sk_free(fScanlineScratch);
|
}
|
|
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));
|
|
const uint8_t* row = fAAClip->findRow(y);
|
int initialCount;
|
row = fAAClip->findX(row, x, &initialCount);
|
|
if (initialCount >= width) {
|
SkAlpha alpha = row[1];
|
if (0 == alpha) {
|
return;
|
}
|
if (0xFF == alpha) {
|
fBlitter->blitH(x, y, width);
|
return;
|
}
|
}
|
|
this->ensureRunsAndAA();
|
expandToRuns(row, initialCount, width, fRuns, fAA);
|
|
fBlitter->blitAntiH(x, y, fAA, fRuns);
|
}
|
|
static void merge(const uint8_t* SK_RESTRICT row, int rowN,
|
const SkAlpha* SK_RESTRICT srcAA,
|
const int16_t* SK_RESTRICT srcRuns,
|
SkAlpha* SK_RESTRICT dstAA,
|
int16_t* SK_RESTRICT dstRuns,
|
int width) {
|
SkDEBUGCODE(int accumulated = 0;)
|
int srcN = srcRuns[0];
|
// do we need this check?
|
if (0 == srcN) {
|
return;
|
}
|
|
for (;;) {
|
SkASSERT(rowN > 0);
|
SkASSERT(srcN > 0);
|
|
unsigned newAlpha = SkMulDiv255Round(srcAA[0], row[1]);
|
int minN = SkMin32(srcN, rowN);
|
dstRuns[0] = minN;
|
dstRuns += minN;
|
dstAA[0] = newAlpha;
|
dstAA += minN;
|
|
if (0 == (srcN -= minN)) {
|
srcN = srcRuns[0]; // refresh
|
srcRuns += srcN;
|
srcAA += srcN;
|
srcN = srcRuns[0]; // reload
|
if (0 == srcN) {
|
break;
|
}
|
}
|
if (0 == (rowN -= minN)) {
|
row += 2;
|
rowN = row[0]; // reload
|
}
|
|
SkDEBUGCODE(accumulated += minN;)
|
SkASSERT(accumulated <= width);
|
}
|
dstRuns[0] = 0;
|
}
|
|
void SkAAClipBlitter::blitAntiH(int x, int y, const SkAlpha aa[],
|
const int16_t runs[]) {
|
|
const uint8_t* row = fAAClip->findRow(y);
|
int initialCount;
|
row = fAAClip->findX(row, x, &initialCount);
|
|
this->ensureRunsAndAA();
|
|
merge(row, initialCount, aa, runs, fAA, fRuns, fAAClipBounds.width());
|
fBlitter->blitAntiH(x, y, fAA, fRuns);
|
}
|
|
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;
|
}
|
}
|
|
typedef void (*MergeAAProc)(const void* src, int width, const uint8_t* row,
|
int initialRowCount, void* dst);
|
|
static void small_memcpy(void* dst, const void* src, size_t n) {
|
memcpy(dst, src, n);
|
}
|
|
static void small_bzero(void* dst, size_t n) {
|
sk_bzero(dst, n);
|
}
|
|
static inline uint8_t mergeOne(uint8_t value, unsigned alpha) {
|
return SkMulDiv255Round(value, alpha);
|
}
|
|
static inline uint16_t mergeOne(uint16_t value, unsigned alpha) {
|
unsigned r = SkGetPackedR16(value);
|
unsigned g = SkGetPackedG16(value);
|
unsigned b = SkGetPackedB16(value);
|
return SkPackRGB16(SkMulDiv255Round(r, alpha),
|
SkMulDiv255Round(g, alpha),
|
SkMulDiv255Round(b, alpha));
|
}
|
|
template <typename T>
|
void mergeT(const void* inSrc, int srcN, const uint8_t* SK_RESTRICT row, int rowN, void* inDst) {
|
const T* SK_RESTRICT src = static_cast<const T*>(inSrc);
|
T* SK_RESTRICT dst = static_cast<T*>(inDst);
|
for (;;) {
|
SkASSERT(rowN > 0);
|
SkASSERT(srcN > 0);
|
|
int n = SkMin32(rowN, srcN);
|
unsigned rowA = row[1];
|
if (0xFF == rowA) {
|
small_memcpy(dst, src, n * sizeof(T));
|
} else if (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;
|
}
|
|
static void upscaleBW2A8(SkMask* dstMask, const SkMask& srcMask) {
|
SkASSERT(SkMask::kBW_Format == srcMask.fFormat);
|
SkASSERT(SkMask::kA8_Format == dstMask->fFormat);
|
|
const int width = srcMask.fBounds.width();
|
const int height = srcMask.fBounds.height();
|
|
const uint8_t* SK_RESTRICT src = (const uint8_t*)srcMask.fImage;
|
const size_t srcRB = srcMask.fRowBytes;
|
uint8_t* SK_RESTRICT dst = (uint8_t*)dstMask->fImage;
|
const size_t dstRB = dstMask->fRowBytes;
|
|
const int wholeBytes = width >> 3;
|
const int leftOverBits = width & 7;
|
|
for (int y = 0; y < height; ++y) {
|
uint8_t* SK_RESTRICT d = dst;
|
for (int i = 0; i < wholeBytes; ++i) {
|
int srcByte = src[i];
|
d[0] = bit2byte(srcByte & (1 << 7));
|
d[1] = bit2byte(srcByte & (1 << 6));
|
d[2] = bit2byte(srcByte & (1 << 5));
|
d[3] = bit2byte(srcByte & (1 << 4));
|
d[4] = bit2byte(srcByte & (1 << 3));
|
d[5] = bit2byte(srcByte & (1 << 2));
|
d[6] = bit2byte(srcByte & (1 << 1));
|
d[7] = bit2byte(srcByte & (1 << 0));
|
d += 8;
|
}
|
if (leftOverBits) {
|
int srcByte = src[wholeBytes];
|
for (int x = 0; x < leftOverBits; ++x) {
|
*d++ = bit2byte(srcByte & 0x80);
|
srcByte <<= 1;
|
}
|
}
|
src += srcRB;
|
dst += dstRB;
|
}
|
}
|
|
void SkAAClipBlitter::blitMask(const SkMask& origMask, const SkIRect& clip) {
|
SkASSERT(fAAClip->getBounds().contains(clip));
|
|
if (fAAClip->quickContains(clip)) {
|
fBlitter->blitMask(origMask, clip);
|
return;
|
}
|
|
const SkMask* mask = &origMask;
|
|
// if we're BW, we need to upscale to A8 (ugh)
|
SkMask grayMask;
|
if (SkMask::kBW_Format == origMask.fFormat) {
|
grayMask.fFormat = SkMask::kA8_Format;
|
grayMask.fBounds = origMask.fBounds;
|
grayMask.fRowBytes = origMask.fBounds.width();
|
size_t size = grayMask.computeImageSize();
|
grayMask.fImage = (uint8_t*)fGrayMaskScratch.reset(size,
|
SkAutoMalloc::kReuse_OnShrink);
|
|
upscaleBW2A8(&grayMask, origMask);
|
mask = &grayMask;
|
}
|
|
this->ensureRunsAndAA();
|
|
// HACK -- we are devolving 3D into A8, need to copy the rest of the 3D
|
// data into a temp block to support it better (ugh)
|
|
const void* src = mask->getAddr(clip.fLeft, clip.fTop);
|
const size_t srcRB = mask->fRowBytes;
|
const int width = clip.width();
|
MergeAAProc mergeProc = find_merge_aa_proc(mask->fFormat);
|
|
SkMask rowMask;
|
rowMask.fFormat = SkMask::k3D_Format == mask->fFormat ? SkMask::kA8_Format : mask->fFormat;
|
rowMask.fBounds.fLeft = clip.fLeft;
|
rowMask.fBounds.fRight = clip.fRight;
|
rowMask.fRowBytes = mask->fRowBytes; // doesn't matter, since our height==1
|
rowMask.fImage = (uint8_t*)fScanlineScratch;
|
|
int y = clip.fTop;
|
const int 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 = SkMin32(localStopY + 1, stopY);
|
|
int initialCount;
|
row = fAAClip->findX(row, clip.fLeft, &initialCount);
|
do {
|
mergeProc(src, width, row, initialCount, rowMask.fImage);
|
rowMask.fBounds.fTop = y;
|
rowMask.fBounds.fBottom = y + 1;
|
fBlitter->blitMask(rowMask, rowMask.fBounds);
|
src = (const void*)((const char*)src + srcRB);
|
} while (++y < localStopY);
|
} while (y < stopY);
|
}
|
|
const SkPixmap* SkAAClipBlitter::justAnOpaqueColor(uint32_t* value) {
|
return nullptr;
|
}
|