potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

C++20 Win32 APIでウィンドウにHLSグラデーションを描画する

Win32 APIでウィンドウにHLS色空間のグラデーションを描画するコードです。"win32_hls_color.hpp"ヘッダーファイルのwin32_hls_colorクラスは以前の投稿のコードを使用しています。

右側の画像の一番上に黒い横線がクラス設計か画像処理の問題かは悩んでいます。以下のコードはおそらく不完全です。

f:id:potisan:20210215234254p:plain
スクリーンショット

#include <memory>
#include <span>

#define STRICT
#define NOMINMAX
#include <Windows.h>
#pragma comment(lib, "shlwapi.lib")
#include <Shlwapi.h>

#include "win32_hls_color.hpp"

static const auto MainWindowClassName = L"MainWindow";
static const auto MainWindowTitle = L"MainWindow";
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

using unique_bitmap_handle = std::unique_ptr<std::remove_pointer_t<HBITMAP>, decltype(&DeleteObject)>;
using unique_dc_handle = std::unique_ptr<std::remove_pointer_t<HDC>, decltype(&DeleteDC)>;
unique_bitmap_handle make_unique_bitmap_handle(HBITMAP hbmp) noexcept;
unique_dc_handle make_unique_dc_handle(HDC hdc) noexcept;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int iCmdShow)
{
    // メインウィンドウのウィンドウクラス登録
    WNDCLASSEXW wcex{
        sizeof(WNDCLASSEXW),
        CS_HREDRAW | CS_VREDRAW,
        WndProc,
        0,
        0,
        hInstance,
        nullptr,
        LoadCursor(nullptr, IDC_ARROW),
        reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1),
        nullptr,
        MainWindowClassName };
    if (!RegisterClassExW(&wcex))
    {
        return GetLastError();
    }

    // メインウィンドウの作成・表示
    auto hwnd = CreateWindowExW(
        WS_EX_OVERLAPPEDWINDOW,
        MainWindowClassName,
        MainWindowTitle,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        nullptr, nullptr, hInstance, nullptr);
    if (hwnd == nullptr)
    {
        return GetLastError();
    }
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    // メッセージループ
    MSG msg;
    int ret;
    while (ret = GetMessageW(&msg, nullptr, 0, 0))
    {
        if (ret == -1)
        {
            break;
        }
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return msg.wParam;
}

HBITMAP CreateAndDrawDIBitmapForHLSSquareGradHL(uint16_t s)
{
    const int width = win32_hls_color::h_max;
    const int height = win32_hls_color::l_max;
    const int width_bytes = width + (width % 4);
    BITMAPINFO bmi{ {sizeof(BITMAPINFO), width, -height, 1, 32, BI_RGB} };
    LPVOID pbits = nullptr;
    auto hbmp = CreateDIBSection(
        nullptr, &bmi, DIB_RGB_COLORS, &pbits, nullptr, 0);
    auto pixels = std::span(
        reinterpret_cast<LPBYTE>(pbits),
        reinterpret_cast<LPBYTE>(pbits) + width_bytes * height * 4);
    for (int y = 0; y < height; y++)
    {
        int t = y * width_bytes * 4;
        for (int x = 0; x < width; x++)
        {
            auto hls = win32_hls_color::from_hls(x, y, s);
            COLORREF rgb = hls.to_rgb();
            pixels[t + 2] = GetRValue(rgb);
            pixels[t + 1] = GetGValue(rgb);
            pixels[t + 0] = GetBValue(rgb);

            t += 4;
        }
    }
    return hbmp;
}

HBITMAP CreateAndDrawDIBitmapForHLSSquareGradHS(uint16_t l)
{
    const int width = win32_hls_color::h_max;
    const int height = win32_hls_color::s_max;
    const int width_bytes = width + (width % 4);
    BITMAPINFO bmi{ {sizeof(BITMAPINFO), width, -height, 1, 32, BI_RGB} };
    LPVOID pbits = nullptr;
    auto hbmp = CreateDIBSection(
        nullptr, &bmi, DIB_RGB_COLORS, &pbits, nullptr, 0);
    auto pixels = std::span(
        reinterpret_cast<LPBYTE>(pbits),
        reinterpret_cast<LPBYTE>(pbits) + width_bytes * height * 4);
    for (int y = 0; y < height; y++)
    {
        int t = y * width_bytes * 4;
        for (int x = 0; x < width; x++)
        {
            auto hls = win32_hls_color::from_hls(x, l, y);
            COLORREF rgb = hls.to_rgb();
            pixels[t + 2] = GetRValue(rgb);
            pixels[t + 1] = GetGValue(rgb);
            pixels[t + 0] = GetBValue(rgb);

            t += 4;
        }
    }
    return hbmp;
}

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

            // 描画するビットマップ設定用のデバイスコンテキスト
            auto hdc2 = make_unique_dc_handle(CreateCompatibleDC(hdc));

            // 左:Sを固定してHLのグラデーション(S = 240)
            auto hbmp1 = make_unique_bitmap_handle(
                CreateAndDrawDIBitmapForHLSSquareGradHL(win32_hls_color::s_max));
            SelectObject(hdc2.get(), hbmp1.get());
            BitBlt(
                hdc, 0, 0, win32_hls_color::h_max, win32_hls_color::l_max,
                hdc2.get(), 0, 0, SRCCOPY);

            // 右:Lを固定してHSをグラデーション(L = 120)
            auto hbmp2 = make_unique_bitmap_handle(
                CreateAndDrawDIBitmapForHLSSquareGradHS(win32_hls_color::l_max / 2));
            SelectObject(hdc2.get(), hbmp2.get());
            BitBlt(
                hdc, win32_hls_color::h_max, 0, win32_hls_color::h_max, win32_hls_color::s_max,
                hdc2.get(), 0, 0, SRCCOPY);

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

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

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

unique_bitmap_handle make_unique_bitmap_handle(HBITMAP hbmp) noexcept
{
    return unique_bitmap_handle(hbmp, &DeleteObject);
}

unique_dc_handle make_unique_dc_handle(HDC hdc) noexcept
{
    return unique_dc_handle(hdc, &DeleteDC);
}