用SetPixel单点着色来画图的实现,请参考文章《使用SetPixel在客户区自由绘画的功能实现》。
所以我们现在要解决的问题就是划线不连续的问题,顺便实现一下将划线变宽的问题。我们先来看看用直线画出的效果图:
那么我们这里要解决这个问题的原理就是使用划线函数来实现。为什么这样就可以解决问题呢?主要还是因为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;
}