| /* | |
| * Copyright 2006 The Android Open Source Project | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "SkRegionPriv.h" | |
| #include "SkBlitter.h" | |
| #include "SkScan.h" | |
| #include "SkTDArray.h" | |
| #include "SkPath.h" | |
| // The rgnbuilder caller *seems* to pass short counts, possible often seens early failure, so | |
| // we may not want to promote this to a "std" routine just yet. | |
| static bool sk_memeq32(const int32_t* SK_RESTRICT a, const int32_t* SK_RESTRICT b, int count) { | |
| for (int i = 0; i < count; ++i) { | |
| if (a[i] != b[i]) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| class SkRgnBuilder : public SkBlitter { | |
| public: | |
| SkRgnBuilder(); | |
| virtual ~SkRgnBuilder(); | |
| // returns true if it could allocate the working storage needed | |
| bool init(int maxHeight, int maxTransitions, bool pathIsInverse); | |
| void done() { | |
| if (fCurrScanline != NULL) { | |
| fCurrScanline->fXCount = (SkRegion::RunType)((int)(fCurrXPtr - fCurrScanline->firstX())); | |
| if (!this->collapsWithPrev()) { // flush the last line | |
| fCurrScanline = fCurrScanline->nextScanline(); | |
| } | |
| } | |
| } | |
| int computeRunCount() const; | |
| void copyToRect(SkIRect*) const; | |
| void copyToRgn(SkRegion::RunType runs[]) const; | |
| virtual void blitH(int x, int y, int width); | |
| #ifdef SK_DEBUG | |
| void dump() const { | |
| SkDebugf("SkRgnBuilder: Top = %d\n", fTop); | |
| const Scanline* line = (Scanline*)fStorage; | |
| while (line < fCurrScanline) { | |
| SkDebugf("SkRgnBuilder::Scanline: LastY=%d, fXCount=%d", line->fLastY, line->fXCount); | |
| for (int i = 0; i < line->fXCount; i++) { | |
| SkDebugf(" %d", line->firstX()[i]); | |
| } | |
| SkDebugf("\n"); | |
| line = line->nextScanline(); | |
| } | |
| } | |
| #endif | |
| private: | |
| /* | |
| * Scanline mimics a row in the region, nearly. A row in a region is: | |
| * [Bottom IntervalCount [L R]... Sentinel] | |
| * while a Scanline is | |
| * [LastY XCount [L R]... uninitialized] | |
| * The two are the same length (which is good), but we have to transmute | |
| * the scanline a little when we convert it to a region-row. | |
| * | |
| * Potentially we could recode this to exactly match the row format, in | |
| * which case copyToRgn() could be a single memcpy. Not sure that is worth | |
| * the effort. | |
| */ | |
| struct Scanline { | |
| SkRegion::RunType fLastY; | |
| SkRegion::RunType fXCount; | |
| SkRegion::RunType* firstX() const { return (SkRegion::RunType*)(this + 1); } | |
| Scanline* nextScanline() const { | |
| // add final +1 for the x-sentinel | |
| return (Scanline*)((SkRegion::RunType*)(this + 1) + fXCount + 1); | |
| } | |
| }; | |
| SkRegion::RunType* fStorage; | |
| Scanline* fCurrScanline; | |
| Scanline* fPrevScanline; | |
| // points at next avialable x[] in fCurrScanline | |
| SkRegion::RunType* fCurrXPtr; | |
| SkRegion::RunType fTop; // first Y value | |
| int fStorageCount; | |
| bool collapsWithPrev() { | |
| if (fPrevScanline != NULL && | |
| fPrevScanline->fLastY + 1 == fCurrScanline->fLastY && | |
| fPrevScanline->fXCount == fCurrScanline->fXCount && | |
| sk_memeq32(fPrevScanline->firstX(), fCurrScanline->firstX(), fCurrScanline->fXCount)) | |
| { | |
| // update the height of fPrevScanline | |
| fPrevScanline->fLastY = fCurrScanline->fLastY; | |
| return true; | |
| } | |
| return false; | |
| } | |
| }; | |
| SkRgnBuilder::SkRgnBuilder() | |
| : fStorage(NULL) { | |
| } | |
| SkRgnBuilder::~SkRgnBuilder() { | |
| sk_free(fStorage); | |
| } | |
| bool SkRgnBuilder::init(int maxHeight, int maxTransitions, bool pathIsInverse) { | |
| if ((maxHeight | maxTransitions) < 0) { | |
| return false; | |
| } | |
| if (pathIsInverse) { | |
| // allow for additional X transitions to "invert" each scanline | |
| // [ L' ... normal transitions ... R' ] | |
| // | |
| maxTransitions += 2; | |
| } | |
| // compute the count with +1 and +3 slop for the working buffer | |
| int64_t count = sk_64_mul(maxHeight + 1, 3 + maxTransitions); | |
| if (pathIsInverse) { | |
| // allow for two "empty" rows for the top and bottom | |
| // [ Y, 1, L, R, S] == 5 (*2 for top and bottom) | |
| count += 10; | |
| } | |
| if (count < 0 || !sk_64_isS32(count)) { | |
| return false; | |
| } | |
| fStorageCount = sk_64_asS32(count); | |
| int64_t size = sk_64_mul(fStorageCount, sizeof(SkRegion::RunType)); | |
| if (size < 0 || !sk_64_isS32(size)) { | |
| return false; | |
| } | |
| fStorage = (SkRegion::RunType*)sk_malloc_flags(sk_64_asS32(size), 0); | |
| if (NULL == fStorage) { | |
| return false; | |
| } | |
| fCurrScanline = NULL; // signal empty collection | |
| fPrevScanline = NULL; // signal first scanline | |
| return true; | |
| } | |
| void SkRgnBuilder::blitH(int x, int y, int width) { | |
| if (fCurrScanline == NULL) { // first time | |
| fTop = (SkRegion::RunType)(y); | |
| fCurrScanline = (Scanline*)fStorage; | |
| fCurrScanline->fLastY = (SkRegion::RunType)(y); | |
| fCurrXPtr = fCurrScanline->firstX(); | |
| } else { | |
| SkASSERT(y >= fCurrScanline->fLastY); | |
| if (y > fCurrScanline->fLastY) { | |
| // if we get here, we're done with fCurrScanline | |
| fCurrScanline->fXCount = (SkRegion::RunType)((int)(fCurrXPtr - fCurrScanline->firstX())); | |
| int prevLastY = fCurrScanline->fLastY; | |
| if (!this->collapsWithPrev()) { | |
| fPrevScanline = fCurrScanline; | |
| fCurrScanline = fCurrScanline->nextScanline(); | |
| } | |
| if (y - 1 > prevLastY) { // insert empty run | |
| fCurrScanline->fLastY = (SkRegion::RunType)(y - 1); | |
| fCurrScanline->fXCount = 0; | |
| fCurrScanline = fCurrScanline->nextScanline(); | |
| } | |
| // setup for the new curr line | |
| fCurrScanline->fLastY = (SkRegion::RunType)(y); | |
| fCurrXPtr = fCurrScanline->firstX(); | |
| } | |
| } | |
| // check if we should extend the current run, or add a new one | |
| if (fCurrXPtr > fCurrScanline->firstX() && fCurrXPtr[-1] == x) { | |
| fCurrXPtr[-1] = (SkRegion::RunType)(x + width); | |
| } else { | |
| fCurrXPtr[0] = (SkRegion::RunType)(x); | |
| fCurrXPtr[1] = (SkRegion::RunType)(x + width); | |
| fCurrXPtr += 2; | |
| } | |
| SkASSERT(fCurrXPtr - fStorage < fStorageCount); | |
| } | |
| int SkRgnBuilder::computeRunCount() const { | |
| if (fCurrScanline == NULL) { | |
| return 0; | |
| } | |
| const SkRegion::RunType* line = fStorage; | |
| const SkRegion::RunType* stop = (const SkRegion::RunType*)fCurrScanline; | |
| return 2 + (int)(stop - line); | |
| } | |
| void SkRgnBuilder::copyToRect(SkIRect* r) const { | |
| SkASSERT(fCurrScanline != NULL); | |
| // A rect's scanline is [bottom intervals left right sentinel] == 5 | |
| SkASSERT((const SkRegion::RunType*)fCurrScanline - fStorage == 5); | |
| const Scanline* line = (const Scanline*)fStorage; | |
| SkASSERT(line->fXCount == 2); | |
| r->set(line->firstX()[0], fTop, line->firstX()[1], line->fLastY + 1); | |
| } | |
| void SkRgnBuilder::copyToRgn(SkRegion::RunType runs[]) const { | |
| SkASSERT(fCurrScanline != NULL); | |
| SkASSERT((const SkRegion::RunType*)fCurrScanline - fStorage > 4); | |
| const Scanline* line = (const Scanline*)fStorage; | |
| const Scanline* stop = fCurrScanline; | |
| *runs++ = fTop; | |
| do { | |
| *runs++ = (SkRegion::RunType)(line->fLastY + 1); | |
| int count = line->fXCount; | |
| *runs++ = count >> 1; // intervalCount | |
| if (count) { | |
| memcpy(runs, line->firstX(), count * sizeof(SkRegion::RunType)); | |
| runs += count; | |
| } | |
| *runs++ = SkRegion::kRunTypeSentinel; | |
| line = line->nextScanline(); | |
| } while (line < stop); | |
| SkASSERT(line == stop); | |
| *runs = SkRegion::kRunTypeSentinel; | |
| } | |
| static unsigned verb_to_initial_last_index(unsigned verb) { | |
| static const uint8_t gPathVerbToInitialLastIndex[] = { | |
| 0, // kMove_Verb | |
| 1, // kLine_Verb | |
| 2, // kQuad_Verb | |
| 2, // kConic_Verb | |
| 3, // kCubic_Verb | |
| 0, // kClose_Verb | |
| 0 // kDone_Verb | |
| }; | |
| SkASSERT((unsigned)verb < SK_ARRAY_COUNT(gPathVerbToInitialLastIndex)); | |
| return gPathVerbToInitialLastIndex[verb]; | |
| } | |
| static unsigned verb_to_max_edges(unsigned verb) { | |
| static const uint8_t gPathVerbToMaxEdges[] = { | |
| 0, // kMove_Verb | |
| 1, // kLine_Verb | |
| 2, // kQuad_VerbB | |
| 2, // kConic_VerbB | |
| 3, // kCubic_Verb | |
| 0, // kClose_Verb | |
| 0 // kDone_Verb | |
| }; | |
| SkASSERT((unsigned)verb < SK_ARRAY_COUNT(gPathVerbToMaxEdges)); | |
| return gPathVerbToMaxEdges[verb]; | |
| } | |
| static int count_path_runtype_values(const SkPath& path, int* itop, int* ibot) { | |
| SkPath::Iter iter(path, true); | |
| SkPoint pts[4]; | |
| SkPath::Verb verb; | |
| int maxEdges = 0; | |
| SkScalar top = SkIntToScalar(SK_MaxS16); | |
| SkScalar bot = SkIntToScalar(SK_MinS16); | |
| while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) { | |
| maxEdges += verb_to_max_edges(verb); | |
| int lastIndex = verb_to_initial_last_index(verb); | |
| if (lastIndex > 0) { | |
| for (int i = 1; i <= lastIndex; i++) { | |
| if (top > pts[i].fY) { | |
| top = pts[i].fY; | |
| } else if (bot < pts[i].fY) { | |
| bot = pts[i].fY; | |
| } | |
| } | |
| } else if (SkPath::kMove_Verb == verb) { | |
| if (top > pts[0].fY) { | |
| top = pts[0].fY; | |
| } else if (bot < pts[0].fY) { | |
| bot = pts[0].fY; | |
| } | |
| } | |
| } | |
| SkASSERT(top <= bot); | |
| *itop = SkScalarRoundToInt(top); | |
| *ibot = SkScalarRoundToInt(bot); | |
| return maxEdges; | |
| } | |
| bool SkRegion::setPath(const SkPath& path, const SkRegion& clip) { | |
| SkDEBUGCODE(this->validate();) | |
| if (clip.isEmpty()) { | |
| return this->setEmpty(); | |
| } | |
| if (path.isEmpty()) { | |
| if (path.isInverseFillType()) { | |
| return this->set(clip); | |
| } else { | |
| return this->setEmpty(); | |
| } | |
| } | |
| // compute worst-case rgn-size for the path | |
| int pathTop, pathBot; | |
| int pathTransitions = count_path_runtype_values(path, &pathTop, &pathBot); | |
| int clipTop, clipBot; | |
| int clipTransitions; | |
| clipTransitions = clip.count_runtype_values(&clipTop, &clipBot); | |
| int top = SkMax32(pathTop, clipTop); | |
| int bot = SkMin32(pathBot, clipBot); | |
| if (top >= bot) | |
| return this->setEmpty(); | |
| SkRgnBuilder builder; | |
| if (!builder.init(bot - top, | |
| SkMax32(pathTransitions, clipTransitions), | |
| path.isInverseFillType())) { | |
| // can't allocate working space, so return false | |
| return this->setEmpty(); | |
| } | |
| SkScan::FillPath(path, clip, &builder); | |
| builder.done(); | |
| int count = builder.computeRunCount(); | |
| if (count == 0) { | |
| return this->setEmpty(); | |
| } else if (count == kRectRegionRuns) { | |
| builder.copyToRect(&fBounds); | |
| this->setRect(fBounds); | |
| } else { | |
| SkRegion tmp; | |
| tmp.fRunHead = RunHead::Alloc(count); | |
| builder.copyToRgn(tmp.fRunHead->writable_runs()); | |
| tmp.fRunHead->computeRunBounds(&tmp.fBounds); | |
| this->swap(tmp); | |
| } | |
| SkDEBUGCODE(this->validate();) | |
| return true; | |
| } | |
| ///////////////////////////////////////////////////////////////////////////////////////////////// | |
| ///////////////////////////////////////////////////////////////////////////////////////////////// | |
| struct Edge { | |
| enum { | |
| kY0Link = 0x01, | |
| kY1Link = 0x02, | |
| kCompleteLink = (kY0Link | kY1Link) | |
| }; | |
| SkRegion::RunType fX; | |
| SkRegion::RunType fY0, fY1; | |
| uint8_t fFlags; | |
| Edge* fNext; | |
| void set(int x, int y0, int y1) { | |
| SkASSERT(y0 != y1); | |
| fX = (SkRegion::RunType)(x); | |
| fY0 = (SkRegion::RunType)(y0); | |
| fY1 = (SkRegion::RunType)(y1); | |
| fFlags = 0; | |
| SkDEBUGCODE(fNext = NULL;) | |
| } | |
| int top() const { | |
| return SkFastMin32(fY0, fY1); | |
| } | |
| }; | |
| static void find_link(Edge* base, Edge* stop) { | |
| SkASSERT(base < stop); | |
| if (base->fFlags == Edge::kCompleteLink) { | |
| SkASSERT(base->fNext); | |
| return; | |
| } | |
| SkASSERT(base + 1 < stop); | |
| int y0 = base->fY0; | |
| int y1 = base->fY1; | |
| Edge* e = base; | |
| if ((base->fFlags & Edge::kY0Link) == 0) { | |
| for (;;) { | |
| e += 1; | |
| if ((e->fFlags & Edge::kY1Link) == 0 && y0 == e->fY1) { | |
| SkASSERT(NULL == e->fNext); | |
| e->fNext = base; | |
| e->fFlags = SkToU8(e->fFlags | Edge::kY1Link); | |
| break; | |
| } | |
| } | |
| } | |
| e = base; | |
| if ((base->fFlags & Edge::kY1Link) == 0) { | |
| for (;;) { | |
| e += 1; | |
| if ((e->fFlags & Edge::kY0Link) == 0 && y1 == e->fY0) { | |
| SkASSERT(NULL == base->fNext); | |
| base->fNext = e; | |
| e->fFlags = SkToU8(e->fFlags | Edge::kY0Link); | |
| break; | |
| } | |
| } | |
| } | |
| base->fFlags = Edge::kCompleteLink; | |
| } | |
| static int extract_path(Edge* edge, Edge* stop, SkPath* path) { | |
| while (0 == edge->fFlags) { | |
| edge++; // skip over "used" edges | |
| } | |
| SkASSERT(edge < stop); | |
| Edge* base = edge; | |
| Edge* prev = edge; | |
| edge = edge->fNext; | |
| SkASSERT(edge != base); | |
| int count = 1; | |
| path->moveTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY0)); | |
| prev->fFlags = 0; | |
| do { | |
| if (prev->fX != edge->fX || prev->fY1 != edge->fY0) { // skip collinear | |
| path->lineTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY1)); // V | |
| path->lineTo(SkIntToScalar(edge->fX), SkIntToScalar(edge->fY0)); // H | |
| } | |
| prev = edge; | |
| edge = edge->fNext; | |
| count += 1; | |
| prev->fFlags = 0; | |
| } while (edge != base); | |
| path->lineTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY1)); // V | |
| path->close(); | |
| return count; | |
| } | |
| #include "SkTSearch.h" | |
| static int EdgeProc(const Edge* a, const Edge* b) { | |
| return (a->fX == b->fX) ? a->top() - b->top() : a->fX - b->fX; | |
| } | |
| bool SkRegion::getBoundaryPath(SkPath* path) const { | |
| // path could safely be NULL if we're empty, but the caller shouldn't | |
| // *know* that | |
| SkASSERT(path); | |
| if (this->isEmpty()) { | |
| return false; | |
| } | |
| const SkIRect& bounds = this->getBounds(); | |
| if (this->isRect()) { | |
| SkRect r; | |
| r.set(bounds); // this converts the ints to scalars | |
| path->addRect(r); | |
| return true; | |
| } | |
| SkRegion::Iterator iter(*this); | |
| SkTDArray<Edge> edges; | |
| for (const SkIRect& r = iter.rect(); !iter.done(); iter.next()) { | |
| Edge* edge = edges.append(2); | |
| edge[0].set(r.fLeft, r.fBottom, r.fTop); | |
| edge[1].set(r.fRight, r.fTop, r.fBottom); | |
| } | |
| qsort(edges.begin(), edges.count(), sizeof(Edge), SkCastForQSort(EdgeProc)); | |
| int count = edges.count(); | |
| Edge* start = edges.begin(); | |
| Edge* stop = start + count; | |
| Edge* e; | |
| for (e = start; e != stop; e++) { | |
| find_link(e, stop); | |
| } | |
| #ifdef SK_DEBUG | |
| for (e = start; e != stop; e++) { | |
| SkASSERT(e->fNext != NULL); | |
| SkASSERT(e->fFlags == Edge::kCompleteLink); | |
| } | |
| #endif | |
| path->incReserve(count << 1); | |
| do { | |
| SkASSERT(count > 1); | |
| count -= extract_path(start, stop, path); | |
| } while (count > 0); | |
| return true; | |
| } |