当前位置:C++技术网 > 精选软件 > Win32橡皮擦的标准实现(矩形和圆形橡皮擦)的效率提升:解决擦除不顺畅和提高擦除效率

Win32橡皮擦的标准实现(矩形和圆形橡皮擦)的效率提升:解决擦除不顺畅和提高擦除效率

更新时间:2015-12-17 20:30:06浏览次数:1+次

    在文章《SetPixel和GetPixel函数实现自由画图和橡皮擦功能》我实现了基本的橡皮擦功能,然而你会发现,擦的效果并不好。
    通过分析代码和测试,有两个问题,一个是擦除的不顺利,有时候反复擦除还擦不掉,经常感觉擦的不连续很不爽。第二个问题就是,如果滑动过快,就和划线断线一个效果,就是断断续续的。橡皮擦的基本原理就是用背景色来绘图而已。
    先来看擦除不顺利的问题。在前面实现的橡皮擦分析文章中,你可以发现,为了提高不必要的擦除,在检测到鼠标经过的点的颜色为黑色,就不再进行擦除操作了。正是这个原因,导致我们虽然扩展了橡皮擦的大小,然而如果橡皮擦中心点如果是黑色的依然无法擦除。那么原本我们的优化反而成为了提高体验的绊脚石了。
    在编程中,找到问题是关键,然后解决问题才是第二步。很多时间我们都是花在找原因上,找的越准确就可以越好的解决问题。问题已经找到,那么我们来设法解决。最简单的就是直接不管是什么颜色,统一用背景色擦除就是了。实际上对于效率也几乎没有影响。当然,如果橡皮擦的大小变大后,自然每次移动要重绘的范围就变大,会带来一些效率的问题。特别擦除时候用户是比较急躁的,想尽快的擦完,所以滑动的比较快,也就让重绘负担变大,可能对效率产生明显的影响。当然这些都需要测试才知道是否可以接受。不过这样简单粗暴的编程起来特别方便。那么我们只要注释这段代码即可:
//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(clr==0)return 0;//更快速的判断,黑色的三个分量都是0,合起来还是0
COLORREF clrClear = RGB(0,0,0);//合成创建一个黑色
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);    //-单像素
    }
}

    运行后的效果如下:

    用红色画笔画图效果

红色画笔正常绘图绘制的粗线效果

    擦除一部分,擦除的线条很流畅,擦除的过程很顺畅很爽,使用的是矩形橡皮擦

擦除一部分,擦除的线条很流畅,擦除的过程很顺畅很爽,使用的是矩形橡皮擦
    那么第一个问题用了第一种方法解决了,那么第二种方法就是提高擦除(绘图)效果来做。思路就是,检测所有的橡皮擦范围内的像素,如果有一个不是背景色的黑色,就要擦除。如果都是黑色,就不擦除。不过这样就引进了判断的过程,至于这个计算过程和直接擦除相比,哪个效率更高,你自己可以比较一下。
    分析一下,加入了判断,也就是多了一个if和一个GetPixel,相对于单个SetPixel来说,反而降低了效率。通过测试也发现,加入判断后,发现擦除的过程产生的黑色块是渐渐的绘制完整的,有那种从左到右的渐变绘画出黑色块的效果。这个要运行代码才能看到。也就是说,第二种方法不仅不能提高擦除的效率,而且还降低了擦除效率。这个渐变的效果,你快速滑动鼠标,看到产生的单个的黑色块之后,就看的更明显。你先将窗口涂满红色,在红色上擦除来看,红色的面积大些看的更直观。代码如下:
