Introduction
Want to check out colors used in a Web page? ColorSpy is a lightweight desktop utility written with WTL. It lets anyone who loves WTL play with colors. What ColorSpy does is three things:
- Samples a pixel color (well, colors) on screen
- Reads a RGB triplet (#rrggbb form) from HTML source and shows its color in color box (see screenshot shown above)
- Reads a color name and shows its color with RGB triplet
Environment
ColorSpy was created with VC++ 6.0 SP5 with WTL 7.0, and was mainly tested on my machine Win2k with SP3. To build, you will need WTL installed. At the time of writing this, MS WTL can be found here. If you haven't tried it out, go get it.
Picking up and sampling a pixel color on screen
In practice, I created a transparent window with WS_EX_TRANSPARENT
style, which covered the entire screen, but soon realized that use of transparent window would not work; you can see all pixel colors through the transparent window, but mouse events won't get through.
The way ColorSpy picks up the pixel color was inspired by other utility apps (e.g. WindowFinder). It adapts one of the golden rules of Windows programming found in those utilities. The golden rule is that when you find there is no appropriate message to response to, you make it yourself. ColorSpy also uses a timer to create its application specific message (WM_APP_COLORSPY
) periodically and then responses to the message by itself, picking up a color of the pixel located by mouse cursor:
::GetCursorPos (&m_cursor);
HDC hdc = ::GetDC(NULL);
m_clrPixel = ::GetPixel(hdc, m_cursor.x, m_cursor.y);
::ReleaseDC(NULL, hdc);
Controlling over the timer will be a key for sampling colors. When holding down CTRL+SHIFT, by killing the timer, ColorSpy samples a pixel color and stores it in colorbox. It becomes inactive until you hit the colorbox again.
if (::GetAsyncKeyState(VK_CONTROL) < 0 &&
::GetAsyncKeyState(VK_SHIFT) < 0)
{
m_colorBox.Lock();
((CEdit)m_infoBox).SetReadOnly(FALSE);
}
If you kept running timer instead, you could always resume color picking using CTRL+SHIFT as a toggle key... but customization is all up to you... there is always more than one way to do it.
Reading color codes from HTML source
When ColorSpy is inactive (while no timer ticking), ColorSpy has a different work to do; it reads a color code (in '#RRGGBB' form including preceding '#') from an HTML file. For example, you visit a web site and view source or open an HTML file in the editor you use.
Selecting the color code and simply pressing CTRL+C will give you a color you have selected.
To do this, ColorSpy utilizes a clipboard management feature called Clipboard viewer window, so it can read color codes clipped by the user. Here is a template class sample that turns ColorSpy into a clipboard viewer window for color-code-watching extension:
template <typename T>
class CColorSpyCBViewer
{
public:
HWND m_hWndNextCBViewer;
bool m_bRegistered;
BEGIN_MSG_MAP(CColorSpyCBViewer< T >)
ALT_MSG_MAP(1)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_INITDIALOG, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_DRAWCLIPBOARD, OnDrawClipboard)
MESSAGE_HANDLER(WM_CHANGECBCHAIN, OnChangeCbChain)
END_MSG_MAP()
CColorSpyCBViewer() : m_hWndNextCBViewer(0), m_bRegistered(false) {}
LRESULT OnCreate(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
T* pT = static_cast<T*>(this);
m_hWndNextCBViewer = pT->SetClipboardViewer();
m_bRegistered = true;
return 0;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
if (IsWindow(m_hWndNextCBViewer))
pT->ChangeClipboardChain(m_hWndNextCBViewer);
return 0;
}
LRESULT OnDrawClipboard(UINT uMsg, WPARAM wParam, LPARAM lParam,
BOOL& )
{
T* pT = static_cast<T*>(this);
if (m_bRegistered == false)
{
ATLTRACE("Registering... wParam: %#x\n", wParam);
return 0;
}
ATLTRACE("OnDrawClipboard(): Wnd %#x put CB data\n", wParam);
if (IsClipboardFormatAvailable(CF_TEXT) && pT->OpenClipboard())
{
CString text;
HANDLE hData = ::GetClipboardData( CF_TEXT );
text = (char*)::GlobalLock( hData );
::GlobalUnlock( hData );
CloseClipboard();
pT->SetColor(text);
}
RouteMessage(uMsg, wParam, lParam);
return 0;
}
LRESULT OnChangeCbChain(UINT , WPARAM wParam, LPARAM lParam,
BOOL& )
{
T* pT = static_cast<T*>(this);
HWND hWndRemove = reinterpret_cast<HWND>(wParam);
HWND hWndAfter = reinterpret_cast<HWND>(lParam);
if (m_hWndNextCBViewer == hWndRemove)
{
m_hWndNextCBViewer = hWndAfter;
}
RouteMessage(WM_CHANGECBCHAIN, wParam, lParam);
return 0;
}
void RouteMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (m_hWndNextCBViewer)
::SendMessage(m_hWndNextCBViewer, uMsg, wParam, lParam);
}
};
Clipboard (viewer window) management
Previously, I stated incorrectly. CB viewer windows are really supposed to respond to WM_CHANGECBCHAIN
message and refresh a pointer to a handler to next viewer window.
If you search web pages on clipboard viewer, you may find SDK documentation like this:
Creating a clipboard viewer window
A clipboard viewer window displays the current content of the clipboard, and receives messages when the clipboard content changes.
To create a clipboard viewer window, your application must do the following:
- Add the window to the clipboard viewer chain.
- Process the
WM_CHANGECBCHAIN
message.
- Process the
WM_DRAWCLIPBOARD
message.
- Remove the window from the clipboard viewer chain before it is destroyed.
Here is my observation and vital update of CB viewer on Windows 2000 SP3. That is, the result on your machine would be most likely the same:
- Add the window to the clipboard viewer chain. => TRUE. With WTL, you do that with
CWindow::SetClipboardWindow()
- Process the
WM_CHANGECBCHAIN
message. => TRUE
- Process the
WM_DRAWCLIPBOARD
message. => TRUE but not quite. SDK documentation says "WPARAM
is not used...", or "must be zero...". WM_DRAWCLIPBOARD
, however, comes with WPARAM
filled, and WPARAM
is actually a handle to the window that has placed CB data. Probably I would not have realized what WPARAM
value was, if I was using MFC. WTL is nice.
- Remove the window from the clipboard viewer chain before it is destroyed. => TRUE. Make sure to call
CWidnow::ChangeClipboardChain()
on your way out.
The documentation on clipboard management API may deserve some remarks. Or does it?
Color names. Tell me your name.
If sampled color value matches the RGB value defined in Color name-to-RGB value table, ColorSpy shows its name in ToolTip. While ColorSpy is inactive, you can try color names like, darkviolet, navy, navajowhite and so on.
Before we start, you might want to pull up the source code for this update. To support color names (hereinafter called colornames
), ColorSpy utilizes a modified version of RGB color name-to-value table defined in rgbtable.h, which was taken from Xpm library from XFree86 (See References). As supplemental information, ColorSpy displays color name as a tooltip text only if the sampled pixel color value matches any RGB value defined in the table. In addition to color triplets (#RRGGBB), it can also interpret a color name and display its color and RGB triplet by looking up the table.
I never thought this color table support would eventually lead to another story to tell you about.
While testing ColorSpy with colornames
, I found that there is some unique color name interpretation in web browsers. Can you tell the names of the following colors rendered beautifully? Take a guess. You can run ColorSpy to check RGB values of these colors now.
Now time is up, sir. If you thought these were Green something, nice try. These are called Gray (grayXX). Unlike those browser unrecognizable color names, with which resultant color may depend on the type of browser you use. As you can see, these names are, however, somehow interpreted.
Because of this interpretation - RGB(0x00, 0xA0, 0xXX)
, where XX is numeric part of Gray color names except for gray100, which is black - I had to comment out the grayXX part from the original color table to avoid confusion. Color names may be informative, but they may not be that essential. Once you played around with color names and found your favorite color name, you can use triplet of the color instead. Oh BTW, this interpretation is a common among the browsers. Bottom line - Confusing yet intriguingly worthwhile.
Quick check on Desktop color settings
This feature is rather for developers, not for web designers. Instead of checking with Display Properties applet in Control Panel, you can check your desktop color settings from system color types (COLOR_XXXX
defined in WinUser.h). For example,
COLOR_INFOBK
, which is background color of ToolTips
COLOR_ACTIVECAPTION
, which is color of the title bar for the active window (Go to Control Panel > Display > Color1 in Appearance)
COLOR_GRADIENTACTIVECAPTION
, which is active titlebar background color. (Color2)
All system color types are defined in CSysColors
class. There are around 30 different display aspects as system color types. In colorbox.h, CSysColors
is also used to determine whether ColorSpy should use system color brush based on your desktop settings as shown below:
#define HANDLE_SYS_COLOR_BRUSH(index) \
if (m_clrBackground == theSystemColors[index].clr) \
{ \
return brh.CreateSysColorBrush(index); \
}
HBRUSH CreateBrush()
{
CBrushHandle brh;
HANDLE_SYS_COLOR_BRUSH(COLOR_BACKGROUND);
[...]
}
When your mouse makes you nuts
You have tried out the earlier version and realized that it is hard to work on tity pictures. Why not try magnifier? This feature may be helpful especially when sampling pixel colors in a dense-colored picture.
Some utilities offer a magnifier, presents a zoomed picture, but somewhat make you feel like a machine when working on a single pixel. Why? Because you still have to pin-point a single pixel on on-screen while watching the zoomed picture. After all 2 px. mouse movement is still 2px. distance so no matter how hard you look at the magnifier, it only tells you the mouse cursor is still pointing to the wrong spot unless you can work on the zoomed picture.
ColorSpy's magnifier acts like a clickable panel with variable magnification levels. So take a snapshot first. You don't have to pin-point the pixel on screen. Then click a magnified image as many as you like. I hope with this feature you no longer have to fire up another magnifier application just for easier viewing. And next time you feel like a machine, you might go like "stinky mouse or is it? software? (possibly, with creepy background music)".
Simplicity over fancy stuff
My ColorSpy is a simple app. I am hoping it will be part of your desktop goodies because of its simplicity. With source code you can make your own ColorSpy. Idea is all yours.
This is my first attempt to write an article and I have always admired tireless work of folks here at CodeProject, contributing their articles. Crappy rating would be welcome if you could first tell me the reason why you have read my article...
Happy WTL programming!
- rgbtable.h - hard coded rgb.txt RGB color database. FTP
History
- 7/6/2003 Initial.
- 7/20/2003 Colorname for designers. System color types for developers.
- 7/24/2003 Updated article due to my bogus description on Clipboard (viewer window) management. See discussion below. Thank you, Mike-.
- 9/29/2003 Introduced new features, clickable magnifier and Hue-Saturation-Lightness (Brightness) display...