potisanのプログラミングメモ

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

C++20 円柱モデルHSV色クラスとHSV四角形の描画

円柱モデルのHSV色を扱うクラスとそれを利用したHSV四角形(V固定)の描画サンプルです。RGBとHSVの変換はWikipediaのコードを使用しています。

HSVをH:[0, 360)、SとV:[0, 1]で扱うとRGBも[0, 1]で扱う方が簡単です。そのため、[0, 1]のRGBと[0, 255]のRGB、それらの相互変換も定義しています。

HSV色クラス

// cylindrical_hsv_color.hpp
#include <stdexcept>
#include <cmath>
#include "rgb_color.hpp"

/// <summary>
/// 円柱モデルのHSV色(H:[0, 360)、S:[0, 1]、V:[0, 1])
/// </summary>
class cylindrical_hsv_color_f
{
private:
    float h, s, v;
public:
    constexpr cylindrical_hsv_color_f() = default;
    constexpr cylindrical_hsv_color_f(float h, float s, float v)
    {
        set_hsv(h, s, v);
    }

    constexpr float get_h() const noexcept { return h; }
    constexpr float get_s() const noexcept { return s; }
    constexpr float get_v() const noexcept { return v; }

    constexpr void set_h(float value)
    {
        if (!(0 <= value && value < 360))
            throw_out_of_range();
        h = value;
    }
    constexpr void set_s(float value)
    {
        if (!(0 <= value && value <= 1))
            throw_out_of_range();
        s = value;
    }
    constexpr void set_v(float value)
    {
        if (!(0 <= value && value <= 1))
            throw_out_of_range();
        v = value;
    }

    constexpr void set_hsv(float h, float s, float v)
    {
        set_h(h);
        set_s(s);
        set_v(v);
    }

    constexpr void get_hsv(float& h, float& s, float& v) const noexcept
    {
        h = this->h;
        s = this->s;
        v = this->v;
    }

    constexpr bool operator== (const cylindrical_hsv_color_f& other) const noexcept
    {
        return h == other.h && s == other.s && v == other.v;
    }

    constexpr bool operator!= (const cylindrical_hsv_color_f& other) const noexcept
    {
        return !(*this == other);
    }

private:
    static void throw_out_of_range()
    {
        throw std::out_of_range("");
    }
};

rgb_color1f to_rgb_color1f(const cylindrical_hsv_color_f& hsv) noexcept
{
    auto c = hsv.get_v() * hsv.get_s();
    auto hd = hsv.get_h() / 60.0f;
    auto x = c * (1 - std::abs(std::fmodf(hd, 2) - 1));
    float r1, g1, b1;
    switch (static_cast<int>(hd))
    {
    case 0:
        r1 = c;
        g1 = x;
        b1 = 0;
        break;
    case 1:
        r1 = x;
        g1 = c;
        b1 = 0;
        break;
    case 2:
        r1 = 0;
        g1 = c;
        b1 = x;
        break;
    case 3:
        r1 = 0;
        g1 = x;
        b1 = c;
        break;
    case 4:
        r1 = x;
        g1 = 0;
        b1 = c;
        break;
    default:
        r1 = c;
        g1 = 0;
        b1 = x;
        break;
    }
    auto m = hsv.get_v() - c;
    rgb_color1f rgb;
    rgb.set_rgb_nocheck(r1 + m, g1 + m, b1 + m);
    return rgb;
}

constexpr cylindrical_hsv_color_f to_cyrindrical_hsv_color_f(const rgb_color1f& rgb) noexcept
{
    auto r = rgb.get_r();
    auto g = rgb.get_g();
    auto b = rgb.get_b();
    auto max = std::max(r, std::max(g, b));
    auto min = std::min(r, std::min(g, b));
    auto diff = max - min;
    cylindrical_hsv_color_f hsv;
    if (min == b)
        hsv.set_h(60 * (g - r) / diff + 60);
    else if (min = r)
        hsv.set_h(60 * (b - g) / diff + 180);
    else
        hsv.set_h(60 * (r - b) / diff + 300);
    hsv.set_s(diff / max);
    hsv.set_v(max);
    return hsv;
}

