Skip to main content
3 of 4
added 85 characters in body
COLORREF color = GetPixel(GetDC(NULL), x, y); <- resource leak

Each call to GetDC should be cleaned up by ReleaseDC, otherwise we get GDI resource leak. You can monitor this GDI resource leak in Windows Task Manager (under "Details" tab, right-click on the list column header, and select to show "GDI objects") See also documentation for GetDC

A call to BeginPaint should be cleaned up with EndPaint (we don't want to call GetDC/ReleaseDC in between)

HDC hdc = BeginPaint(hwnd, &ps);
//use hdc...
EndPaint(hwnd, &ps);

Font created by CreateFont also needs cleanup with DeleteObject. You must also save the old font and restore it (see suggested code).

Use InvalidateRect, instead of PostMessage(main_wnd, WM_PAINT, 0, 0); to update paint. WM_PAINT message is not for sending messages, it is used for processing the message. This, and the resource leaks, are likely the cause of lag in resizing.

Use GetKeyState(VK_CONTROL) to see if control key is pushed.

LowLevelMouseProc should return CallNextHookEx if it does not process the message.

We can also handle WM_SIZE to update the window on resizing.

Error handling for functions such as GetCursorPos is not necessary. This function fails only if the parameter is incorrect, or there is something seriously wrong, in which case the program will crash anyway.

Make sure process is DPI aware, otherwise mouse position will be off, and GetPixel returns the wrong color.

Suggested changes:

#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <stdio.h>

HWND main_wnd;
HHOOK mouseHook;
COLORREF color = 0xffffffff;

VOID WINAPI CopyToClipboard(HWND hwnd)
{
    if (!OpenClipboard(hwnd))
        return;
    EmptyClipboard();

    int len = 10;
    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len);
    if (!hMem) return;
    char* buffer = GlobalLock(hMem);
    if (!buffer) return;
    sprintf_s(buffer, len, "#%02x%02x%02x",
        GetRValue(color), GetGValue(color), GetBValue(color));
    GlobalUnlock(buffer);

    SetClipboardData(CF_TEXT, hMem);
    CloseClipboard();
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{
    switch (uMsg) 
    {
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        const int TEXT_LINE_HEIGHT = 30;

        if ((GetKeyState(VK_LBUTTON) & 0x8000) == 0)//mouse is up
        {
            POINT pt;
            GetCursorPos(&pt);
            HDC hdc_desktop = GetDC(0);
            COLORREF clr = GetPixel(hdc_desktop, pt.x, pt.y);
            ReleaseDC(0, hdc_desktop);
            color = clr;
        }

        RECT rect;
        GetClientRect(hwnd, &rect);
        rect.bottom -= TEXT_LINE_HEIGHT;

        HBRUSH brush = CreateSolidBrush(color);
        FillRect(hdc, &rect, brush);
        DeleteObject(brush);

        // Draw the RGB rectangle:
        brush = CreateSolidBrush(RGB(255, 255, 255));
        rect.top = rect.bottom;
        rect.bottom += TEXT_LINE_HEIGHT;
        FillRect(hdc, &rect, brush);
        DeleteObject(brush);

        wchar_t buffer[20];
        wsprintf(buffer, L"#%02x%02x%02x",
            GetRValue(color), GetGValue(color), GetBValue(color));

        SetTextColor(hdc, RGB(0, 0, 0));
        HFONT hFont = CreateFont(30, 0, 0, 0, FW_BOLD, 0, 0, 0,
            0, 0, 0, ANTIALIASED_QUALITY, 0, L"Monospaced");
        HFONT oldfont = (HFONT)SelectObject(hdc, hFont);
        TextOut(hdc, 5, rect.bottom - TEXT_LINE_HEIGHT, buffer, 7);
        SelectObject(hdc, oldfont);
        DeleteObject(hFont);

        EndPaint(hwnd, &ps);
        return 0;
    }

    case WM_KEYDOWN:
        //Ctrl+C:
        if ((GetKeyState(VK_CONTROL) & 0x8000) == 0x8000 && wParam == 'C') 
            CopyToClipboard(hwnd);
        return 0;

    case WM_SIZE:
        InvalidateRect(hwnd, NULL, FALSE);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) 
{
    if (wParam == WM_MOUSEMOVE)
    {
        int is_mouse_down = (GetKeyState(VK_LBUTTON) & 0x8000) != 0;
        if(!is_mouse_down)
            InvalidateRect(main_wnd, 0, 0);
    }
    return CallNextHookEx(mouseHook, nCode, wParam, lParam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE prev, PWSTR cmd, int show)
{
    const wchar_t CLASS_NAME[] = L"GrabPixlMainWndw";

    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(wc);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    RegisterClassEx(&wc);

    int screenWidth = GetSystemMetrics(SM_CXFULLSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYFULLSCREEN);

    const int w = 300;
    const int h = 200;
    main_wnd = CreateWindowEx(0, CLASS_NAME, L"GrabPixel 1.6",
        WS_OVERLAPPEDWINDOW, (screenWidth - w) / 2, (screenHeight - h) / 2,
        w, h, NULL, NULL, hInstance, NULL);
    ShowWindow(main_wnd, show);
    UpdateWindow(main_wnd);

    mouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, hInstance, 0);

    MSG msg = { 0 };
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}