aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar reed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2011-10-04 21:38:55 +0000
committerGravatar reed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2011-10-04 21:38:55 +0000
commite36707a4a82a4dea7d480d969220f3ed223305dc (patch)
treeb58c4355637fd6f352ecbf245b1a55c0e71f1d17
parent02f55841854ae32f21a13417e9ee711463e488cf (diff)
first cut at aaclipping
git-svn-id: http://skia.googlecode.com/svn/trunk@2410 2bbb7eff-a529-9590-31e7-b0007b416f81
-rw-r--r--src/core/SkAAClip.cpp676
-rw-r--r--src/core/SkAAClip.h99
2 files changed, 775 insertions, 0 deletions
diff --git a/src/core/SkAAClip.cpp b/src/core/SkAAClip.cpp
new file mode 100644
index 0000000000..c7cfca5c34
--- /dev/null
+++ b/src/core/SkAAClip.cpp
@@ -0,0 +1,676 @@
+
+/*
+ * 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 "SkPath.h"
+#include "SkScan.h"
+#include "SkThread.h"
+
+static inline bool x_in_rect(int x, const SkIRect& rect) {
+ return (unsigned)(x - rect.fLeft) < (unsigned)rect.width();
+}
+
+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 {
+ int32_t fRefCnt;
+ int32_t fRowCount;
+ int32_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 = 1;
+ head->fRowCount = rowCount;
+ head->fDataSize = dataSize;
+ return head;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkAAClip::freeRuns() {
+ if (this->isComplex()) {
+ SkASSERT(fRunHead->fRefCnt >= 1);
+ if (1 == sk_atomic_dec(&fRunHead->fRefCnt)) {
+ sk_free(fRunHead);
+ }
+ }
+}
+
+SkAAClip::SkAAClip() {
+ fBounds.setEmpty();
+ fRunHead = SkAAClip_gEmptyPtr;
+}
+
+SkAAClip::SkAAClip(const SkAAClip& src) {
+ fRunHead = SkAAClip_gEmptyPtr;
+ *this = src;
+}
+
+SkAAClip::~SkAAClip() {
+ this->freeRuns();
+}
+
+SkAAClip& SkAAClip::operator=(const SkAAClip& src) {
+ if (this != &src) {
+ this->freeRuns();
+ fBounds = src.fBounds;
+ fRunHead = src.fRunHead;
+ if (this->isComplex()) {
+ sk_atomic_inc(&fRunHead->fRefCnt);
+ }
+ }
+ return *this;
+}
+
+bool operator==(const SkAAClip& a, const SkAAClip& b) {
+ 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.isComplex() || !b.isComplex()) {
+ return false;
+ }
+
+ return ah->fRowCount == bh->fRowCount &&
+ ah->fDataSize == bh->fDataSize &&
+ !memcmp(ah->data(), bh->data(), ah->fDataSize);
+}
+
+void SkAAClip::swap(SkAAClip& other) {
+ SkTSwap(fBounds, other.fBounds);
+ SkTSwap(fRunHead, other.fRunHead);
+}
+
+bool SkAAClip::setEmpty() {
+ this->freeRuns();
+ fBounds.setEmpty();
+ fRunHead = SkAAClip_gEmptyPtr;
+ return false;
+}
+
+bool SkAAClip::setRect(const SkIRect& bounds) {
+ if (bounds.isEmpty()) {
+ return this->setEmpty();
+ }
+ this->freeRuns();
+ fBounds = bounds;
+ fRunHead = SkAAClip_gRectPtr;
+ return true;
+}
+
+bool SkAAClip::setRect(const SkRect& r) {
+ if (r.isEmpty()) {
+ return this->setEmpty();
+ }
+
+ SkIRect ibounds;
+ r.roundOut(&ibounds);
+
+ SkRegion clip;
+ clip.setRect(ibounds);
+
+ SkPath path;
+ path.addRect(r);
+ return this->setPath(path, clip);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const uint8_t* SkAAClip::findRow(int y, int* lastYForRow) const {
+ SkASSERT(this->isComplex());
+
+ if (!y_in_rect(y, fBounds)) {
+ return NULL;
+ }
+ 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 = 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) {
+ *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 (this->isRect()) {
+ return true;
+ }
+
+ int lastY;
+ const uint8_t* row = this->findRow(top, &lastY);
+ if (lastY < bottom) {
+ return false;
+ }
+ // now just need to check in X
+ int initialCount;
+ row = this->findX(row, left, &initialCount);
+ return initialCount >= (right - left) && 0xFF == row[1];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkAAClip::Builder {
+ SkIRect fBounds;
+ struct Row {
+ int fY;
+ int fWidth;
+ SkTDArray<uint8_t>* fData;
+ };
+ SkTDArray<Row> fRows;
+ Row* fCurrRow;
+ int fPrevY;
+ int fWidth;
+
+public:
+ Builder(const SkIRect& bounds) : fBounds(bounds) {
+ fPrevY = -1;
+ fWidth = bounds.width();
+ fCurrRow = NULL;
+ }
+
+ ~Builder() {
+ Row* row = fRows.begin();
+ Row* stop = fRows.end();
+ while (row < stop) {
+ delete row->fData;
+ row += 1;
+ }
+ }
+
+ 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());
+ }
+
+ RunHead* finish() {
+ 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;
+ }
+
+ RunHead* head = RunHead::Alloc(fRows.count(), dataSize);
+ YOffset* yoffset = head->yoffsets();
+ uint8_t* data = head->data();
+ uint8_t* baseData = data;
+
+ row = fRows.begin();
+ while (row < stop) {
+ yoffset->fY = row->fY;
+ yoffset->fOffset = data - baseData;
+ yoffset += 1;
+
+ size_t n = row->fData->count();
+ memcpy(data, row->fData->begin(), n);
+ data += n;
+
+ row += 1;
+ }
+
+ return head;
+ }
+
+ 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");
+ }
+
+#if 0
+ int prevY = -1;
+ for (y = 0; y < fRows.count(); ++y) {
+ const Row& row = fRows[y];
+ const SkTDArray<uint8_t>& data = *row.fData;
+ int count = data.count();
+ for (int n = prevY; n < row.fY; ++n) {
+ const uint8_t* ptr = data.begin();
+ for (int x = 0; x < count; x += 2) {
+ for (int i = 0; i < ptr[0]; ++i) {
+ SkDebugf("%02X", ptr[1]);
+ }
+ ptr += 2;
+ }
+ SkDebugf("\n");
+ }
+ prevY = row.fY;
+ }
+#endif
+ }
+
+ 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) {
+ w += ptr[0];
+ SkASSERT(w <= fWidth);
+ ptr += 2;
+ }
+ SkASSERT(w == fWidth);
+ prevY = row.fY;
+ }
+#endif
+ }
+
+private:
+ Row* flushRow(bool readyForAnother) {
+ Row* next = NULL;
+ int count = fRows.count();
+ if (count > 0) {
+ // flush current row if needed
+ Row* curr = &fRows[count - 1];
+ if (curr->fWidth < fWidth) {
+ AppendRun(*curr->fData, 0, fWidth - curr->fWidth);
+ }
+ }
+ 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 {
+public:
+ BuilderBlitter(Builder* builder) {
+ fBuilder = builder;
+ }
+
+ virtual void blitV(int x, int y, int height, SkAlpha alpha) SK_OVERRIDE
+ { unexpected(); }
+ virtual void blitRect(int x, int y, int width, int height) SK_OVERRIDE
+ { unexpected(); }
+ virtual void blitMask(const SkMask&, const SkIRect& clip) SK_OVERRIDE
+ { unexpected(); }
+
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t*) SK_OVERRIDE {
+ return false;
+ }
+
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE {
+ fBuilder->addRun(x, y, 0xFF, width);
+ }
+
+ virtual void blitAntiH(int x, int y, const SkAlpha alpha[],
+ const int16_t runs[]) SK_OVERRIDE {
+ for (;;) {
+ int count = *runs;
+ if (count <= 0) {
+ return;
+ }
+ fBuilder->addRun(x, y, *alpha, count);
+ runs += count;
+ alpha += count;
+ x += count;
+ }
+ }
+
+private:
+ Builder* fBuilder;
+
+ void unexpected() {
+ SkDebugf("---- did not expect to get called here");
+ sk_throw();
+ }
+};
+
+bool SkAAClip::setPath(const SkPath& path, const SkRegion& clip) {
+ if (clip.isEmpty()) {
+ return this->setEmpty();
+ }
+
+ SkIRect ibounds;
+
+ if (!path.isInverseFillType()) {
+ path.getBounds().roundOut(&ibounds);
+ if (ibounds.isEmpty() || !ibounds.intersect(clip.getBounds())) {
+ return this->setEmpty();
+ }
+ } else {
+ ibounds = clip.getBounds();
+ }
+
+ Builder builder(ibounds);
+ BuilderBlitter blitter(&builder);
+
+ SkScan::AntiFillPath(path, clip, &blitter, true);
+
+ this->freeRuns();
+ fBounds = ibounds;
+ fRunHead = builder.finish();
+
+ builder.dump();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkAAClip::op(const SkAAClip&, const SkAAClip&, SkRegion::Op op) {
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+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(fRuns);
+}
+
+void SkAAClipBlitter::ensureRunsAndAA() {
+ if (NULL == fRuns) {
+ // add 1 so we can store the terminating run count of 0
+ int count = fAAClipBounds.width() + 1;
+ fRuns = (int16_t*)sk_malloc_throw(count * sizeof(int16_t) +
+ count * sizeof(SkAlpha));
+ 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));
+
+ int lastY;
+ const uint8_t* row = fAAClip->findRow(y, &lastY);
+ 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];
+ for (;;) {
+ if (0 == srcN) {
+ break;
+ }
+ 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 == (rowN -= minN)) {
+ row += 2;
+ rowN = row[0]; // reload
+ }
+
+ SkDEBUGCODE(accumulated += minN;)
+ SkASSERT(accumulated <= width);
+ }
+}
+
+void SkAAClipBlitter::blitAntiH(int x, int y, const SkAlpha aa[],
+ const int16_t runs[]) {
+ int lastY;
+ const uint8_t* row = fAAClip->findRow(y, &lastY);
+ 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;
+ }
+
+ int stopY = y + height;
+ do {
+ int lastY;
+ const uint8_t* row = fAAClip->findRow(y, &lastY);
+ int initialCount;
+ row = fAAClip->findX(row, x, &initialCount);
+ SkAlpha newAlpha = SkMulDiv255Round(alpha, row[1]);
+ if (newAlpha) {
+ fBlitter->blitV(x, y, lastY - y + 1, newAlpha);
+ }
+ y = lastY + 1;
+ } while (y < stopY);
+}
+
+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;
+ }
+}
+
+void SkAAClipBlitter::blitMask(const SkMask& mask, const SkIRect& clip) {
+ fBlitter->blitMask(mask, clip);
+}
+
+const SkBitmap* SkAAClipBlitter::justAnOpaqueColor(uint32_t* value) {
+ return NULL;
+}
+
diff --git a/src/core/SkAAClip.h b/src/core/SkAAClip.h
new file mode 100644
index 0000000000..c62292f79e
--- /dev/null
+++ b/src/core/SkAAClip.h
@@ -0,0 +1,99 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkAAClip_DEFINED
+#define SkAAClip_DEFINED
+
+#include "SkBlitter.h"
+#include "SkRegion.h"
+
+#define SkAAClip_gEmptyPtr ((SkAAClip::RunHead*)-1)
+#define SkAAClip_gRectPtr NULL
+
+class SkAAClip {
+public:
+ SkAAClip();
+ SkAAClip(const SkAAClip&);
+ ~SkAAClip();
+
+ SkAAClip& operator=(const SkAAClip&);
+ friend bool operator==(const SkAAClip&, const SkAAClip&);
+ friend bool operator!=(const SkAAClip& a, const SkAAClip& b) {
+ return !(a == b);
+ }
+
+ void swap(SkAAClip&);
+
+ bool isEmpty() const { return SkAAClip_gEmptyPtr == fRunHead; }
+ bool isRect() const { return SkAAClip_gRectPtr == fRunHead; }
+ bool isComplex() const { return !this->isEmpty() && !this->isRect(); }
+ const SkIRect& getBounds() const { return fBounds; }
+
+ bool setEmpty();
+ bool setRect(const SkIRect&);
+ bool setRect(const SkRect&);
+ bool setPath(const SkPath&, const SkRegion& clip);
+
+ bool op(const SkAAClip&, const SkAAClip&, SkRegion::Op);
+
+ // called internally
+
+ bool quickContains(int left, int top, int right, int bottom) const;
+ const uint8_t* findRow(int y, int* lastYForRow) const;
+ const uint8_t* findX(const uint8_t data[], int x, int* initialCount) const;
+
+private:
+ struct RunHead;
+ struct YOffset;
+
+ SkIRect fBounds;
+ RunHead* fRunHead;
+
+ void freeRuns();
+
+ class Builder;
+ friend class Builder;
+ class BuilderBlitter;
+ friend class BuilderBlitter;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkAAClipBlitter : public SkBlitter {
+public:
+ SkAAClipBlitter() : fRuns(NULL) {}
+ virtual ~SkAAClipBlitter();
+
+ void init(SkBlitter* blitter, const SkAAClip* aaclip) {
+ SkASSERT(aaclip && !aaclip->isEmpty());
+ fBlitter = blitter;
+ fAAClip = aaclip;
+ fAAClipBounds = aaclip->getBounds();
+ }
+
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+ virtual void blitAntiH(int x, int y, const SkAlpha[],
+ const int16_t runs[]) SK_OVERRIDE;
+ virtual void blitV(int x, int y, int height, SkAlpha alpha) SK_OVERRIDE;
+ virtual void blitRect(int x, int y, int width, int height) SK_OVERRIDE;
+ virtual void blitMask(const SkMask&, const SkIRect& clip) SK_OVERRIDE;
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t* value) SK_OVERRIDE;
+
+private:
+ SkBlitter* fBlitter;
+ const SkAAClip* fAAClip;
+ SkIRect fAAClipBounds;
+
+ // lazily allocated
+ int16_t* fRuns;
+ SkAlpha* fAA; // points into fRuns allocation
+
+ void ensureRunsAndAA();
+};
+
+#endif