当前位置:C++技术网 > 精选软件 > Win32客户区中用画笔自由绘图的功能标准实现

Win32客户区中用画笔自由绘图的功能标准实现

更新时间:2015-12-17 17:15:29浏览次数:1+次

    这是基于原来用SetPixel实现的自由画图的改进版本。因为使用SetPixel实现的画图你会发现WM_MOUSEMOVE接受的消息并不是连续的,也就是鼠标经过的所有点不会都产生消息,因为消息要保证实时性。所以鼠标移动快了就产生了中断的情况,所以画的线就断了。要画连续的线,你就要慢慢移动鼠标哦。而且画出来的线是一个像素的,很细的线,如果要画宽线就同时额外画几个像素,这样就可以形成粗线了。
    用SetPixel单点着色来画图的实现,请参考文章《使用SetPixel在客户区自由绘画的功能实现》。
    所以我们现在要解决的问题就是划线不连续的问题,顺便实现一下将划线变宽的问题。我们先来看看用直线画出的效果图:
    Win32客户区中用画笔自由绘图的功能标准实现
    那么我们这里要解决这个问题的原理就是使用划线函数来实现。为什么这样就可以解决问题呢?主要还是因为WM_MOUSEMOVE消息咯。当然,划线的话,也会让绘画的效率提高。
    你会想,划线怎么会实现画任何弯曲的线条呢?你要知道,WM_MOUSEMOVE消息产生是会很密集的,在鼠标滑过的瞬间可以产生大量的消息,也就可以得到大量的坐标,这些坐标连起来就是滑过的轨迹。然而因为消息太多所以根本来不及处理,所以有些消息就丢弃了,SetPixel因此而失败,所以有些点就没有画出来,就断线了。
    而划线只需要两个点,就是直线的两个端点,这样就可以很好的解决消息处理不过来而丢失的问题。我们假设在极短的距离比如十几个像素的范围内,肉眼是分辨不出来的,经过的轨迹可以看成是直线。同时消息来不及处理的问题呢也可以因此而解决。因为只需要两端的点就可以划线了,中间的一系列点都忽略了,自然需要处理的消息极大变小,这样也来得及处理,中间都被直线划过来了,没有出现断线的问题了。
    在效果图中,我们看到画的线都很粗,我们并没有像SiePixel那样自己多画几个像素,而是用画笔来画。我们只要设置好画笔为实线、一个线宽和颜色即可。这样就分分钟解决了这个问题。代码如下:
CreatePen(PS_SOLID,6,RGB(255,0,0));//返回一个画笔句柄,直接传给了函数
    我们划线使用函数MoveToEx将当前画笔移动到线的开始处,也就是我们鼠标左键单击的位置,这个位置就在WM_LBUTTONDOWN消息下记录下来,因为这个位置要在其他消息中使用,所以设置为了静态的变量。通过GET_X_LPARAM和GET_Y_LPARAM宏从WM_LBUTTONDOWN消息的lParam参数提取坐标。代码如下:
MoveToEx(hdc,ptOld.x,ptOld.y,NULL);//将当前位置移动到上一次结束的位置,虽然系统会自动更新,但是第一次我们自己设置一下,可以从单击的位置开始画
    因为我们画画是移动鼠标,移动的过程会产生很多的鼠标移动消息,我们要将接受到的所有的鼠标位置用直线连接起来。所以,这个静态变量我取名为了ptOld,因为每次接收到鼠标移动消息后,就将上一个点和当前的点连接后,将当前点设置为上一个点的坐标,以便后面的点与这个点连接。这个就是不断的往前推进迭代了。
    划线函数是LineTo,后面两个参数就是直线结束的位置,而直线的开始位置就是上一个点的位置咯。因为划线之后,系统将结束的点当做当前的画笔所在的点,接受到鼠标移动的消息后,发现画笔移动了,所以要用LineTo划线到新的位置,到了新的位置后,系统又会用线结束的位置设置为当前的点咯。而我们自己将新的位置设置为上次的点哦。代码如下:
