[Win32]曲线绘制

来源:互联网 发布:华泰证券交易软件ipad 编辑:程序博客网 时间:2024/05/26 12:02

1. 用直线拟合正弦曲线以及矩形、椭圆线等的绘制:

// sinewave_linedemo.c#include <windows.h>#include <math.h>#define NUM1000#define DPI( 2 * 3.1415926 )POINTapt[NUM]; // 用1000个点来拟合一个周期的sinLRESULT CALLBACK WndPorc( HWND hWnd, UINT message, WPARAM wPram, LPARAM lParam );int WINAPI WinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpszCmdLine,intnCmdShow){static TCHARszAppName[] = TEXT("sinewave_linedemo");WNDCLASSwndclass;HWNDhWnd;MSGmsg;wndclass.style= CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc= WndPorc;wndclass.cbClsExtra= 0;wndclass.cbWndExtra= 0;wndclass.hInstance= hInstance;wndclass.hIcon= LoadIcon( NULL, IDI_APPLICATION );wndclass.hCursor= LoadCursor( NULL, IDC_ARROW );wndclass.hbrBackground= (HBRUSH)GetStockObject( WHITE_BRUSH );wndclass.lpszMenuName= NULL;wndclass.lpszClassName= szAppName;RegisterClass( &wndclass );hWnd = CreateWindow(szAppName,TEXT("Sine Wave & Line Demo"),WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, // 上半部显示正弦波下半部显示各种曲线,因此窗口开到最大CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL);ShowWindow( hWnd, SW_MAXIMIZE );UpdateWindow( hWnd );while ( GetMessage( &msg, NULL, 0, 0 ) ){TranslateMessage( &msg );DispatchMessage( &msg );}return msg.wParam;}LRESULT CALLBACK WndPorc( HWND hWnd, UINT message, WPARAM wPram, LPARAM lParam ){static intcxClient, cyClient;HDChDC;PAINTSTRUCTps;inti;switch ( message){case WM_SIZE:cxClient = LOWORD( lParam );cyClient = HIWORD( lParam );return 0;case WM_PAINT:hDC = BeginPaint( hWnd, &ps );// 在任何时候环境设备都有一个当前位置,即一个点// 很多画线函数都是以该点作为起始点进行画线的// 先画一条横的sin主轴MoveToEx( hDC, 0, cyClient / 4, NULL ); // 将设备当前位置跟新到第二个和第三个参数指定的点// 第三个参数是POINT指针// 函数将老的(之前的)当前位置保存在里面LineTo( hDC, cxClient, cyClient / 4 ); // 以设备当前位置作为起始点画直线   // 终点即为第二、三个参数指定的点   // 凡是后缀为To的绘画函数绘画的起始点都是基于设备当前位置的!for ( i = 0; i < NUM; i++ ) // 设定sin一个周期中1000个点的拟合值,要位于上半部窗口{apt[i].x = i * cxClient / NUM;apt[i].y = (int)( cyClient / 4 * ( 1 - sin( DPI * i / NUM ) ) );}Polyline( hDC, apt, NUM ); // 画一条由多条直线首尾相接而构成的折线(由于点的间距小拟合曲线效果好)   // 其后缀不带To,表示该函数不使用也不改变设备当前位置MoveToEx( hDC, 0, cyClient / 2, NULL ); // 再将窗口对半划,上半部已经画好了正弦波LineTo( hDC, cxClient, cyClient / 2 );// 再画一矩形,2/3参数为矩形左上角坐标,接下来两个是右下角坐标// 但是该函数不是单纯的画线函数,而是画线填充函数,即先画出矩形轮廓线然后再将内部用// hbrBackground指定的画刷填充Rectangle( hDC, cxClient / 8, cyClient * 9 / 16, cxClient * 7 / 8, cyClient * 15 / 16 );// 下半部用直线打个对角线×MoveToEx( hDC, 0, cyClient / 2, NULL );LineTo( hDC, cxClient, cyClient );MoveToEx( hDC, cxClient, cyClient / 2, NULL );LineTo( hDC, 0, cyClient );// 画一个椭圆,参数是椭圆外接矩形的左上角和右下角坐标// 也是画线填充函数,会将之前画的×在椭圆内部的部分用默认的白画刷覆盖Ellipse( hDC, cxClient / 8, cyClient * 9 / 16, cxClient * 7 / 8, cyClient * 15 / 16 );// 画圆角矩形// hDC后的四个参数即外接矩形的左上角和右下角// 由于Windows使用椭圆来实现圆角的// 因此最后两个参数就是角上的椭圆的宽和高// 如果椭圆的宽和高不同,则圆角的水平部分和垂直部分的弧度不一不美观// 因此通常将圆角的宽和高设成一样显得更加美观// 当圆角的宽高和矩形的宽高对应相等时画出来的就是椭圆!// 该函数也是画线填充函数RoundRect( hDC, cxClient / 4, cyClient * 5 / 8, cxClient * 3 / 4, cyClient * 7 / 8,cyClient / 8, cyClient / 8 );EndPaint( hWnd, &ps );return 0;case WM_DESTROY:PostQuitMessage( 0 );return 0;}return DefWindowProc( hWnd, message, wPram, lParam );}

