You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
531 lines
23 KiB
531 lines
23 KiB
/******************************Module*Header*******************************\
|
|
* Module Name: rgn2path.cxx *
|
|
* *
|
|
* Created: 14-Sep-1993 11:00:07 *
|
|
* Author: Kirk Olynyk [kirko] *
|
|
* *
|
|
* Copyright (c) 1993-1999 Microsoft Corporation *
|
|
* *
|
|
* Discussion *
|
|
* *
|
|
* Input *
|
|
* *
|
|
* The input to the diagonalization routing is a rectangular *
|
|
* path whose vertices have integer endpoints. Moreover it *
|
|
* is required that the path always has the region on its *
|
|
* left and that successive lines are mutually orthogonal. *
|
|
* *
|
|
* All paths are in device 28.4 coordinates. (Since all of *
|
|
* the input coordinates are integers, the fractional part of all *
|
|
* coordinates is zero.) *
|
|
* *
|
|
* Output *
|
|
* *
|
|
* A path that contains the same pixels as the originl path. *
|
|
* *
|
|
* Filling Convention *
|
|
* *
|
|
* Any region bounded by two non-horizontal lines is closed *
|
|
* on the left and open on the right. If the region is bounded *
|
|
* by two horizontal lines, it is closed on the top and open on *
|
|
* bottom. *
|
|
* *
|
|
* Definition *
|
|
* *
|
|
* A CORNER is subsequence of two lines from the orignal axial path. *
|
|
* It is convenient to partition the set of corners into two classes; *
|
|
* HORIZONTAL-VERTIAL and VERTICAL-HORIZONTAL. *
|
|
* *
|
|
* A corner is "diagonalizable" the original two lines can be replaced *
|
|
* by a single diagonal line such that same pixels would be rendered *
|
|
* (using the filling convention defined above). *
|
|
* *
|
|
* *
|
|
* Nomenclature *
|
|
* *
|
|
* S ::= "SOUTH" ::= one pixel move in +y-direction *
|
|
* N ::= "NORTH" ::= one pixel move in -y-direction *
|
|
* E ::= "EAST" ::= one pixel move in +x direction *
|
|
* W ::= "WEST" ::= one pixel move in -x direction *
|
|
* *
|
|
* The set of diagonalizable corners are described by *
|
|
* the following regular expressions: *
|
|
* *
|
|
* DIAGONALIZABLE CORNERS *
|
|
* *
|
|
* S(E+|W+) a one pixel move in the +y-direction *
|
|
* followed by at least one pixel in any horizontal *
|
|
* direction *
|
|
* *
|
|
* S+W an arbitary number of pixels in the +y-direction *
|
|
* followed by a single pixel move in the *
|
|
* negative x-direction. *
|
|
* *
|
|
* EN+ a one pixel move in the positive x-direction *
|
|
* followed by at least one pixel move in the negative *
|
|
* x-direction *
|
|
* *
|
|
* (E+|W+)N at least one-pixel move in the horizontal followed *
|
|
* by a single pixel move in the negative *
|
|
* y-direction. *
|
|
* *
|
|
* Algorithm *
|
|
* *
|
|
* BEGIN *
|
|
* <For each corner in the orginal path> *
|
|
* BEGIN *
|
|
* <if the corner is diagonalizable> THEN *
|
|
* *
|
|
* <just draw a single diagonal line> *
|
|
* ELSE *
|
|
* <draw both legs of the original corner> *
|
|
* END *
|
|
* *
|
|
* <Go around the path once again, merging successive *
|
|
* identical moves into single lines> *
|
|
* END *
|
|
* *
|
|
* In the code, both of these steps are done in parallel *
|
|
* *
|
|
* Further Improvements *
|
|
* *
|
|
* The output path the I generate with this algorithm will contain only *
|
|
* points that were vertices of the original axial path. A larger of *
|
|
* regular expressions could be searched for if I were willing to *
|
|
* consider using new vertices for the output path. For example *
|
|
* the regular exprssios N+WN and S+ES describe two "chicane turns" that *
|
|
* can be diagonalized. The price to be paid is the a more complex *
|
|
* code path. *
|
|
* *
|
|
\**************************************************************************/
|
|
|
|
#include "precomp.hxx"
|
|
|
|
/******************************Public*Routine******************************\
|
|
* RTP_PATHMEMOBJ::bDiagonalize *
|
|
* *
|
|
* Produces a diagonalized path that is pixel equivalent. *
|
|
* *
|
|
* Assumptions *
|
|
* *
|
|
* 0. *this is the original path which will not be changed. *
|
|
* 1. All points on the path lie on integers *
|
|
* 2. All subpaths have the inside on the left *
|
|
* 3. All subpaths are closed *
|
|
* *
|
|
* History: *
|
|
* Mon 13-Sep-1993 15:53:50 by Kirk Olynyk [kirko] *
|
|
* Wrote it. *
|
|
\**************************************************************************/
|
|
|
|
BOOL RTP_PATHMEMOBJ::bDiagonalizePath(EPATHOBJ* pepoOut_)
|
|
{
|
|
pepoOut = pepoOut_;
|
|
bMoreToEnum = TRUE;
|
|
vEnumStart();
|
|
while (bFetchSubPath())
|
|
{
|
|
if (!bDiagonalizeSubPath())
|
|
{
|
|
return(FALSE);
|
|
}
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* RTP_PATHMEMOBJ::bFetchSubPath *
|
|
* *
|
|
* History: *
|
|
* Wed 15-Sep-1993 14:19:14 by Kirk Olynyk [kirko] *
|
|
* Wrote it. *
|
|
\**************************************************************************/
|
|
|
|
BOOL RTP_PATHMEMOBJ::bFetchSubPath()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
|
|
if (bMoreToEnum) {
|
|
|
|
// first we whiz on by any empty subpaths
|
|
|
|
do {
|
|
bMoreToEnum = bEnum(&pd);
|
|
} while ((pd.count == 0) && (bMoreToEnum));
|
|
|
|
if (pd.count && (pd.flags & PD_BEGINSUBPATH) && pd.pptfx) {
|
|
// record the first point in the sub-path, we will need it later
|
|
// when dealing with the last corner in the path
|
|
|
|
ptfxFirst = *(pd.pptfx);
|
|
bRet = TRUE;
|
|
}
|
|
else
|
|
WARNING("RTP_PATHMEMOBJ::bFetchSubPath -- bad SubPath\n");
|
|
}
|
|
return(bRet);
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* RTP_PATHMEMOBJ::bWritePoint *
|
|
* *
|
|
* This routine takes as input a candidate point for writing. However *
|
|
* this routine is smart in that it analyzes the stream of candidate *
|
|
* points looking for consecutive sub-sets of points that all lie on the *
|
|
* same line. When such a case is recognized, then only the endpoints of *
|
|
* the interpolating line are actually added to the output path. *
|
|
* *
|
|
* I do not go to a great deal of trouble to determine if a candidate *
|
|
* point is on a line. All that I do is to see if the vector increment *
|
|
* to the new point is the same as the increment between prior points *
|
|
* in the input path. *
|
|
* *
|
|
* History: *
|
|
* Mon 13-Sep-1993 15:53:35 by Kirk Olynyk [kirko] *
|
|
* Wrote it. *
|
|
\**************************************************************************/
|
|
|
|
BOOL RTP_PATHMEMOBJ::bWritePoint()
|
|
{
|
|
POINTFIX ptfxNewAB;
|
|
BOOL bRet = TRUE;
|
|
int jA = j;
|
|
|
|
if (cPoints == 2)
|
|
{
|
|
ptfxNewAB.x = aptfx[jA].x - aptfxWrite[1].x;
|
|
ptfxNewAB.y = aptfx[jA].y - aptfxWrite[1].y;
|
|
if (ptfxNewAB.x != ptfxAB.x || ptfxNewAB.y != ptfxAB.y)
|
|
{
|
|
if (!(bRet = pepoOut->bPolyLineTo(aptfxWrite,1)))
|
|
{
|
|
WARNING((
|
|
"pepoOut->bPolyLineTo(aptfxWrite,1) failed when"
|
|
" called from RTP_PATHMEMOBJ::bWritePoint()\n"
|
|
));
|
|
}
|
|
else
|
|
{
|
|
aptfxWrite[0] = aptfxWrite[1];
|
|
ptfxAB = ptfxNewAB;
|
|
}
|
|
}
|
|
aptfxWrite[1] = aptfx[jA];
|
|
}
|
|
else if (cPoints == 0)
|
|
{
|
|
aptfxWrite[0] = aptfx[jA];
|
|
cPoints += 1;
|
|
}
|
|
else if (cPoints == 1)
|
|
{
|
|
aptfxWrite[1] = aptfx[jA];
|
|
ptfxAB.x = aptfxWrite[1].x - aptfxWrite[0].x;
|
|
ptfxAB.y = aptfxWrite[1].y - aptfxWrite[0].y;
|
|
cPoints += 1;
|
|
}
|
|
else
|
|
{
|
|
RIP("RTP_PATHMEMOBJ::bWritePoint -- bad cPoints\n");
|
|
bRet = FALSE;
|
|
}
|
|
return(bRet);
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* bFetchNextPoint ... in sub-path *
|
|
* *
|
|
* History: *
|
|
* Tue 14-Sep-1993 14:13:01 by Kirk Olynyk [kirko] *
|
|
* Wrote it. *
|
|
\**************************************************************************/
|
|
|
|
BOOL RTP_PATHMEMOBJ::bFetchNextPoint()
|
|
{
|
|
#define TRUE_BIT 1
|
|
#define DONE_BIT 2
|
|
|
|
int jold;
|
|
int flag = TRUE_BIT;
|
|
|
|
// advance the corner buffer along the path
|
|
// jold points to the stale member of the corner buffer. This is
|
|
// where we will store the new point in the path
|
|
|
|
jold = j;
|
|
j++;
|
|
if (j > 2)
|
|
{
|
|
j -= 3;
|
|
}
|
|
if (pd.count == 0)
|
|
{
|
|
// there are no points left in the current batch.
|
|
|
|
if (pd.flags & PD_ENDSUBPATH)
|
|
{
|
|
// If the PD_ENDSUBPATH flag was set, then we must add
|
|
// into this path the first point in the subpath. This
|
|
// is done so that later on, we can examine the last
|
|
// corner which, of course, contains the first point.
|
|
|
|
afl[jold] = 0;
|
|
aptfx[jold] = ptfxFirst; // close the path
|
|
pd.count -= 1;
|
|
flag = DONE_BIT | TRUE_BIT;
|
|
}
|
|
else
|
|
{
|
|
ASSERTGDI(
|
|
bMoreToEnum,
|
|
"RTP_PATHMEMOBJ::bFetchNextPoint() -- bMoreToEnum == FALSE\n"
|
|
);
|
|
|
|
// If you get to here, you have exhauseted the current batch of
|
|
// points, but there are more points left to be fetched for the
|
|
// current subpath. This means that we will have to make another
|
|
// call to bEnum()
|
|
|
|
bMoreToEnum = bEnum(&pd);
|
|
|
|
// At this point I check to make sure that the returned batch makes
|
|
// sense
|
|
|
|
if (!(pd.count > 0 && ((pd.flags & PD_BEGINSUBPATH) == 0) && pd.pptfx))
|
|
{
|
|
WARNING("RTP_PATHMEMOBJ::bFetchNextPoint -- bad pd\n");
|
|
flag = DONE_BIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(flag & DONE_BIT))
|
|
{
|
|
if ((LONG) pd.count > 0)
|
|
{
|
|
aptfx[jold] = *(pd.pptfx);
|
|
if (pd.count == 1 && (pd.flags & PD_ENDSUBPATH))
|
|
{
|
|
afl[jold] = RTP_LAST_POINT;
|
|
}
|
|
else
|
|
{
|
|
afl[jold] = 0;
|
|
}
|
|
pd.pptfx += 1;
|
|
pd.count -= 1;
|
|
}
|
|
else
|
|
{
|
|
ASSERTGDI(
|
|
(LONG) pd.count > -3,
|
|
"RTP_PATHMEMOBJ::bFetchNextPoint -- pd.count < -2\n"
|
|
);
|
|
}
|
|
}
|
|
return((BOOL) flag & TRUE_BIT);
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* RTP_PATHMEMOBJ::bDiagonalizeSubPath *
|
|
* *
|
|
* History: *
|
|
* Tue 14-Sep-1993 12:47:49 by Kirk Olynyk [kirko] *
|
|
* Wrote it. *
|
|
\**************************************************************************/
|
|
|
|
#define ROTATE_BACKWARD(x,y,z) {int ttt = x; x = z; z = y; y = ttt;}
|
|
#define ROTATE_FORWARD(x,y,z) {int ttt = x; x = y; y = z; z = ttt;}
|
|
|
|
BOOL RTP_PATHMEMOBJ::bDiagonalizeSubPath()
|
|
{
|
|
FIX fxAB; // length of the first leg
|
|
FIX fxBC; // length of the second leg
|
|
int bH; // set to 1 if second leg is horizontal
|
|
int jA,jB,jC;
|
|
register BOOL bRet = TRUE; // if FALSE then return immediately
|
|
// otherwise keep processing.
|
|
|
|
cPoints = 0; // no points so far in the write buffer
|
|
j = 0; // set the start of the circular buffer
|
|
|
|
// Fill the circular buffer with the first three points of the
|
|
// path. The three member buffer, defines two successive lines, or
|
|
// one corner (the path is guaranteed to be composed of alternating
|
|
// lines along the x-axis and y-axis). I shall label the three vertices
|
|
// of the corner A,B, and C. The point A always resides at ax[j],
|
|
// point B resides at ax[iMod3[j+1]], and point C resides at
|
|
// ax[iMod3[j+2]] where j can have one of the values 0, 1, 2.
|
|
|
|
if (bRet = bFetchNextPoint() && bFetchNextPoint() && bFetchNextPoint())
|
|
{
|
|
ASSERTGDI(j == 0,"RTP_PATHMEMOBJ::bDiagonalizeSubPath() -- j != 0\n");
|
|
|
|
// bH ::= <is the second leg of the corner horizontal?>
|
|
//
|
|
// if the second leg of the corner is horizontal set bH=1 otherwise
|
|
// set bH=0. Calculate the length of the first leg of the corner
|
|
// and save it in fxAB. Note that I do not need to use the iMod3
|
|
// modulus operation since j==0.
|
|
|
|
if (aptfx[2].y == aptfx[1].y)
|
|
{
|
|
bH = 1;
|
|
fxAB = aptfx[1].y - aptfx[0].y;
|
|
}
|
|
else
|
|
{
|
|
bH = 0;
|
|
fxAB = aptfx[1].x - aptfx[0].x;
|
|
}
|
|
|
|
// Start a new subpath at the first point of the subpath.
|
|
|
|
bRet = pepoOut->bMoveTo(aptfx);
|
|
|
|
jA = 0;
|
|
jB = 1;
|
|
jC = 2;
|
|
}
|
|
|
|
while (bRet)
|
|
{
|
|
#if DBG
|
|
if (!(afl[jA] & RTP_LAST_POINT))
|
|
{
|
|
// Assert that the the legs of the corner are along
|
|
// the axes, and that the two legs are mutually
|
|
// orthogonal
|
|
|
|
ASSERTGDI(
|
|
aptfx[jC].x == aptfx[jB].x ||
|
|
aptfx[jC].y == aptfx[jB].y,
|
|
"Bad Path :: C-B is not axial\n"
|
|
);
|
|
ASSERTGDI(
|
|
aptfx[jA].x == aptfx[jB].x ||
|
|
aptfx[jA].y == aptfx[jB].y,
|
|
"Bad Path :: B-A is not axial\n"
|
|
);
|
|
ASSERTGDI(
|
|
(aptfx[jC].x - aptfx[jB].x) *
|
|
(aptfx[jB].x - aptfx[jA].x)
|
|
+
|
|
(aptfx[jC].y - aptfx[jB].y) *
|
|
(aptfx[jB].y - aptfx[jA].y)
|
|
== 0,
|
|
"Bad Path :: B-A is not orthogonal to C-B"
|
|
);
|
|
}
|
|
#endif
|
|
// If the first vertex of the corner is the last point in the
|
|
// original subpath then we terminate the processing. This point
|
|
// has either been recorded with PATHMEMOBJ::bMoveTo or
|
|
// PATHMEMOBJ::bPolyLineTo. All that remains is to close the
|
|
// subpath which is done outside the while loop
|
|
|
|
if (afl[jA] & RTP_LAST_POINT)
|
|
break;
|
|
|
|
// There are two paths through the following if-else clause
|
|
// They are for VERTICAL-HORIZONTAL and HORIZONTAL-VERTICAL
|
|
// corners respectively. These two clauses are identical
|
|
// except for the interchange of ".x" with ".y". It might be
|
|
// a good idea to have macros or subrouines for these sections
|
|
// in order that they be guranteed to be identical.
|
|
|
|
// Is the second leg of the corner horizontal?
|
|
|
|
if (bH)
|
|
{
|
|
// Yes, the second leg of the corner is horizontal
|
|
|
|
fxBC = aptfx[jC].x - aptfx[jB].x;
|
|
|
|
// Is the corner diagonalizable?
|
|
|
|
if ((fxAB > 0) && ((fxAB == FIX_ONE) || (fxBC == -FIX_ONE)))
|
|
{
|
|
// Yes, the corner is diagonalizable
|
|
//
|
|
// If the middle of the corner was the last point in the
|
|
// original path then the last point in the output path
|
|
// is the first point in the corner. This is because the
|
|
// last line in the output path is this diagonalized
|
|
// corner which will be produced automatically by the
|
|
// CloseFigure() call after this while-loop. Thus, in
|
|
// this case we would just break out of the loop.
|
|
|
|
if (afl[jB] & RTP_LAST_POINT)
|
|
break;
|
|
|
|
// The corner is diagonalizable. This means that we are no
|
|
// longer interested in the first two points of this corner.
|
|
// We therefore fetch the next two points of the path
|
|
// an place them in our circular corner-buffer.
|
|
|
|
if (!(bRet = bFetchNextPoint() && bFetchNextPoint()))
|
|
break;
|
|
|
|
// under modulo 3 arithmetic, incrementing by 2 is
|
|
// equivalent to decrementing by 1
|
|
|
|
ROTATE_BACKWARD(jA,jB,jC);
|
|
|
|
// fxAB is set to the length of the first leg of the new
|
|
// corner.
|
|
|
|
fxAB = aptfx[jB].y - aptfx[jA].y;
|
|
}
|
|
else
|
|
{
|
|
// No, the corner is not diagonalizable
|
|
//
|
|
// The corner cannot be diagonalized. Advance the corner
|
|
// to the next point in the original path. The orientation
|
|
// of the second leg of the corner will change. The length
|
|
// of the first leg of the new corner is set equal to the
|
|
// length of the second leg of the previous corner.
|
|
|
|
if (!(bRet = bFetchNextPoint()))
|
|
break;
|
|
ROTATE_FORWARD(jA,jB,jC);
|
|
bH ^= 1;
|
|
fxAB = fxBC;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Diagonalize the HORIZONTAL->VERTICAL corner
|
|
|
|
fxBC = aptfx[jC].y - aptfx[jB].y;
|
|
if ((fxBC < 0) && ((fxAB == FIX_ONE) || (fxBC == -FIX_ONE)))
|
|
{
|
|
if (afl[jB] & RTP_LAST_POINT)
|
|
break;
|
|
if (!(bRet = bFetchNextPoint() && bFetchNextPoint()))
|
|
break;
|
|
ROTATE_BACKWARD(jA,jB,jC);
|
|
fxAB = aptfx[jB].x - aptfx[jA].x;
|
|
}
|
|
else
|
|
{
|
|
if (!(bRet = bFetchNextPoint()))
|
|
break;
|
|
ROTATE_FORWARD(jA,jB,jC);
|
|
bH ^= 1;
|
|
fxAB = fxBC;
|
|
}
|
|
}
|
|
if (!(bRet = bWritePoint()))
|
|
break;
|
|
}
|
|
|
|
if (bRet)
|
|
{
|
|
ASSERTGDI(cPoints == 2,"GDI Region To Path -- cPoints is not 2\n");
|
|
|
|
bRet = pepoOut->bPolyLineTo(aptfxWrite, 2) && pepoOut->bCloseFigure();
|
|
}
|
|
return(bRet);
|
|
}
|