ptOld.x = GET_X_LPARAM(lParam);
ptOld.y = GET_Y_LPARAM(lParam);
    我们提供了擦除划线的功能,这个实现分析见文章《SetPixel和GetPixel函数实现自由画图和橡皮擦功能》。为了更快的擦除所有的线,就处理了键盘消息WM_KEYDOWN,只要单击了键盘的按键,就让客户区失效导致重绘,进而就擦掉了客户区画的内容了。对于使客户区失效导致重绘的函数InvalidateRect的详细分析,请看《InvalidateRect是否删除背景效果分析以及实现下划线删除线》和《InvalidateRect无效矩形的图文分析和在字符串中移动光标》。
    那么本程序的所有点基本就解释到了,要完全理解,一定要看这些给出的技术点解释文章。本功能的实现完整代码如下:
#include "windows.h"
#include <Windowsx.h>//GET_X_LPARAM,GET_Y_LPARAM宏需要这个头文件
#include <tchar.h>
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static bool bisPaint=false;
    static bool bisClear=false;
    TCHAR Info[100]=_T("【C++技术网http://www.cjjjs.com】");
    int iRed,iGreen,iBlue;
    static POINT ptOld;
    switch (message)
    {
     case WM_PAINT:
         hdc = BeginPaint(hwnd,&ps);//会清除无效矩形
         TextOut(hdc,0,10,Info,lstrlen(Info));
         EndPaint(hwnd,&ps);
         return 0;
     case WM_LBUTTONDOWN:
         bisPaint=true;//开始作画
         ptOld.x = GET_X_LPARAM(lParam);
         ptOld.y = GET_Y_LPARAM(lParam);
         
         return 0;
     case WM_LBUTTONUP:
         bisPaint=false;//松开鼠标,结束绘画
         return 0;
     case WM_RBUTTONDOWN:
         bisClear=true;
         return 0;
     case WM_RBUTTONUP:
         bisClear=false;
         return 0;
     case WM_KEYDOWN:
         InvalidateRect(hwnd,NULL,TRUE);
         return 0;
     case WM_MOUSEMOVE:
         if(bisPaint)//如果按着鼠标时,表示在作画,因此可以开始绘制
         {
             hdc = GetDC(hwnd);
             MoveToEx(hdc,ptOld.x,ptOld.y,NULL);
             SelectObject(hdc,CreatePen(PS_SOLID,6,RGB(255,0,0)));
             LineTo(hdc,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
             ptOld.x = GET_X_LPARAM(lParam);
             ptOld.y = GET_Y_LPARAM(lParam);
             ReleaseDC(hwnd,hdc);
         }
         if(bisClear)
         {
             //-擦除绘图
             hdc = GetDC(hwnd);
             COLORREF clr = GetPixel(hdc,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));//获得当前像素颜色
             int red = GetRValue(clr);
             int green = GetGValue(clr);
             int blue = GetBValue(clr);
             if(red==0&&green==0&&blue==0)return 0;//-如果本身就是背景的黑色,就不用擦除
             //if(clr==0)return 0;//更快速的判断,黑色的三个分量都是0,合起来还是0
             COLORREF clrClear = RGB(0,0,0);//合成创建一个黑色
             //循环设置25个像素大小的橡皮擦,从中心点(当前鼠标光标所在的点)向四周延伸两个像素
            for (int i=-10;i<=10;i++)
            {
                for (int j=-10;j<=10;j++)
                {
                    SetPixel(hdc,GET_X_LPARAM(lParam)+i,GET_Y_LPARAM(lParam)+j,clrClear);    //-单像素
                }
            }
             ReleaseDC(hwnd,hdc);
         }
         return 0;
     case WM_DESTROY:
         PostQuitMessage(0);
         return 0;
     default:
         return DefWindowProc(hwnd, message, wParam, lParam);
    }
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
    TCHAR ClassName[] = _T("MyClass");
    TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
    WNDCLASS wndClass;
    wndClass.cbClsExtra=0;
    wndClass.cbWndExtra=0;
    wndClass.hbrBackground= (HBRUSH)GetStockObject(BLACK_BRUSH);
    wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
    wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hInstance = hInstance;
    wndClass.lpfnWndProc = WinProc;
    wndClass.lpszClassName = ClassName;
    wndClass.lpszMenuName=NULL;
    wndClass.style=CS_HREDRAW|CS_VREDRAW;

    if(!RegisterClass(&wndClass))
    {
        return 0;
    }

    HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,440,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOWNORMAL);

    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}