2. 对以上程序的几点补充说明:

    1) 默认设备环境中点(0, 0)为最初设定的当前位置;

    2) 可以通过函数BOOL GetCurrentPositionEX( HDC hdc, LPPOINT &lpPoint );来获取当前位置,lpPoint为输出参数;

    3) 画矩形、椭圆、圆角矩形的时候,外接矩形总是默认和坐标轴平行,不可以斜画;

    4) 总结画线函数以及画线填充函数:

         画线函数:只绘制曲线,一般绘制的曲线不会形成封闭区域,因此无需填充,主要有7个:

            LineTo,画直线

            PolyLine、PolylineTo,画由一条或多条首尾相连的直线构成的折线

            PolyPolyline,画多条折线

            Arc、ArcTo,画椭圆弧线

            PolyBezier、PolyBezierTo,画贝塞尔样条曲线

            PolyDraw,画多条贝塞尔样条曲线或者一条Polyline

         画线填充函数:绘制出来的曲线会形成一块封闭区域,因此会对该封闭区域进行填充,主要有5个:

            Rectangle,画矩形

            Ellipse,画椭圆

            RoundRect,画圆角矩形

            Pie,画椭圆上的扇形

            Chord,画椭圆上的由弦分割出来的弓形区域





3. 贝塞尔样条曲线:

主要使用的函数为:

BOOL PolyBezier( HDC hdc, CONST POINT * lppt, DWORD cPoints );

BOOL PolyBezierTo( HDC hdc, CONST POINT *lppt, DWORD cPoints );

一条Bezier曲线包含四个重要的点,两个端点和两个控点,点的信息有Point数组作为第二个参数传入,函数解析时是按照第一个端点、第一个端点的控点、第二个端点的控点、第二个端点的顺序解析的,第一个函数不改变当前位置,因此cPoints必须是1 + 3的正整数倍,因为第一条曲线的终点将作为第二条曲线的起点,而第二个函数的cPoiints只要为3的正整数倍就行了,第一条曲线的起点默认为设备环境当前位置;

Bezier样条曲线的特点就是控点和端点的线段和曲线本身相切,并且控点就好像磁铁一样把曲线从两个端点间的直线吸弯;

