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