for (int i=-10;i<=10;i++)
{
    for (int j=-10;j<=10;j++)
    {
        //if(GetPixel(hdc,GET_X_LPARAM(lParam)+i,GET_Y_LPARAM(lParam)+j)==0)continue;//加入判断导致绘制擦除的黑色块的效率反而降低
        SetPixel(hdc,GET_X_LPARAM(lParam)+i,GET_Y_LPARAM(lParam)+j,RGB(0,0,0));    //-单像素
    }
}
    第一个问题用判断的方式提升效率失败。有没有其他方式来优化呢?当然还有了。既然橡皮擦是块状的自然就想到了矩形咯。是的,我们就用画矩形的方式来代替自己用循环画点形成矩形,这样提高了效率,也就能够处理的更快。
    用画矩形实现橡皮擦的方块,只需要确定两点即可,即矩形的左上角和右下角的点。画矩形就不是划线,而是画一个区域,所以,我们要用画刷来填充区域的颜色。因为矩形充当了橡皮擦,所以矩形内部要用背景颜色黑色来填充。所以,就获取系统的黑色画刷。然而我是直接将返回的画刷句柄传给了SelectObject函数与DC关联,这样就可以画黑色的矩形了。特别注意哈。代码如下:
SelectObject(hdc,GetStockObject(BLACK_BRUSH));
    如果是使用自己创建的画刷,那么一定要记得删除画刷,并且在删除前用其他的画刷替换,一般就用系统画刷设置为当前画刷即可。因为这里背景就是黑色的,所以就不用自己创建画刷了,这样不仅没有必要,反而降低了效率。系统画刷直接获取,只管使用,不要释放什么的。从这点来说,也是可以优化代码效率的。
    那么接下来就是绘制矩形的擦除块。确定的方式和我们自定义的画矩形一样的,以光标为中心扩展得到矩形,方法和前面实现的一样,只是只需要确定两个点即可。代码如下:
int size=50;//size*2+1就是整个的矩形块的大小,中心点有一个像素所以+1
Rectangle(hdc,GET_X_LPARAM(lParam)-size,GET_Y_LPARAM(lParam)-size,GET_X_LPARAM(lParam)+size,GET_Y_LPARAM(lParam)+size);
    我们这里使用的是正方形,要实现圆形的橡皮擦,调用函数Ellipse即可,参数和Rectangle一模一样,而圆就是正方形的矩形的内切圆,所以其实这个参数就和矩形是一样的。然而我们却可以实现圆形橡皮擦了哦。

    我们可以看到效果图如下:

    圆形橡皮擦擦除效果,红色中间就是擦除后的效果

圆形橡皮擦擦除效果,红色中间就是擦除后的效果
    我们可以看到,红色中间的黑色就是圆形的橡皮擦擦出来的效果。
    本程序的画图和其他功能的分析,见文章《Win32客户区中用画笔自由绘图的功能标准实现》。本程序所有的代码如下:
#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;
         ptOld.x = GET_X_LPARAM(lParam);
         ptOld.y = GET_Y_LPARAM(lParam);
         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,50,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(clr==0)return 0;//更快速的判断,黑色的三个分量都是0,合起来还是0

            //从中心点(当前鼠标光标所在的点)向四周延伸两个像素
            //for (int i=-10;i<=10;i++)
            //{
            //    for (int j=-10;j<=10;j++)
            //    {
            //        //if(GetPixel(hdc,GET_X_LPARAM(lParam)+i,GET_Y_LPARAM(lParam)+j)==0)continue;//加入判断导致绘制擦除的黑色块的效率反而降低
            //        SetPixel(hdc,GET_X_LPARAM(lParam)+i,GET_Y_LPARAM(lParam)+j,RGB(0,0,0));    //-单像素
            //    }
            //}
             //-用圆形矩形区域填充来实现橡皮擦,提高效率
             SelectObject(hdc,GetStockObject(BLACK_BRUSH));
             int size=50;
             //矩形橡皮擦
             //Rectangle(hdc,GET_X_LPARAM(lParam)-size,GET_Y_LPARAM(lParam)-size,GET_X_LPARAM(lParam)+size,GET_Y_LPARAM(lParam)+size);
             //圆形橡皮擦
             Ellipse(hdc,GET_X_LPARAM(lParam)-size,GET_Y_LPARAM(lParam)-size,GET_X_LPARAM(lParam)+size,GET_Y_LPARAM(lParam)+size);
             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;
}