diff options
Diffstat (limited to 'SrcShared/EmRegion.cpp')
-rw-r--r-- | SrcShared/EmRegion.cpp | 1500 |
1 files changed, 1500 insertions, 0 deletions
diff --git a/SrcShared/EmRegion.cpp b/SrcShared/EmRegion.cpp new file mode 100644 index 0000000..d811ef2 --- /dev/null +++ b/SrcShared/EmRegion.cpp @@ -0,0 +1,1500 @@ +/* -*- mode: C++; tab-width: 4 -*- */ +/* ===================================================================== *\ + Copyright (c) 2000-2001 Palm, Inc. or its subsidiaries. + All rights reserved. + + This file is part of the Palm OS Emulator. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +\* ===================================================================== */ + +#include "EmCommon.h" +#include "EmRegion.h" + +/* + See .h file for usage. + + EmRegion relies on an internal helper class, EmRegionImpl. EmRegionImpl + is a ref-counted implementation class. It can be shared among multiple + ARegions for efficient copying. If one region is altered, it creates + a new EmRegionImpl to contain the results, dropping the reference to + the previous EmRegionImpl (possibly causing it to be deleted). + + EmRegionImpl provides basic operations on regions. It stores region + data in a buffer of ACoords in the following format: + + <number of transition points in this scanline> + <y coordinate of scanline> + <x coordinate of "enter region" event> + <x coordinate of "exit region" event> + ... + <number of transition points in this scanline> + <y coordinate of scanline> + <x coordinate of "enter region" event> + <x coordinate of "exit region" event> + ... + ... + <number of points in this scanline> + <y coordinate of scanline> + <End of region marker> + + For example, take the following region: + + + | 5 10 15 20 25 30 + ----+-----|-----|-----|-----|-----|-----|----- + | + | + 5 - +-----+ +-----+ + | |.....| |.....| + | |.....| |.....| + 10 - |.....+-----------+.....| + | |.......................| + | |.......................| + 15 - +-----------------------+ + | + + This region would be represented with the following data: + + 5 // Number of coordinates in this line + 5 // y coordinate + 5 // entering x coordinate + 10 // exiting x coordinate + 20 // entering x coordinate + 25 // exiting x coordinate + 3 // Number of coordinates on this line + 10 // y coordinate + 5 // entering x coordinate + 25 // exiting x coordinate + 1 // Number of coordinates in this line + 15 // y coordinate + 0 // End of region marker + + Thus, the buffer would look as follows: + + 5 5 5 10 20 25 3 10 5 25 1 15 0 +*/ + +#include "EmCommon.h" +#include "EmRegion.h" + +#include "string.h" // memcpy + +// --------------------------------------------------------------------------- +// * EmRegionImpl +// --------------------------------------------------------------------------- +// Constructor for an empty region. + +EmRegion::EmRegionImpl::EmRegionImpl (void) : + fBounds (0, 0, 0, 0), + fCapacity (0), + fBuf (NULL) +{ +} + + +// --------------------------------------------------------------------------- +// * EmRegionImpl +// --------------------------------------------------------------------------- +// Constructor for a rectangular region. Uses a built-in array instead of +// getting memory from the heap. The array ends up looking as follows: +// +// 3, top, left, right, 1, bottom ,0 + +EmRegion::EmRegionImpl::EmRegionImpl (const EmRect& r) : + fBounds (r), + fCapacity (7), + fBuf (fRectBuf) +{ + EmCoord* s = fBuf; + + *s++ = 3; + *s++ = r.fTop; + *s++ = r.fLeft; + *s++ = r.fRight; + + *s++ = 1; + *s++ = r.fBottom; + + *s = 0; +} + + +// --------------------------------------------------------------------------- +// * EmRegionImpl +// --------------------------------------------------------------------------- +// Constructor for a general region. Works from a buffer created by +// RegionOp, so it's not likely that clients will be calling this constructor. + +EmRegion::EmRegionImpl::EmRegionImpl (const EmCoord* s, long len) : + fCapacity (0), + fBuf (NULL) +{ + if (s && len > 0) + { + fCapacity = len; + + if (len <= 7) + fBuf = fRectBuf; + else + fBuf = new EmCoord[len]; + + memcpy (fBuf, s, len * sizeof (EmCoord)); + + CalcBounds (); + } +} + + +// --------------------------------------------------------------------------- +// * ~EmRegionImpl +// --------------------------------------------------------------------------- +// Destructor + +EmRegion::EmRegionImpl::~EmRegionImpl (void) +{ + if (fRectBuf != fBuf) + delete [] fBuf; +} + + +// --------------------------------------------------------------------------- +// * Equality +// --------------------------------------------------------------------------- + +Bool +EmRegion::EmRegionImpl::operator== (const EmRegionImpl& other) const +{ + return this->IsEqual (other); +} + + +// --------------------------------------------------------------------------- +// * GetRects +// --------------------------------------------------------------------------- +// Returns the number of rects this region decomposes to. If a buffer is +// provided, return the rects themselves, too. This method assumes the +// buffer is already large enough, so you might want to call it with NULL +// first to get a rect count. Or, better yet, use EmRegionRectIterator to +// get the rectangles one-by-one. + +long +EmRegion::EmRegionImpl::GetRects (EmRect* rp) const +{ + EmCoord* s = fBuf; + long ii = 0; + + if (s) + { + EmCoord next; + EmRect r; + + while ((next = *s++) != 0) + { + r.fTop = *s++; + r.fBottom = s[next]; + + while (next > 1) + { + r.fLeft = *s++; + r.fRight = *s++; + next -= 2; + + if (!r.IsEmpty ()) + { + if (rp) + rp[ii] = r; + + ++ii; + } + } + } + + EmAssert (s <= fBuf + fCapacity); + } + + return ii; +} + + +// --------------------------------------------------------------------------- +// * CalcBounds +// --------------------------------------------------------------------------- +// Scan the buffer data and determine the region's extent. This is done by +// essentially decomposing the region into a series of rectangles and then +// unioning those rectangles together. + +void +EmRegion::EmRegionImpl::CalcBounds (void) +{ + EmCoord* s = fBuf; + + fBounds.BeEmpty (); + + if (s) + { + EmCoord next; + EmRect r; + + while ((next = *s++) != 0) + { + r.fTop = *s++; + r.fBottom = s[next]; + + while (next > 1) + { + r.fLeft = *s++; + r.fRight = *s++; + next -= 2; + fBounds.UnionWith (r); + } + } + + EmAssert (s <= fBuf + fCapacity); + } +} + + +// --------------------------------------------------------------------------- +// * Contains +// --------------------------------------------------------------------------- +// Scan the buffer data and determine if the given point falls within +// the region. For each scanline, see if the y-coordinate of the point +// falls between it and the next scanline. If so, scan all the x-coordinate +// pairs for the scanline to see if the x-coordinate of the point falls +// between any of them. + +Bool +EmRegion::EmRegionImpl::Contains (const EmPoint& p) const +{ + EmCoord* bp = fBuf; + EmCoord s, e, next; + + if (fBounds.Contains (p)) + { + while ((next = *bp++) != 0) // while we haven't hit the end... + { + s = *bp++; + if (p.fY >= s && p.fY < bp[next]) // if between right scanlines... + { + while (next > 1) // while we have more x-pairs... + { + s = *bp++; // entering region x-coord + e = *bp++; // exiting region x-coord + if (p.fX >= s && p.fX < e) + return true; // *** We're in! Return TRUE. *** + + next -= 2; // move to next pair + } + } + else + { + bp += next - 1; // move to next scanline + } + } + + EmAssert (bp <= fBuf + fCapacity); + } + + return false; +} + + +// --------------------------------------------------------------------------- +// * Offset +// --------------------------------------------------------------------------- +// Scan the buffer data, adding dy to each y-event, and dx to each pair of +// x-events. Also update the region bounds. + +void +EmRegion::EmRegionImpl::Offset (EmCoord dx, EmCoord dy) +{ + EmCoord* s = fBuf; + + if (s && (dx || dy)) + { + EmCoord next; + + while ((next = *s++) != 0) + { + *s++ += dy; + while (next > 1) + { + *s++ += dx; + *s++ += dx; + next -= 2; + } + } + + fBounds.Offset (dx, dy); // !!! Do this outside "if (s)..."? + + EmAssert (s <= fBuf + fCapacity); + } +} + + +// --------------------------------------------------------------------------- +// * IsEqual +// --------------------------------------------------------------------------- +// Determine if two regions encompass the same area. First do some +// quick-tests: +// +// - See if the buffer lengths are equal. If not, regions are not equal. +// +// - See if the buffers are both NULL. If so, they're both empty regions +// and are considered equal. +// +// - See if just one buffer is NULL. If so, it's empty and the other is +// not, so they are not equal. +// +// Finally, compare the two buffers. Since the data in the buffer is well- +// ordered, we can do a direct memory compare to see if they are equal. + +Bool +EmRegion::EmRegionImpl::IsEqual (const EmRegionImpl& r2) const +{ + if (fCapacity != r2.fCapacity) + return false; + + if (fBuf == NULL && r2.fBuf == NULL) + return true; + + if (fBuf == NULL || r2.fBuf == NULL) + return false; + + return memcmp (fBuf, r2.fBuf, fCapacity * sizeof (EmCoord)) == 0; +} + + + + +// --------------------------------------------------------------------------- +// * EmRegion +// --------------------------------------------------------------------------- +// You want constructors, we got constructors... + +EmRegion::EmRegion (void) : + fImpl (new EmRegionImpl) +{ + EmAssert (fImpl.get ()); +} + +EmRegion::EmRegion (const EmCoord* s, long len) : + fImpl (new EmRegionImpl (s, len)) +{ + EmAssert (fImpl.get ()); +} + +EmRegion::EmRegion (const EmRect& r) : + fImpl (new EmRegionImpl (r)) +{ + EmAssert(fImpl.get ()); +} + +EmRegion::EmRegion (const EmRegion& r) : + fImpl (r.fImpl) +{ + EmAssert (fImpl.get ()); +} + + +// --------------------------------------------------------------------------- +// * ~EmRegion +// --------------------------------------------------------------------------- +// Destructor. Merely unreference our implementation object. If we're the +// last/only region referencing it, it will delete itself. + +EmRegion::~EmRegion (void) +{ +} + + +// --------------------------------------------------------------------------- +// * Assignment operator +// --------------------------------------------------------------------------- +// If one region is being assigned to another, drop the reference to the +// old implementation object and alias the new implementation object. +// +// If a rectangle is being assigned to a region (a common operation), see +// if the region already holds rectangular information and if we are the +// exclusive owners of that information. If so, do a quick rect-copy. If +// not, create a new implementation object to hold the rect. + +EmRegion& +EmRegion::operator= (const EmRegion& r) +{ + if (this != &r) + { + fImpl = r.fImpl; + } + + return *this; +} + + +EmRegion& +EmRegion::operator= (const EmRect& r) +{ + EmAssert (fImpl.get ()); + + if (!fImpl->isShared () && fImpl->fCapacity <= 7) + { +// *fImpl = EmRegionImpl(r); // !!! Do it this way? + + if (fImpl->fBuf == NULL) + fImpl->fBuf = fImpl->fRectBuf; + + fImpl->fCapacity = 7; + + EmCoord* s = fImpl->fBuf; + + *s++ = 3; + *s++ = r.fTop; + *s++ = r.fLeft; + *s++ = r.fRight; + + *s++ = 1; + *s++ = r.fBottom; + + *s = 0; + + fImpl->fBounds = r; + } + else + { + fImpl = EmRefCounter<EmRegionImpl> (new EmRegionImpl (r)); + } + + return *this; +} + + +// --------------------------------------------------------------------------- +// * BeEmpty +// --------------------------------------------------------------------------- +// If we are not already empty and if we are the exclusive owner of our +// implementation object, quickly set the implementation to an empty rect. +// Otherwise, create a new empty implementation object. + +void +EmRegion::BeEmpty (void) +{ + EmAssert (fImpl.get ()); + + if (fImpl->fBuf) // If not already empty + { + if (!fImpl->isShared () && fImpl->fBuf == fImpl->fRectBuf) + { + fImpl->fBuf = NULL; + fImpl->fCapacity = 0; + fImpl->fBounds.BeEmpty (); + } + else + { + fImpl = new EmRegionImpl; + } + } +} + + +// --------------------------------------------------------------------------- +// * Bounds +// --------------------------------------------------------------------------- + +const EmRect& +EmRegion::Bounds (void) const +{ + EmAssert (fImpl.get ()); + + return fImpl->fBounds; +} + + +// --------------------------------------------------------------------------- +// * GetRects +// --------------------------------------------------------------------------- + +long +EmRegion::GetRects (EmRect* rp) const +{ + EmAssert (fImpl.get ()); + + return fImpl->GetRects (rp); +} + + +// --------------------------------------------------------------------------- +// * IsEmpty +// --------------------------------------------------------------------------- + +Bool +EmRegion::IsEmpty (void) const +{ + EmAssert (fImpl.get ()); + + return fImpl->fBounds.IsEmpty (); +} + + +// --------------------------------------------------------------------------- +// * IsEqual +// --------------------------------------------------------------------------- + +Bool +EmRegion::IsEqual (const EmRegion& other) const +{ + EmAssert (fImpl.get ()); + + if (fImpl == other.fImpl) + return true; + + return fImpl->IsEqual (*other.fImpl); +} + + +// --------------------------------------------------------------------------- +// * Contains +// --------------------------------------------------------------------------- + +Bool +EmRegion::Contains (const EmPoint& p) const +{ + EmAssert (fImpl.get ()); + + return fImpl->Contains (p); +} + + +// --------------------------------------------------------------------------- +// * Equality/inequality +// --------------------------------------------------------------------------- + +Bool operator== (const EmRegion& r1, const EmRegion& r2) +{ + return r1.IsEqual (r2); +} + +Bool operator!= (const EmRegion& r1, const EmRegion& r2) +{ + return !r1.IsEqual (r2); +} + + +// --------------------------------------------------------------------------- +// * Offset +// --------------------------------------------------------------------------- + +void +EmRegion::Offset (const EmPoint& pt) +{ + EmAssert (fImpl.get ()); + + fImpl->Offset (pt.fX, pt.fY); +} + +void +EmRegion::Offset (EmCoord dx, EmCoord dy) +{ + EmAssert (fImpl.get ()); + + fImpl->Offset (dx, dy); +} + + +// --------------------------------------------------------------------------- +// * Inset +// --------------------------------------------------------------------------- +// Doing a horizontal inset is easy: Just take each x-event pair and move +// the first one to the right and the second one to the left. +// +// But wait, is it really so easy? What happens if the two x values cross +// over each other? Then the pair must be discarded. Or what if the inset +// operation is passed negative values and the x range now overlaps an +// adjacent x range? +// +// The easiest thing to do is convert the region into a series of rectangles, +// perform the inset/outset on the left and right coordinates of the +// rectangles, and then union the whole shebang back together, letting +// the RegionOp function sort out overlaps and empty rectangles. +// +// Once that's done, we have to figure out how to perform the vertical +// inset operation. The easiest way is to turn the region 90 degrees and +// perform another horizontal inset. To do the rotation, the region +// is turned into rectangles, the rectangles are flipped around the X==Y +// axis, and a new region is built up. That new region is inset, and +// the resulting region is again flipped, giving us our final answer. + +void +EmRegion::Inset (const EmPoint& pt) +{ + this->Inset (pt.fX, pt.fY); +} + +void +EmRegion::Inset (EmCoord dx, EmCoord dy) +{ + EmRegion newRgn, rectRgn; + EmRect r, r2; + + for (int ii = 0; ii < 2; ii++) + { + EmRegionRectIterator iter1(*this); + newRgn.BeEmpty(); + + while (iter1.Next (r)) + { + r.fLeft += dx; + r.fRight -= dx; + + if (!r.IsEmpty ()) + { + // !!! I can probably flip the rectangles here, + // and forego doing it as a seperate loop below. + rectRgn = r; + newRgn.UnionWith (rectRgn); + } + } + + *this = newRgn; + + EmRegionRectIterator iter2(*this); + newRgn.BeEmpty(); + + while (iter2.Next (r)) + { + r2.fTop = r.fLeft; + r2.fLeft = r.fTop; + r2.fBottom = r.fRight; + r2.fRight = r.fBottom; + + rectRgn = r2; + newRgn.UnionWith (rectRgn); + } + + *this = newRgn; + + dx = dy; + } +} + + +// --------------------------------------------------------------------------- +// * Operations +// --------------------------------------------------------------------------- + +EmRegion& +EmRegion::UnionWith (const EmRegion& other) +{ + return *this = RegionOp (EmRegion::eUnion, *this, other); +} + +EmRegion& +EmRegion::IntersectWith (const EmRegion& other) +{ + return *this = RegionOp (EmRegion::eIntersection, *this, other); +} + +EmRegion& +EmRegion::Subtract (const EmRegion& other) +{ + return *this = RegionOp (EmRegion::eDifference, *this, other); +} + +EmRegion& +EmRegion::XorWith (const EmRegion& other) +{ + return *this = Xor (*this, other); +} + +EmRegion Union (const EmRegion& r1, const EmRegion& r2) +{ + return EmRegion::RegionOp (EmRegion::eUnion, r1, r2); +} + +EmRegion Intersection (const EmRegion& r1, const EmRegion& r2) +{ + return EmRegion::RegionOp (EmRegion::eIntersection, r1, r2); +} + +EmRegion Difference (const EmRegion& r1, const EmRegion& r2) +{ + return EmRegion::RegionOp (EmRegion::eDifference, r1, r2); +} + +EmRegion Xor (const EmRegion& r1, const EmRegion& r2) +{ + return Difference (Union (r1, r2), Intersection (r1, r2)); +} + + +// --------------------------------------------------------------------------- +// * GetBuf +// --------------------------------------------------------------------------- + +EmCoord* +EmRegion::GetBuf (void) const +{ + EmAssert (fImpl.get ()); + + return fImpl->fBuf; +} + + +// --------------------------------------------------------------------------- +// * Length +// --------------------------------------------------------------------------- + +long +EmRegion::Length (void) const +{ + EmAssert (fImpl.get ()); + + return fImpl->fCapacity; +} + + +// --------------------------------------------------------------------------- +// * RegionOp +// --------------------------------------------------------------------------- +// Scan over two regions, building a third region from them. As we scan +// over the two regions, keep track of our state. We keep track of whether +// or not we are in each of the regions. At each point where we transition +// in or out of particular region, we optionally record an x event depending +// on the operation. + +EmRegion +EmRegion::RegionOp (EOpcode code, const EmRegion& r1, const EmRegion& r2) +{ + // + // Do the easy cases first + // + switch (code) + { + case EmRegion::eUnion: + if (r1.IsEmpty () && r2.IsEmpty ()) + return EmRegion(); + + if (r1.IsEmpty ()) + return r2; + + if (r2.IsEmpty ()) + return r1; + + break; + + case EmRegion::eDifference: + if (r1.IsEmpty ()) + return EmRegion (); + + if (r2.IsEmpty ()) + return r1; + + break; + + case EmRegion::eRevDifference: + if (r2.IsEmpty ()) + return EmRegion (); + + if (r1.IsEmpty ()) + return r2; + + break; + + case EmRegion::eIntersection: + if (r1.IsEmpty () || r2.IsEmpty ()) + return EmRegion (); + + break; + } + + // + // Get working pointers to our two input buffers. + // "shape1" and "shape2" point to the beginnings + // of the next scanlines to process. + // + EmCoord* shape1 = r1.GetBuf (); + EmCoord* shape2 = r2.GetBuf (); + + // + // Get a pointer to our destination buffer. + // Use a buffer on the stack for speed if possible. + // + long buflen = (r1.Length () * r2.Length ()); + EmAssert (buflen > 0); + + EmCoord* buf; + EmCoord stackBuf[100]; // !!! Bigger? Smaller? + if (buflen > (long) countof (stackBuf)) + buf = new EmCoord[buflen]; + else + buf = stackBuf; + + EmAssert(buf); + + EmCoord* ss = buf; + EmCoord* oldss = NULL; + + EmCoord* x1 = 0; + EmCoord* x2 = 0; + EmCoord* fixup; + EmCoord y, l1 = 0, l2 = 0, tl1, tl2; + long len, oldlen = -1; + int test; + + for (;;) + { + // + // Get the lengths of the next scanlines to work with. + // + tl1 = *shape1; + tl2 = *shape2; + + // + // If they are both zero, we are at the ends of both regions, + // and can quit. + // + if (tl1 == 0 && tl2 == 0) + break; + + // + // Set "test" based on which scanline is above the other. + // If scanline1 is above scanline2, set "test" to negative. + // If scanline1 is below scanline2, set "test" to positive. + // If both scanlines are on the same line, set "test" to zero. + // If the length of the scanline is zero, we are at the end + // of the region, and so are conceptually at the bottom of + // the coordinate space. + // + if (tl1 == 0) + test = 1; + else if (tl2 == 0) + test = -1; + else + test = shape1[1] - shape2[1]; + + // + // Start processing the scanlines. Remember some information + // on each one before merging them together. + // + if (test <= 0) + { + y = shape1[1]; // Remember y coordinate + x1 = &shape1[2]; // Point to start of x-pairs + shape1 += tl1 + 1; // Bump to next scanline in this region + l1 = tl1 - 1; // Remember number of x-coordinates + } + + if (test >= 0) + { + y = shape2[1]; // Remember y coordinate + x2 = &shape2[2]; // Point to start of x-pairs + shape2 += tl2 + 1; // Bump to next scanline in this region + l2 = tl2 - 1; // Remember number of x-coordinates + } + + // + // Start outputting a new scanline. First, remember where it + // starts so we can write the final length later. Next, write + // an initial length of 1. Follow it with the y-coordinate of + // the new scanline. + // + fixup = ss; + *ss++ = 1; + *ss++ = y; + + EmAssert(ss - buf < buflen); + + if (l1 == 0 && l2 == 0) + { + // + // If there are no x events in either scanline, there's nothing + // to merge together, so there's nothing to output. + // + } + else if (l1 == 0) + { + // + // The first region is depleted or we haven't reached its + // first scanline yet, so work solely with the second + // region. If we're doing a union or reverse difference + // operation (where region A is subtracted from region B), + // copy region B's data. + // + if (code == EmRegion::eUnion || code == EmRegion::eRevDifference) + { + memcpy (ss, x2, l2 * sizeof (EmCoord)); + ss += l2; + EmAssert (ss - buf < buflen); + } + } + else if (l2 == 0) + { + // + // The second region is depleted or we haven't reached its + // first scanline yet, so work solely with the first + // region. If we're doing a union or difference + // operation (where region B is subtracted from region A), + // copy region A's data. + // + if (code == EmRegion::eUnion || code == EmRegion::eDifference) + { + memcpy (ss, x1, l1 * sizeof (EmCoord)); + ss += l1; + EmAssert (ss - buf < buflen); + } + } + else + { + // + // Start merging two scanlines together. While there + // are still x-pairs left to examine: + // + // -- Compare an x-value from region A against an + // x-value from region B. + // + // -- If Region A's is less than Region B's, bump + // to the next x-value in Region A and invert + // a state bit indicating whether or not we are + // "in" region A. + // + // -- If Region B's is less than Region A's, bump + // to the next x-value in Region B and invert + // a state bit indicating whether or not we are + // "in" region B. + // + // -- If both x-values are the same, bump to the next + // x-values in both regions, and invert the state + // bits for both regions. + // + // -- Examine the state bits. They tell us when we are + // leaving one state and entering another. If we + // are entering a desired state (for instance, we + // are doing an intersection operation and both + // state bits are now on), emit an x-event in the + // destination region. Similarly, if we were just + // in a desired state and are now leaving it, again + // emit an x-event to close off the previous x-event. + // + // Note that our operation codes are cleverly chosen to + // correspond to our state value. If we are doing an + // intersection operation, for example, we are interested + // in pixels that are in both Region A and Region B. Thus + // we are interested in ranges where both state bits are + // on. When both state bits are on, the state value is "3", + // which also happens to be the value of eIntersection. + // + // As another example, consider the "difference" operation, + // where Region B is subtracted from Region A. In that case, + // we are interested in the pixels that are in Region A but + // not in Region B. In other words, we are interested in + // ranges where Region A's state bit (bit 0) is set and + // Region B's state bit (bit 1) is clear. At that time, the + // state value is "1", which also happens to be the value + // of eDifference. + // + // The process of the eRevDifference operation is analogous. + // + // The process of the eUnion operation is a little + // tricky. The value of eUnion is zero, which means that we + // are interested in ranges that belong to _neither_ of the + // two regions. That means that our resulting region logically + // describes the pixels _outside_ the union of the two source + // regions. However, it's all a matter of definition. Ultimately, + // what we have is an outline. Whether we choose to be interested + // in the bits "inside" the outline or "outside" the outline is + // up to us. In other words, if the union of two regions happens + // to result in a square, our algorithm will think its generating + // the following region: + // + // . . . . . . . . . . . . . . . + // . . . . . . . . . . . . . . . + // . . . . . . . . . . . . . . . + // . . . . +--------+. . . . . . + // . . . . .| | . . . . . + // . . . . | |. . . . . . + // . . . . .| | . . . . . + // . . . . | |. . . . . . + // . . . . .+--------+ . . . . . + // . . . . . . . . . . . . . . . + // . . . . . . . . . . . . . . . + // . . . . . . . . . . . . . . . + // + // But we are perfectly free to think of it as: + // + // +--------+ + // |. . . . | + // | . . . .| + // |. . . . | + // | . . . .| + // +--------+ + // + EmCoord* p1 = x1; + EmCoord* p2 = x2; + EmCoord xl1 = l1, xl2 = l2; + EmCoord x, xold = 0; + int xflag = 0; + + while (xl1 > 0 && xl2 > 0) + { + test = *p1 - *p2; + + if (test <= 0) + { + x = *p1++; + xflag ^= 1; + xl1--; + } + + if (test >= 0) + { + x = *p2++; + xflag ^= 2; + xl2--; + } + + if (xflag == code || xold == code) + { + *ss++ = x; + EmAssert (ss - buf < buflen); + } + + xold = xflag; + } + + // + // One of the scanlines has been exhausted. Determine what + // to do with the remaining one based on the operation we're + // performing. If we're doing a union or difference operation + // and there's still data left in Region A's scanline, copy + // that data to the destination. If we're doing a union or + // reverse difference operation and there's still data in + // Region B's scanline, copy that data to the destination. + // + if (code == EmRegion::eUnion || code == EmRegion::eDifference) + { + while (xl1-- > 0) + { + *ss++ = *p1++; + EmAssert (ss - buf < buflen); + } + } + + if (code == EmRegion::eUnion || code == EmRegion::eRevDifference) + { + while (xl2-- > 0) + { + *ss++ = *p2++; + EmAssert (ss - buf < buflen); + } + } + } + + // + // We've just merged two scanlines. First, determine the new + // scanline's length and write it out to the fixup location. + // + len = ss - fixup - 2; + *fixup = len + 1; + + // + // Next, if the new scanline happens to be identical to the + // previous scanline, don't bother recording it (reset our + // "ss" pointer so that a subsequent scanline will overwrite + // the one we're getting rid of). + // + if (len > 0 && + len == oldlen && + memcmp (&oldss[2], &fixup[2], len * sizeof (EmCoord)) == 0) + { + ss = fixup; + } + else + { + oldss = fixup; + oldlen = len; + } + } + + // + // Done with all the scanlines. Write out our terminator. + // + *ss++ = 0; + + EmAssert (ss - buf < buflen); + + // + // Create our new region from the raw buffer. + // + EmRegion result (buf, ss - buf); + + // + // Delete our raw buffer if necessary. + // + if (buf != stackBuf) + delete [] buf; + + return result; +} + + + + +// --------------------------------------------------------------------------- +// * EmRegionRectIterator +// --------------------------------------------------------------------------- +// Constructor. Note that "fRegion" is an EmRegion, _not_ a reference to an +// EmRegion. This means that any changes to the source region will not affect +// the iterator, as the iterator effectively makes a copy of the region +// at construction time. + +EmRegionRectIterator::EmRegionRectIterator (const EmRegion& r) : + fRegion (r) +{ + this->Reset (); +} + + +// --------------------------------------------------------------------------- +// * Reset +// --------------------------------------------------------------------------- + +void +EmRegionRectIterator::Reset (void) +{ + fBufPtr = fRegion.GetBuf (); + fNext = 0; +} + + +// --------------------------------------------------------------------------- +// * Next +// --------------------------------------------------------------------------- +// Return the next rectangle composing the region. +// +// Iteration works as follows. The first time we're called, we notice the +// fact and fetch information on the next scanline. That information +// includes the scanline's y-coordinate and an offset to the next scanline. +// The y-coordinate of the current scanline is our rectangle's top value, +// while the y-coordinate of the next scanline is our rectangles bottom +// value. Next, we start processing the rest of the scanline, which +// contains x-coordinate pairs. Each pair makes up the left and right +// values of the rectangle. Rectangles are formed until we reach the +// end of the scanline, at which time we move to the next scanline. If +// we run out of scanlines, we're done iterating. + +Bool +EmRegionRectIterator::Next (EmRect& r) +{ + while (fBufPtr) + { + if (fNext > 1) + { + r.fLeft = *fBufPtr++; + r.fRight = *fBufPtr++; + fNext -= 2; + + EmAssert (fBufPtr <= fRegion.GetBuf () + fRegion.Length ()); + return true; + } + + fNext = *fBufPtr++; + if (fNext == 0) + { + EmAssert (fBufPtr <= fRegion.GetBuf () + fRegion.Length ()); + return false; + } + + r.fTop = *fBufPtr++; + r.fBottom = fBufPtr[fNext]; + } + + EmAssert (fBufPtr <= fRegion.GetBuf () + fRegion.Length ()); + + return false; +} + +#if 0 +// --------------------------------------------------------------------------- +// * Testing code +// --------------------------------------------------------------------------- +#include "stdio.h" + +void TestRegion(); +void PrintRegion(const EmRegion& r); +Bool VerifyRegion(const char*, const EmRegion& r, EmRect rects[], int numRects); + +void PrintRegion(const EmRegion& r) +{ + int ii = 0; + EmRect testRect; + EmRegionRectIterator iter(r); + while (iter.Next(testRect)) + { + ii++; + printf("rect #%d: l = %ld, t = %ld, r = %ld, b = %ld\n", ii, + testRect.fLeft, testRect.fTop, testRect.fRight, testRect.fBottom); + } + + printf("\n"); +} + +Bool VerifyRegion(const char* testName, const EmRegion& r, EmRect rects[], int numRects) +{ + Bool success = true; + + printf("Region test: %s\n", testName); + + int ii = 0; + EmRect testRect; + EmRegionRectIterator iter(r); + while (iter.Next(testRect)) + { + if (ii < numRects) + { + if (testRect != rects[ii]) + { + printf("expected rect #%d: l = %ld, t = %ld, r = %ld, b = %ld\n", ii, + rects[ii].fLeft, rects[ii].fTop, rects[ii].fRight, rects[ii].fBottom); + printf("returned rect #%d: l = %ld, t = %ld, r = %ld, b = %ld\n", ii, + testRect.fLeft, testRect.fTop, testRect.fRight, testRect.fBottom); + + success = false; + } + } + else + { + printf("Iterator returned extra rectangle:\n"); + printf("returned rect #%d: l = %ld, t = %ld, r = %ld, b = %ld\n", ii, + testRect.fLeft, testRect.fTop, testRect.fRight, testRect.fBottom); + + success = false; + } + ii++; + } + + if (ii != numRects) + { + printf("Iterator returned %d rects, expected %d\n", ii, numRects); + + success = false; + } + + printf("\n"); + + return success; +} + +void TestRegion() +{ + printf("Testing EmRegion class...\n"); + + Bool success = true; + + EmRegion testRegion1; + EmRegion testRegion2; + + testRegion1 = EmRegion(EmRect(0, 0, 0, 0)); + EmRect resultRects1[] = + { + EmRect( 0, 0, 0, 0 ) + }; + success &= VerifyRegion("Set to empty rect", testRegion1, resultRects1, countof(resultRects1)); + + testRegion2 = EmRegion(EmRect(1, 2, 3, 4)); + EmRect resultRects2[] = + { + EmRect( 1, 2, 3, 4 ) + }; + success &= VerifyRegion("Set to non-empty rect", testRegion2, resultRects2, countof(resultRects2)); + + testRegion2 = EmRegion(EmRect(5, 6, 7, 8)); + EmRect resultRects3[] = + { + EmRect( 5, 6, 7, 8 ) + }; + success &= VerifyRegion("Reassign to non-empty rect", testRegion2, resultRects3, countof(resultRects3)); + + EmRegion testRegion(EmRect(0, 0, 10, 10)); + EmRegion containedRegion(EmRect(3, 3, 7, 7)); + EmRegion containingRegion(EmRect(-5, -5, 15, 15)); + EmRegion disjointRegion(EmRect(15, 15, 25, 25)); + EmRegion overlappingRegion(EmRect(5, 5, 15, 15)); + EmRegion result; + + { + // + // Test Subtract + // + + result = Difference(testRegion, containedRegion); + EmRect resultRects1[] = + { + EmRect( 0, 0, 10, 3 ), + EmRect( 0, 3, 3, 7 ), + EmRect( 7, 3, 10, 7 ), + EmRect( 0, 7, 10, 10 ) + }; + success &= VerifyRegion("Difference/contained", result, resultRects1, countof(resultRects1)); + + result = Difference(testRegion, containingRegion); + success &= VerifyRegion("Difference/containing", result, NULL, 0); + + result = Difference(testRegion, disjointRegion); + EmRect resultRects3[] = + { + EmRect( 0, 0, 10, 10 ) + }; + success &= VerifyRegion("Difference/disjoint", result, resultRects3, countof(resultRects3)); + + result = Difference(testRegion, overlappingRegion); + EmRect resultRects4[] = + { + EmRect( 0, 0, 10, 5 ), + EmRect( 0, 5, 5, 10 ) + }; + success &= VerifyRegion("Difference/overlapping", result, resultRects4, countof(resultRects4)); + } + + { + // + // Test Intersection + // + + result = Intersection(testRegion, containedRegion); + EmRect resultRects1[] = + { + EmRect( 3, 3, 7, 7 ) + }; + success &= VerifyRegion("Intersection/contained", result, resultRects1, countof(resultRects1)); + + result = Intersection(testRegion, containingRegion); + EmRect resultRects2[] = + { + EmRect( 0, 0, 10, 10 ) + }; + success &= VerifyRegion("Intersection/containing", result, resultRects2, countof(resultRects2)); + + result = Intersection(testRegion, disjointRegion); + success &= VerifyRegion("Intersection/disjoint", result, NULL, 0); + + result = Intersection(testRegion, overlappingRegion); + EmRect resultRects4[] = + { + EmRect( 5, 5, 10, 10 ) + }; + success &= VerifyRegion("Intersection/overlapping", result, resultRects4, countof(resultRects4)); + } + + { + // + // Test Union + // + + result = Union(testRegion, containedRegion); + EmRect resultRects1[] = + { + EmRect( 0, 0, 10, 10 ) + }; + success &= VerifyRegion("Union/contained", result, resultRects1, countof(resultRects1)); + + result = Union(testRegion, containingRegion); + EmRect resultRects2[] = + { + EmRect( -5, -5, 15, 15 ) + }; + success &= VerifyRegion("Union/containing", result, resultRects2, countof(resultRects2)); + + result = Union(testRegion, disjointRegion); + EmRect resultRects3[] = + { + EmRect( 0, 0, 10, 10 ), + EmRect( 15, 15, 25, 25 ) + }; + success &= VerifyRegion("Union/disjoint", result, resultRects3, countof(resultRects3)); + + result = Union(testRegion, overlappingRegion); + EmRect resultRects4[] = + { + EmRect( 0, 0, 10, 5 ), + EmRect( 0, 5, 15, 10 ), + EmRect( 5, 10, 15, 15 ) + }; + success &= VerifyRegion("Union/overlapping", result, resultRects4, countof(resultRects4)); + } + + { + // + // Test offset + // + + EmRegion rgn1(EmRect(10, 0, 40, 10)); + EmRegion rgn2(EmRect(20, 0, 30, 5)); + + result = Difference(rgn1, rgn2); + result.Offset(1, 1); + + EmRect resultRects1[] = + { + EmRect( 11, 1, 21, 6 ), + EmRect( 31, 1, 41, 6 ), + EmRect( 11, 6, 41, 11 ) + }; + success &= VerifyRegion("Offset", result, resultRects1, countof(resultRects1)); + + // Test with different x and y values to make + // sure I haven't swapped them. + result = Difference(rgn1, rgn2); + result.Offset(1, 2); + + EmRect resultRects2[] = + { + EmRect( 11, 2, 21, 7 ), + EmRect( 31, 2, 41, 7 ), + EmRect( 11, 7, 41, 12 ) + }; + success &= VerifyRegion("Offset", result, resultRects2, countof(resultRects2)); + + // Test with one of them zero to make sure I + // don't improperly optimize. + result = Difference(rgn1, rgn2); + result.Offset(0, 2); + + EmRect resultRects3[] = + { + EmRect( 10, 2, 20, 7 ), + EmRect( 30, 2, 40, 7 ), + EmRect( 10, 7, 40, 12 ) + }; + success &= VerifyRegion("Offset", result, resultRects3, countof(resultRects3)); + } + + { + // + // Test inset + // + + EmRegion rgn1(EmRect(10, 0, 40, 10)); + EmRegion rgn2(EmRect(20, 0, 30, 5)); + + result = Difference(rgn1, rgn2); + result.Inset(1, 1); + + EmRect resultRects[] = + { + EmRect( 11, 1, 19, 6 ), + EmRect( 31, 1, 39, 6 ), + EmRect( 11, 6, 39, 9 ) + }; + success &= VerifyRegion("Inset", result, resultRects, countof(resultRects)); + } + + if (success) + printf("TestRegion() succeeded.\n"); + else + printf("TestRegion() failed!\n"); +} + +#endif // if 0 |