RGB色クラス

// rgb_color.hpp
#include <cstdint>
#include <stdexcept>

class rgb_color255
{
public:
    constexpr rgb_color255() = default;
    constexpr rgb_color255(std::uint8_t r, std::uint8_t  g, std::uint8_t  b)
    {
        set_rgb(r, g, b);
    }
    constexpr std::uint8_t get_r() const noexcept
    {
        return r;
    }
    constexpr std::uint8_t get_g() const noexcept
    {
        return g;
    }
    constexpr std::uint8_t get_b() const noexcept
    {
        return b;
    }
    constexpr void set_r(std::uint8_t value)
    {
        r = value;
    }
    constexpr void set_g(std::uint8_t value)
    {
        g = value;
    }
    constexpr void set_b(std::uint8_t value)
    {
        b = value;
    }

    constexpr void get_rgb(std::uint8_t& r, std::uint8_t& g, std::uint8_t& b) const noexcept
    {
        r = this->r;
        g = this->g;
        b = this->b;
    }
    constexpr void set_rgb(std::uint8_t r, std::uint8_t g, std::uint8_t b)
    {
        set_r(r);
        set_g(g);
        set_b(b);
    }

    constexpr bool operator==(const rgb_color255& other)
    {
        return r == other.r && g == other.g && b == other.b;
    }
    constexpr bool operator!=(const rgb_color255& other)
    {
        return !(*this == other);
    }

private:
    std::uint8_t r;
    std::uint8_t g;
    std::uint8_t b;
};

class rgb_color1f
{
public:
    constexpr rgb_color1f() = default;
    rgb_color1f(float r, float g, float b)
    {
        set_rgb(r, g, b);
    }
    constexpr float get_r() const noexcept
    {
        return r;
    }
    constexpr float get_g() const noexcept
    {
        return g;
    }
    constexpr float get_b() const noexcept
    {
        return b;
    }
    void set_r(float value)
    {
        if (!(0 <= value && value <= 1))
            throw_out_of_range();
        r = value;
    }
    void set_g(float value)
    {
        if (!(0 <= value && value <= 1))
            throw_out_of_range();
        g = value;
    }
    void set_b(float value)
    {
        if (!(0 <= value && value <= 1))
            throw_out_of_range();
        b = value;
    }
    constexpr void set_r_nocheck(float value) noexcept
    {
        r = value;
    }
    constexpr void set_g_nocheck(float value) noexcept
    {
        g = value;
    }
    constexpr void set_b_nocheck(float value) noexcept
    {
        b = value;
    }

    constexpr void get_rgb(float& r, float& g, float& b) const noexcept
    {
        r = this->r;
        g = this->g;
        b = this->b;
    }
    void set_rgb(float r, float g, float b)
    {
        set_r(r);
        set_g(g);
        set_b(b);
    }
    constexpr void set_rgb_nocheck(float r, float g, float b) noexcept
    {
        set_r_nocheck(r);
        set_g_nocheck(g);
        set_b_nocheck(b);
    }

    constexpr bool operator==(const rgb_color1f& other)
    {
        return r == other.r && g == other.g && b == other.b;
    }
    constexpr bool operator!=(const rgb_color1f& other)
    {
        return !(*this == other);
    }

private:
    float r;
    float g;
    float b;
    static void throw_out_of_range()
    {
        throw std::out_of_range("");
    }
};

constexpr rgb_color1f to_rgb_color1f(const rgb_color255& source) noexcept
{
    rgb_color1f rgb;
    rgb.set_rgb_nocheck(
        source.get_r() / 255.0f,
        source.get_g() / 255.0f,
        source.get_b() / 255.0f);
    return rgb;
}

constexpr rgb_color255 to_rgb_color255(const rgb_color1f& source) noexcept
{
    return rgb_color255(
        static_cast<std::uint8_t>(source.get_r() * 255),
        static_cast<std::uint8_t>(source.get_g() * 255),
        static_cast<std::uint8_t>(source.get_b() * 255));
}

