home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
pctchnqs
/
1991
/
number6
/
bezier
/
bez.c
< prev
next >
Wrap
Text File
|
1991-11-11
|
15KB
|
397 lines
/* BEZ.C : Program to draw Bezier curves and their handles
interactively. User draws first handle by dragging, then second
handle; the Bezier curve rubber-bands together with the second
handle. Demonstrates the de Casteljau algorithm for fast
calculation of Bezier points.
Copyright (c) 1991, Michael A. Bertrand. */
#include <windows.h>
#include "bez.h"
HPEN hRedPen; /* red pen for handles. */
int LogPerDevice; /* #logical units per device unit
(both axes). */
WORD cxClient; /* size of client area (x). */
WORD cyClient; /* size of client area (y). */
HANDLE hInst; /* current instance */
POINT BezPts[NUM_BEZPTS]; /* array of pts along Bezier curve */
POINT *PtrBezPts; /* pointer into BezPts[] array */
char Instr1[] =
"* Left Button down, drag, button up for 1st handle.";
char Instr2[] =
"* Left Button down, drag, button up for 2nd handle and Bez.";
char Instr3[] = "* Right click to clear window.";
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
/*
USE: Register window and set dispach message loop.
IN: hInstance,hPrevInstance,lpszCmdLine,nCmdShow : standard WinMain parms
*/
{
static char szAppName [] = "Bezier";
static char szIconName[] = "BezIcon";
static char szMenuName[] = "BezMenu";
HWND hWnd; /* handle to WinMain's window */
MSG msg; /* message dispached to window */
WNDCLASS wc; /* for registering window */
/* Save instance handle in global var
so can use for "About" dialog box. */
hInst = hInstance;
/* Register application window class. */
if (!hPrevInstance)
{
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc; /* fn to get window's messages */
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, szIconName);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = szMenuName; /* menu resource in RC file */
wc.lpszClassName = szAppName; /* name used in call to CreateWindow() */
if (!RegisterClass(&wc))
return(FALSE);
}
/* Initialize specific instance. */
hWnd = CreateWindow(szAppName, szAppName, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow); /* display the window */
UpdateWindow(hWnd); /* update client area; send WM_PAINT */
/* Read msgs from app que and dispatch them to appropriate win
function. Continues until GetMessage() returns NULL when it
receives WM_QUIT. */
while (GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg); /* process char input from keyboard */
DispatchMessage(&msg); /* pass message to window function */
}
return(msg.wParam);
}
long FAR PASCAL WndProc(HWND hWnd, unsigned iMessage,
WORD wParam, LONG lParam)
/*
USE: Application's window procedure : all app's messages come here.
IN: hWnd,iMessage,wParam,lParam : standard Windows proc parameters
*/
{
HDC hDC; /* must generate our own handle to DC to draw */
PAINTSTRUCT ps; /* needed when receive WM_PAINT message */
FARPROC lpProcAbout; /* pointer to "AboutBez" function */
switch(iMessage)
{
case WM_CREATE:
/* Create hRedPen once and store as global. */
hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
break; /* WM_CREATE */
case WM_SIZE:
/* Get client area size into globals when window resized. */
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
break; /* WM_SIZE */
case WM_COMMAND:
if (wParam == IDM_ABOUT)
{
/* "About" menu item chosen by user :
call "AboutBez" function. */
lpProcAbout = MakeProcInstance(AboutBez, hInst);
DialogBox (hInst, "AboutBez", hWnd, lpProcAbout);
FreeProcInstance(lpProcAbout);
}
break; /* WM_COMMAND */
case WM_PAINT:
/* Repaint instructions at upper left of window. */
hDC = BeginPaint(hWnd, &ps);
SelectObject(hDC, GetStockObject(ANSI_VAR_FONT));
TextOut(hDC, 0, 0, Instr1, lstrlen(Instr1));
TextOut(hDC, 0, 15, Instr2, lstrlen(Instr2));
TextOut(hDC, 0, 30, Instr3, lstrlen(Instr3));
EndPaint(hWnd, &ps);
break; /* WM_PAINT */
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MOUSEMOVE:
case WM_LBUTTONUP:
/* Mouse events passed on to BezTool() for processing. */
BezTool(hWnd, iMessage, lParam);
break; /* WM_LBUTTONDOWN... */
case WM_DESTROY:
/* Destroy window & delete pen when application terminated. */
DeleteObject(hRedPen);
PostQuitMessage(0);
break; /* WM_DESTROY */
default:
return(DefWindowProc(hWnd, iMessage, wParam, lParam));
} /* switch(iMessage) */
return(0L);
}
void NEAR PASCAL BezTool(HWND hWnd, unsigned iMessage, LONG lParam)
/*
USE: Process mouse event to draw handles and Bezier curve.
IN: hWnd : handle to window
iMessage : mouse event (WM_LBUTTONDOWN, etc.)
lParam : mouse coords (x == loword, y == hiword)
NOTE: This is the interactive Bezier drawing tool which processes
WM_RBUTTONDOWN, WM_LBUTTONDOWN, WM_MOUSEMOVE, and WM_LBUTTONUP
messages. BezTool() is called repeatedly as the user draws. The
current state of the tool is maintained in the key static variable
iState. iState's value, as set last time thru the tool, determines
the tool's action this time thru. Bezier control and handle points,
as input by the user, are also maintained as statics so BezTool()
remembers them the next time thru.
*/
{
HDC hDC; /* must generate our own handle to DC to draw */
WORD maxClient; /* larger of (cxClient, cyClient) */
POINT inPt; /* incoming point */
POINT pts[2]; /* to get LogPerDevice, #logical units/dev. unit */
/* user-entered Bez control & handle (1st): */
static POINT ctrl1, hand1;
/* user-entered Bez control & handle (2nd): */
static POINT ctrl2, hand2;
static int iState; /* BezTool()'s state : DRAG_HAND1, etc. */
hDC = GetDC(hWnd);
/* Set extents and origin so will be working
in range [-15000, +15000]. */
SetMapMode(hDC, MM_ISOTROPIC);
SetWindowExt(hDC, 30000, 30000);
maxClient = (cxClient > cyClient) ? cxClient : cyClient;
SetViewportExt(hDC, maxClient, -maxClient);
SetViewportOrg(hDC, cxClient >> 1, cyClient >> 1);
/* Calculate #logical units per device unit --
will need later when draw little 3x3 boxes in DrawHandle(). */
pts[0].x = pts[0].y = 0;
pts[1].x = pts[1].y = 1;
DPtoLP(hDC, pts, 2);
LogPerDevice = (pts[1].x > pts[0].x) ? (pts[1].x - pts[0].x) :
(pts[0].x - pts[1].x);
/* Incoming point in device coordinates. */
inPt.x = LOWORD(lParam);
inPt.y = HIWORD(lParam);
/* Convert to logical coordinates. */
DPtoLP(hDC, &inPt, 1);
switch(iMessage)
{
case WM_RBUTTONDOWN:
/* Erase client area if not in middle of Bez. */
if (iState == NOT_STARTED)
InvalidateRect(hWnd, NULL, TRUE);
break; /* WM_RBUTTONDOWN */
case WM_LBUTTONDOWN:
switch(iState)
{
case NOT_STARTED:
iState = DRAG_HAND1; /* starting drag */
hand1.x = ctrl1.x = inPt.x; /* store user point
hand1.y = ctrl1.y = inPt.y; in statics */
break; /* NOT_STARTED */
case WAIT_FOR_CTRL2:
iState = DRAG_HAND2; /* starting drag */
hand2.x = ctrl2.x = inPt.x; /* store user point
hand2.y = ctrl2.y = inPt.y; in statics */
SetROP2(hDC, R2_NOTXORPEN); /* draw in XOR */
DrawBez(hDC, ctrl1, hand1, hand2, ctrl2);
break; /* NOT_STARTED */
} /* switch(iState) */
break; /* WM_LBUTTONDOWN */
case WM_MOUSEMOVE:
switch(iState)
{
case DRAG_HAND1:
SetROP2(hDC, R2_NOTXORPEN); /* draw in XOR */
DrawHandle(hDC, ctrl1, hand1); /* erase old */
hand1.x = inPt.x; /* get new handle */
hand1.y = inPt.y;
DrawHandle(hDC, ctrl1, hand1); /* draw new */
break; /* DRAG_HAND1 */
case DRAG_HAND2:
SetROP2(hDC, R2_NOTXORPEN); /* draw in XOR */
DrawHandle(hDC, ctrl2, hand2); /* erase old */
DrawBez(hDC, ctrl1, hand1, hand2, ctrl2);
hand2.x = inPt.x; /* get new handle */
hand2.y = inPt.y;
DrawHandle(hDC, ctrl2, hand2); /* draw new */
DrawBez(hDC, ctrl1, hand1, hand2, ctrl2);
break; /* DRAG_HAND1 */
} /* switch(iState) */
break; /* WM_MOUSEMOVE */
case WM_LBUTTONUP:
switch(iState)
{
case DRAG_HAND1:
iState = WAIT_FOR_CTRL2;
SetROP2(hDC, R2_COPYPEN); /* COPY pen for final handle */
DrawHandle(hDC, ctrl1, hand1); /* draw in COPY mode */
break; /* DRAG_HAND1 */
case DRAG_HAND2:
iState = NOT_STARTED;
SetROP2(hDC, R2_COPYPEN); /* COPY pen for final handle */
DrawHandle(hDC, ctrl2, hand2); /* draw in COPY mode */
DrawBez(hDC, ctrl1, hand1, hand2, ctrl2);
break; /* DRAG_HAND2 */
} /* switch(iState) */
break; /* WM_LBUTTONUP */
} /* switch(iMessage) */
ReleaseDC(hWnd, hDC);
}
BOOL FAR PASCAL AboutBez(HWND hDlg, unsigned iMessage,
WORD wParam, LONG lParam)
/*
USE: Application's "About" dialog box function.
IN: hDlg : handle to dialog box
iMessage : message type
wParam : auxiliary message info (act on IDOK, IDCANCEL)
lParam : unused
RET: Return TRUE if processed appropriate message, FALSE otherwise.
NOTE: Closes "About" box only when user clicks OK button
or system close. */
{
switch (iMessage)
{
case WM_INITDIALOG: /* initialize dialog box */
return (TRUE);
case WM_COMMAND: /* received a command */
/* IDOK if OK box selected; IDCANCEL if system menu close command */
if (wParam == IDOK || wParam == IDCANCEL)
{
EndDialog(hDlg, TRUE); /* exit dialog box */
return(TRUE); /* did proccess message */
}
break; /* WM_COMMAND */
} /* switch (iMessage) */
return (FALSE); /* did not process message */
}
void NEAR PASCAL DrawBez(HDC hDC, POINT ctrl1, POINT hand1,
POINT hand2, POINT ctrl2)
/*
USE: Draw Bezier curve given control and handle points.
IN: ctrl1,hand1,hand2,ctrl2 : control and handle points for Bezier
NOTE: Set up, then call SubDivideBez(), the recursive de Casteljau
routine, generate points along the Bez. Windows' Polyline() displays
the Bez as a polygon. BEZ_DEPTH = recursive depth of de Casteljau.
Initial POINT ctrl1 loaded here, then recursive routine calculates
and loads the remaining 2^BEZ_DEPTH = (NUM_BEZPTS - 1) de Casteljau
pts. */
{
PtrBezPts = BezPts; /* init ptr to start of array */
*PtrBezPts++ = ctrl1; /* first control point special case */
SubDivideBez(ctrl1, hand1, hand2, ctrl2, BEZ_DEPTH); /* calc pts */
Polyline(hDC, BezPts, NUM_BEZPTS); /* call Windows to draw */
}
void NEAR PASCAL SubDivideBez(POINT p0, POINT p1,
POINT p2, POINT p3, int depth)
/*
USE: Calculate de Casteljau construction points and break Bez
in two.
IN: p0,p1,p2,p3 : control/handle/handle/control for Bez to
subdivide depth: current recursive depth of algorithm.
NOTE: Calculates the de Casteljau construction points so the
Bezier can be subdivided into 2 parts (left, then right) by
recursive calls to this routine. Recursion is broken off when
depth, decremented once for each recursion level, becomes 0.
This is the finest level of subdivision; the right-most point on
the small subdivided Bezier is also a point on the original
Bezier, so we load it into global array BezPts[] (thru PtrBezPts
which points into the array). */
{
/* de Casteljau construction points: */
POINT q0, q1, q2, r0, r1, s0;
/* depth == 0 means we are at the finest subdivision level:
grab point into global array and return, breaking off recursion. */
if (!depth)
{
*PtrBezPts++ = p3;
return;
}
/* Calculate de Casteljau construction points as averages of
previous points (ie., midway points); note shift right is
fast division by 2. */
/* q's are midway between 4 incoming control and handle points. */
q0.x = (p0.x + p1.x) >> 1; q0.y = (p0.y + p1.y) >> 1;
q1.x = (p1.x + p2.x) >> 1; q1.y = (p1.y + p2.y) >> 1;
q2.x = (p2.x + p3.x) >> 1; q2.y = (p2.y + p3.y) >> 1;
/* r's are midway between 3 q's. */
r0.x = (q0.x + q1.x) >> 1; r0.y = (q0.y + q1.y) >> 1;
r1.x = (q1.x + q2.x) >> 1; r1.y = (q1.y + q2.y) >> 1;
/* s0 is midway between 2 r's and is in middle of original Bez. */
s0.x = (r0.x + r1.x) >> 1; s0.y = (r0.y + r1.y) >> 1;
/* Decrement depth; subdivide incoming Bez into 2 parts:
left, then right.*/
SubDivideBez(p0, q0, r0, s0, --depth);
SubDivideBez(s0, r1, q2, p3, depth);
}
void NEAR PASCAL DrawHandle(HDC hDC, POINT p, POINT q)
/*
USE: Draws handle on screen from p to q with hRedPen.
IN: hDC : handle to display context
p,q : handle start and end points
NOTE: Don't CreatePen or delete--these are done globally once only.
Handles are drawn with little 3x3 pixel boxes at each end.
Each pixel is LogPerDevice logical units; logical units must be
used for the boxes since we are in MM_ISOTROPIC mapping mode.
*/
{
HPEN origPen; /* DC's orinal pen */
int xLeft; /* left coord of little box at end of handle */
int xRight; /* right coord of little box */
int y; /* y coord of little box */
/* Save original pen, select red pen. */
origPen = SelectObject(hDC, hRedPen);
/* Draw handle. */
MoveTo(hDC, p.x, p.y); LineTo(hDC, q.x, q.y);
/* Set left and right coords around q.x (3 pixels). Remember
Windows lines do not draw last pixel. */
xLeft = q.x - LogPerDevice;
xRight = q.x + (LogPerDevice << 1);
/* Init y coord 1 pixel below q.y. */
y = q.y - LogPerDevice;
/* Draw little box : 3x3 pixels. */
MoveTo(hDC, xLeft, y); LineTo(hDC, xRight, y); y += LogPerDevice;
MoveTo(hDC, xLeft, y); LineTo(hDC, xRight, y); y += LogPerDevice;
MoveTo(hDC, xLeft, y); LineTo(hDC, xRight, y); y += LogPerDevice;
/* Re-select original pen. */
SelectObject(hDC, origPen);
}