// bezier.c#include <windows.h>LRESULT CALLBACK WndPorc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );int WINAPI WinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpszCmdLine,intnCmdShow){static TCHARszAppName[] = TEXT("bezier");WNDCLASSwndclass;HWNDhWnd;MSGmsg;wndclass.style= CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc= WndPorc;wndclass.cbClsExtra= 0;wndclass.cbWndExtra= 0;wndclass.hInstance= hInstance;wndclass.hIcon= LoadIcon( NULL, IDI_APPLICATION );wndclass.hCursor= LoadCursor( NULL, IDC_ARROW );wndclass.hbrBackground= (HBRUSH)GetStockObject( WHITE_BRUSH );wndclass.lpszMenuName= NULL;wndclass.lpszClassName= szAppName;RegisterClass( &wndclass );hWnd = CreateWindow(szAppName,TEXT("Bezier Splines"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL );ShowWindow( hWnd, nCmdShow );UpdateWindow( hWnd );while ( GetMessage( &msg, NULL, 0, 0 ) ){TranslateMessage( &msg );DispatchMessage( &msg );}return msg.wParam;}void DrawBezier( HDC hdc, POINT apt[] ){PolyBezier( hdc, apt, 4 );MoveToEx( hdc, apt[0].x, apt[0].y, NULL );LineTo( hdc, apt[1].x, apt[1].y );MoveToEx( hdc, apt[2].x, apt[2].y, NULL );LineTo( hdc, apt[3].x, apt[3].y );}LRESULT CALLBACK WndPorc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ){static POINTapt[4];HDChdc;PAINTSTRUCTps;intcxClient, cyClient;switch ( message ){case WM_SIZE:cxClient = LOWORD( lParam );cyClient = HIWORD( lParam );apt[0].x = cxClient / 4; // 第一个端点apt[0].y = cyClient / 2;apt[1].x = cxClient / 2; // 第一个控点apt[1].y = cyClient / 4;apt[2].x = cxClient / 2; // 第二个控点apt[2].y = 3 * cyClient / 4;apt[3].x = 3 * cxClient / 4; // 第二个端点apt[3].y = cyClient / 2;return 0;case WM_LBUTTONDOWN: // !关于鼠标消息的情况之后会讲到case WM_RBUTTONDOWN:case WM_MOUSEMOVE:if ( wParam & MK_LBUTTON || wParam & MK_RBUTTON ){// 分几种情况讨论:// 鼠标的左键或右键按下,此时需要改变控点位置,将曲线重画// 或者是鼠标在移动,但是鼠标键没有按下,此时跳出if语句什么都不做// 或者是鼠标在移动,并且鼠标键也按下了,此时就是拖动   // 因此曲线的控点会根据鼠标的拖动时时改变位置,因此也要时时重画曲线// 这里左键控制第一个控点,右键控制第二个控点// 实验时可以同时按着双键拖动,此时两个控点重合,并移动着hdc = GetDC( hWnd );// 因为曲线要重绘,因此需要用白笔将曲线先“擦掉”// 曲线默认是用stock_pen绘制的SelectObject( hdc, GetStockObject( WHITE_PEN ) );DrawBezier( hdc, apt ); // 此时是用白笔再画一遍旧曲线,就相当于擦除(背景是白的)if ( wParam & MK_LBUTTON ) // 移动第一个控点{apt[1].x = LOWORD( lParam );apt[1].y = HIWORD( lParam );}if ( wParam & MK_RBUTTON ) // 移动第二个控点{apt[2].x = LOWORD( lParam );apt[2].y = HIWORD( lParam );}// 再用黑笔重画SelectObject( hdc, GetStockObject( BLACK_PEN ) );DrawBezier( hdc, apt );ReleaseDC( hWnd, hdc );}return 0;case WM_PAINT: // 一旦发生窗口重绘,就将客户区抹掉,曲线重新初始化成WM_PAINT所指定的模样InvalidateRect( hWnd, NULL, TRUE ); // 先将整个客户区抹白hdc = BeginPaint( hWnd, &ps );DrawBezier( hdc, apt );EndPaint( hWnd, &ps );return 0;case WM_DESTROY:PostQuitMessage( 0 );return 0;}return DefWindowProc( hWnd, message, wParam, lParam );}
关于鼠标消息的简介:

对于每个鼠标消息,和滚条消息一样也会将一定的通知码存放在wParam中,以WM_MOUSEMOVE消息来讲,只知道鼠标在移动是远远不够的,比如移动的时候鼠标键有没按下、是哪个键按下了、并且当前鼠标的坐标是多少等等,当然WM_LBUTTONDOWN的通知码中一定会有MK_LBUTTON,WM_RBUTTONDOWN消息中一定有MK_RBUTTON,而WM_MOUSEMOVE消息中具体是哪个键按下或者根本没按下就必须得看wParam的情况了!


0 0
原创粉丝点击