// This is a simple interactive Bezier curve demo that allows the user to position the end and
// control points by dragging them with the mouse, while it updates the curve in real time.
//
// Tested only with Version 8.00.60 (Win64).
#include <windows.h>
#define TARGET_RADIUS 8
#define CLIENTWIDTH 400
#define CLIENTHEIGHT 300
// This function sizes the specified window so the client area is the specified width and height and
// optionally centers the window on the screen. Unlike AdjustWindowRect and AdjustWindowRectEx,
// this function can handle a window with the WS_OVERLAPPED style.
void SetClientSize( HWND hwnd, int pixelWidth, int pixelHeight, BOOL bCenter )
{
int x, y, w, h;
RECT rcc, rcw;
GetClientRect( hwnd, &rcc );
GetWindowRect( hwnd, &rcw );
w = (rcw.right - rcw.left) - (rcc.right - pixelWidth) - 1;
h = (rcw.bottom - rcw.top) - (rcc.bottom - pixelHeight) - 1;
if( bCenter )
{
x = (GetSystemMetrics( SM_CXSCREEN ) / 2) - w / 2;
y = (GetSystemMetrics( SM_CYSCREEN ) / 2) - h / 2;
}
else
{
x = rcw.left;
y = rcw.top;
}
MoveWindow( hwnd, x, y, w, h, TRUE );
}
void DrawTarget( HDC hdc, POINT pt, COLORREF clr )
{
HBRUSH hbr, hbrDef;
HPEN hpen, hpenDef;
hbr = GetStockObject( NULL_BRUSH );
hbrDef = SelectObject( hdc, hbr );
hpen = CreatePen( PS_SOLID, 3, clr );
hpenDef = SelectObject( hdc, hpen );
Ellipse( hdc, pt.x-TARGET_RADIUS, pt.y-TARGET_RADIUS, pt.x+TARGET_RADIUS, pt.y+TARGET_RADIUS );
SelectObject( hdc, hpenDef );
SelectObject( hdc, hbrDef );
DeleteObject( hpen );
}
int HitTestTarget( LPARAM cursorCoords, POINT pt, BOOL b2x )
{
int mx, my, px, py;
mx = LOWORD(cursorCoords);
my = HIWORD(cursorCoords);
px = pt.x;
py = pt.y;
if( b2x )
return (abs((mx-px)*(mx-px))+abs((my-py)*(my-py))<=4*(TARGET_RADIUS+1)*(TARGET_RADIUS+1));
else
return (abs((mx-px)*(mx-px))+abs((my-py)*(my-py))<=(TARGET_RADIUS+1)*(TARGET_RADIUS+1));
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
POINT pt0, pt1;
HDC hdc;
HPEN hpenDef;
RECT rc;
BOOL tooClose;
int i;
static HDC hdcMem;
static HBITMAP hbmp, hbmpDef;
static HPEN hpen0, hpen1;
// Arrays dimensioned with unused element 0 so iTarget can serve as a flag and an index.
static POINT points[5];
static COLORREF colors[5];
static int iTarget;
switch( uMsg )
{
case WM_CREATE:
SetClientSize( hwnd, CLIENTWIDTH, CLIENTHEIGHT, TRUE );
hdc = GetDC( hwnd );
hdcMem = CreateCompatibleDC( hdc );
GetClientRect( hwnd, &rc );
hbmp = CreateCompatibleBitmap( hdc, rc.right, rc.bottom );
ReleaseDC( hwnd, hdc );
hbmpDef= SelectObject( hdcMem, hbmp );
// For PolyBezier the line width is the pen width.
// One pen to draw with and one to erase with:
hpen0 = CreatePen( PS_SOLID, 2, RGB(255,255,255) );
hpen1 = CreatePen( PS_SOLID, 2, RGB(0,0,0) );
FillRect( hdcMem, &rc, GetStockObject( WHITE_BRUSH ));
// End points are blue and red, control points are cyan and magenta.
points[1].x = CLIENTWIDTH/5;
points[1].y = CLIENTHEIGHT/2;
colors[1] = RGB(0,0,255);
points[2].x = 2*CLIENTWIDTH/5;
points[2].y = CLIENTHEIGHT/2;
colors[2] = RGB(0,255,255);
points[3].x = 3*CLIENTWIDTH/5;
points[3].y = CLIENTHEIGHT/2;
colors[3] = RGB(255,0,255);
points[4].x = 4*CLIENTWIDTH/5;
points[4].y = CLIENTHEIGHT/2;
colors[4] = RGB(255,0,0);
DrawTarget( hdcMem, points[1], colors[1] );
DrawTarget( hdcMem, points[2], colors[2] );
DrawTarget( hdcMem, points[3], colors[3] );
DrawTarget( hdcMem, points[4], colors[4] );
hpenDef = SelectObject( hdcMem, hpen1 );
PolyBezier( hdcMem, &points[1], 4 );
SelectObject( hdcMem, hpenDef );
break;
case WM_LBUTTONDOWN:
// Confine the targets to the client area.
GetClientRect( hwnd, &rc );
pt0.x = rc.left+(TARGET_RADIUS+2);
pt0.y = rc.top+(TARGET_RADIUS+2);
pt1.x = rc.right-(TARGET_RADIUS+2);
pt1.y = rc.bottom-(TARGET_RADIUS+2);
ClientToScreen( hwnd, &pt0 );
ClientToScreen( hwnd, &pt1 );
SetRect( &rc, pt0.x, pt0.y, pt1.x, pt1.y );
ClipCursor( &rc );
iTarget = 0;
for( i = 1; i<=4; i++ )
if( HitTestTarget( lParam, points[i], FALSE ) ) iTarget = i;
break;
case WM_MOUSEMOVE:
if( iTarget > 0 && (wParam & MK_LBUTTON) )
{
// This to avoid having targets overlap, which could cause one to overlay the other,
// and since the mouse would then select them as a unit there would be no way to
// separate them.
tooClose = FALSE;
for( i = 1; i <= 4; i++ )
{
if( i != iTarget )
{
if( HitTestTarget( lParam, points[i], TRUE ) ) tooClose = TRUE;
}
}
if( tooClose == FALSE )
{
hpenDef = SelectObject( hdcMem, hpen0 );
PolyBezier( hdcMem, &points[1], 4 );
SelectObject( hdcMem, hpenDef );
DrawTarget( hdcMem, points[iTarget], RGB(255,255,255) );
points[iTarget].x = LOWORD(lParam);
points[iTarget].y = HIWORD(lParam);
DrawTarget( hdcMem, points[1], colors[1] );
DrawTarget( hdcMem, points[2], colors[2] );
DrawTarget( hdcMem, points[3], colors[3] );
DrawTarget( hdcMem, points[4], colors[4] );
hpenDef = SelectObject( hdcMem, hpen1 );
PolyBezier( hdcMem, &points[1], 4 );
SelectObject( hdcMem, hpenDef );
InvalidateRect( hwnd, NULL, FALSE );
}
}
break;
case WM_LBUTTONUP:
// Release the cursor.
ClipCursor( NULL );
break;
case WM_PAINT:
BeginPaint(hwnd, &ps);
BitBlt( ps.hdc, 0, 0, CLIENTWIDTH, CLIENTHEIGHT, hdcMem, 0, 0, SRCCOPY );
EndPaint(hwnd, &ps);
break;
case WM_COMMAND:
// This allows the user to close the window by pressing the Escape key.
// This behavior depends on the message loop calling IsDialogMessage.
if( wParam == IDCANCEL ) DestroyWindow(hwnd);
break;
case WM_CLOSE:
DestroyWindow( hwnd );
case WM_DESTROY:
SelectObject( hdcMem, hbmpDef );
DeleteObject( hbmp );
DeleteObject( hpen0 );
DeleteObject( hpen1 );
DeleteDC( hdcMem );
PostQuitMessage( 0 );
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return (LRESULT) NULL;
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
int w,h,x,y;
HWND hwnd;
WNDCLASSEX wcx;
MSG msg;
wcx.cbSize = sizeof(wcx);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = WindowProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.hInstance = hInstance;
wcx.hIcon = LoadIcon(NULL,IDI_APPLICATION);
wcx.hCursor = LoadCursor(NULL,IDC_ARROW);
wcx.hbrBackground = GetStockObject(WHITE_BRUSH);
wcx.lpszMenuName = 0;
wcx.lpszClassName = "BezierDemoClass";
wcx.hIconSm = LoadImage( hInstance,MAKEINTRESOURCE(5), IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR );
RegisterClassEx(&wcx);
w = 800;
h = 600;
x = (GetSystemMetrics( SM_CXSCREEN ) - w)/2;
y = (GetSystemMetrics( SM_CYSCREEN ) - h)/2;
hwnd = CreateWindowEx( 0, "BezierDemoClass", "Bezier Demo", WS_OVERLAPPED | WS_SYSMENU,
x, y, w, h, NULL, NULL, NULL, NULL );
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while( GetMessage( &msg, hwnd, 0, 0 ) > 0 )
{
if( IsDialogMessage( hwnd, &msg ) == 0 )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
exit(0);
}
Edit: The abs() calls are not necessary.