potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

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;
}