背景をHSV四角形で塗りつぶしたウィンドウを表示するコード

#include <bit>
#include <string>
#include "cylindrical_hsv_color.hpp"

#define STRICT
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>

const std::wstring_view MAINWINDOW_CLASSNAME{ L"MainWindow" };
const std::wstring_view MAINWINDOW_WINDOWNAME{ L"hsv_color_square" };

LRESULT CALLBACK MainWindow_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void MainWindow_OnClose(HWND hwnd);
void MainWindow_OnPaint(HWND hwnd);
HBITMAP CreateHSVSquareDIBSectionWithV(int w, int h, float v);

inline constexpr long GetWidth(const RECT& rc) noexcept { return rc.right - rc.left; }
inline constexpr long GetHeight(const RECT& rc) noexcept { return rc.bottom - rc.top; }

HWND g_hwndMain = nullptr;

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int cmdShow)
{
    WNDCLASSEXW wcex{ sizeof(wcex) };
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
    wcex.hIcon = LoadIconW(nullptr, IDI_APPLICATION);
    wcex.lpfnWndProc = MainWindow_WndProc;
    wcex.lpszClassName = MAINWINDOW_CLASSNAME.data();
    if (!RegisterClassExW(&wcex))
        return -1;

    auto hwnd = CreateWindowExW(
        WS_EX_OVERLAPPEDWINDOW, MAINWINDOW_CLASSNAME.data(), MAINWINDOW_WINDOWNAME.data(),
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        nullptr, nullptr, nullptr, nullptr);
    ShowWindow(hwnd, cmdShow);
    UpdateWindow(hwnd);

    MSG msg;
    int ret;
    while ((ret = GetMessageW(&msg, nullptr, 0, 0)) != 0)
    {
        if (ret == -1)
            break;
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return ret;
}

LRESULT CALLBACK MainWindow_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CLOSE:
        MainWindow_OnClose(hwnd);
        return 0;
    case WM_PAINT:
        MainWindow_OnPaint(hwnd);
        return 0;
    default:
        return DefWindowProcW(hwnd, msg, wParam, lParam);
    }
}

void MainWindow_OnClose(HWND hwnd)
{
    PostQuitMessage(0);
}

void MainWindow_OnPaint(HWND hwnd)
{
    PAINTSTRUCT ps;
    BeginPaint(hwnd, &ps);

    RECT rcClient;
    GetClientRect(hwnd, &rcClient);
    auto w = GetWidth(rcClient);
    auto h = GetHeight(rcClient);
    auto hbmp = CreateHSVSquareDIBSectionWithV(w, h, 1);

    auto hdcBmp = CreateCompatibleDC(ps.hdc);
    SelectObject(hdcBmp, hbmp);
    BitBlt(ps.hdc, 0, 0, w, h, hdcBmp, 0, 0, SRCCOPY);
    DeleteDC(hdcBmp);
    DeleteObject(hbmp);

    EndPaint(hwnd, &ps);
}

HBITMAP CreateHSVSquareDIBSectionWithV(int w, int h, float v)
{
    BITMAPINFOHEADER bih{ sizeof(bih), w, h, 1, 32 };
    LPBYTE pixels = nullptr;
    auto hbmp = CreateDIBSection(
        nullptr, std::bit_cast<LPBITMAPINFO>(&bih),
        DIB_RGB_COLORS, std::bit_cast<void**>(&pixels), nullptr, 0);
    auto x0 = 0;
    auto dx = 360.0f / w;
    auto dy = 1.0f / h;
    for (int y = 0; y < h; ++y)
    {
        auto ydy = y * dy;
        for (int x = 0; x < w; ++x)
        {
            cylindrical_hsv_color_f hsv(x * dx, ydy, v);
            auto rgb = to_rgb_color255(to_rgb_color1f(hsv));
            pixels[x0] = rgb.get_b();   // B
            pixels[++x0] = rgb.get_g(); // G
            pixels[++x0] = rgb.get_r(); // R
            x0 += 2;
        }
    }
    return hbmp;
}