我们自己来实现一个拾色器,通过本文的详细讲解,你也可以轻松制作一个自己的拾色器哦。下面是拾色器程序的截图:
【拾色器拾取颜色后的截图】
我们先要知道拾色器是如何工作的,然后才是实现。我们并不是为了制作拾色器而制作拾色器。我们需要知道其中的原理,然后就可以更加灵活的增加自己的需求。拾色器的使用方法是:在窗口客户区中按下鼠标左键,然后拖动鼠标到屏幕的任何一个地方,都可以获取鼠标光标热点下的这个点的颜色值。然后程序将这个颜色值设置为窗口客户区的背景色,这样整个客户区就显示了当前拾取的背景色,达到预览的效果。
通常情况下,我们鼠标移出窗口外,就不在窗口的控制范围内了。而我们确实需要这样的需求,可以脱离窗口外在屏幕任何位置上取得像素的颜色。这就需要使用鼠标捕获的技术了。对于鼠标捕获的技术分析,请阅读文章《SetCapture捕捉鼠标全面深入详解》。
我们在鼠标左键按下时捕获鼠标,并且将鼠标光标替换为十字架,表示现在鼠标处于捕获状态,可以随意移动鼠标。当鼠标左键弹起,就释放鼠标捕获,恢复光标为箭头。
我们现在已经具备可以在全屏幕取颜色的光标了。那么如何得到屏幕的DC呢?如果没有屏幕的DC,而只有窗口的DC,不管鼠标光标移动到哪,得到的都是窗口的像素颜色。这样是无法完成获取屏幕像素点的颜色的。
要得到屏幕DC有两种方法,代码如下:
//hScreen = CreateDC(_T("DISPLAY"),0,0,0);//方法1
hScreen = GetDC(NULL);//方法2
两种方法得到的效果都是一样的。所谓屏幕DC,其实就是当前显示的画面的视频显示卡上的一个位图副本。我们取色就是在这个位图副本对应的坐标取得相应的像素点的值,像素点的值由颜色值构成。我们这里获取的是24位的真彩色,不讨论索引颜色。一样两句代码选其一即可,都是获取DC,和获取窗口DC用法一样。我们在WM_CREATE创建,并将屏幕DC句柄存储起来,全局使用。在WM_DESTROY删除或者释放。如果是第一种方法,则对应的是DeleteDC,第二种就是ReleaseDC。不要混乱了,记住:获取的东西只要释放即可,创建的东西需要删除!
我们在窗口第一次显示时,使用WM_PAINT提示初始化颜色值和操作方法。以后除非要重画窗口,否则不会在有什么作用了。我们的实时提示和填充背景颜色,都是在鼠标移动消息中处理的。这样效率比较高。而在WM_PAINT消息中的代码保持和鼠标移动消息一样,是为了在拾取颜色之后,再调整窗口大小时,客户区还是保持一致,不会因为调整了窗口的大小后,客户区的颜色变了。
在客户区绘制文字和填充背景,还是要获取客户区的DC。在WM_MOUSEMOVE消息的lParam参数中,携带了鼠标的客户区坐标,高字部分为y坐标,低字部分为x坐标,我们使用宏进行提取。对于LOWORD和HIWORD的使用,请参见《DWORD、WORD的高低部分提取和合成宏代码示例》。因为坐标是可以具备正负的,因为我们使用了鼠标捕获,鼠标光标可以到达客户区负数坐标的位置,即左边和上边。所以,我们要将坐标转换为short有符号类型。因为WORD宏就是unsigned short的重定义,所以它是无符号的。然而我们此时需要有符号的值,要将值转换为short类型。而且还不能是int类型。int类型的位数太多了,无法分辨出正负了。
我们要在屏幕中获取像素值,自然要得到屏幕的坐标才是正确的。所以要将客户区坐标转换为屏幕坐标,使用ClientToScreen来完成。对于客户区坐标和屏幕坐标的互相转换,请阅读《图解客户区坐标与屏幕坐标相互转换》。
我们将获取的颜色值,即COLORREF类型的值,使用宏提取出红绿蓝三个颜色分量,然后用来创建画刷,并使用FillRect来填充客户区。颜色的分量提取,可以参考文章《COLOR和COLORREF之间的理解与相互转换》。
FillRect函数的使用分析,可以参考《FillRect、FrameRect与Rectangle矩形绘制函数使用对比分析》
代码里面,基本都标注了详细的注释,就不再一一介绍了。
下面是完整的代码:
#include "windows.h"
#include <tchar.h>
TCHAR tip[100]=_T("");
TCHAR txt[100]=_T("C++技术网提示:按住鼠标左键拖动光标到窗口外可以拾取屏幕任何位置的颜色。");
TCHAR clrhex[20]=_T("");
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static HDC hScreen;
RECT rect;
static COLORREF clr;
HBRUSH hBrush;
switch (message)
{
case WM_CREATE:
{
//使用下面两种的一种都可以获得屏幕的DC,在窗口销毁要做对应的删除或者释放
//hScreen = CreateDC(_T("DISPLAY"),0,0,0);//方法1
hScreen = GetDC(NULL);//方法2
}
case WM_PAINT:
hdc = BeginPaint(hwnd,&ps);
//重绘窗口显示提示文字
GetClientRect(hwnd,&rect);//获取客户区矩形
hBrush = CreateSolidBrush(clr);//创建画刷
FillRect(hdc,&rect,hBrush);//使用画刷填充客户区,即显示拾取的颜色
SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));//使用等宽字体
SetBkMode(hdc,TRANSPARENT);//设置文字背景透明
SetTextColor(hdc,RGB(255-GetRValue(clr),255-GetGValue(clr),255-GetBValue(clr)));//设置文字颜色
DrawText(hdc,txt,-1,&rect,DT_WORDBREAK);//显示操作方法
TextOut(hdc,0,50,tip,lstrlen(tip));//输出颜色提示
EndPaint(hwnd,&ps);
return 0;
case WM_LBUTTONDOWN:
SetCapture(hwnd);//捕获鼠标
SetCursor(LoadCursor(NULL,IDC_CROSS));//显示十字架光标
return 0;
case WM_LBUTTONUP:
ReleaseCapture();//释放鼠标
SetCursor(LoadCursor(NULL,IDC_ARROW));//显示箭头光标
return 0;
case WM_MOUSEMOVE:
{
hdc = GetDC(hwnd);
POINT ptScreen;
//提取鼠标坐标,基于客户区坐标
ptScreen.x = (short)LOWORD(lParam);
ptScreen.y = (short)HIWORD(lParam);
ClientToScreen(hwnd,&ptScreen);//转换为屏幕坐标
clr = GetPixel(hScreen,ptScreen.x,ptScreen.y);//获取屏幕中指定坐标的颜色值
wsprintf(clrhex,_T("%X%X%X"),GetRValue(clr),GetGValue(clr),GetBValue(clr));//格式化颜色值到字符数组
//格式化显示的颜色提示
wsprintf(tip,_T("颜色(%d,%d,%d),十六进制:#%s"),GetRValue(clr),GetGValue(clr),GetBValue(clr),clrhex);
GetClientRect(hwnd,&rect);//获取客户区矩形
hBrush = CreateSolidBrush(clr);//创建画刷
FillRect(hdc,&rect,hBrush);//使用画刷填充客户区,即显示拾取的颜色
SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));//使用等宽字体
SetBkMode(hdc,TRANSPARENT);//设置文字背景透明
SetTextColor(hdc,RGB(255-GetRValue(clr),255-GetGValue(clr),255-GetBValue(clr)));//设置文字颜色
DrawText(hdc,txt,-1,&rect,DT_WORDBREAK);//显示操作方法
TextOut(hdc,0,50,tip,lstrlen(tip));//输出颜色提示
DeleteObject(SelectObject(hdc,GetStockObject(WHITE_BRUSH)));//删除画刷
ReleaseDC(hwnd,hdc);
}
return 0;
case WM_DESTROY:
//好习惯,用完删除或者释放
//DeleteDC(hScreen);
ReleaseDC(hwnd,hScreen);
PostQuitMessage(0);
return 0;
default:
break;//跳出到默认处理
}
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(WHITE_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|CS_DBLCLKS;
if(!RegisterClass(&wndClass))return 0;
HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,10,100,550,400,NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,SW_SHOWNORMAL);
MSG msg;
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}