|
|
|
@ -3,11 +3,14 @@ |
|
|
|
|
|
|
|
|
|
#include "matrix.hpp" |
|
|
|
|
#include <algorithm> |
|
|
|
|
#include <bit> |
|
|
|
|
#include <cmath> |
|
|
|
|
#include <cstddef> |
|
|
|
|
#include <cstdint> |
|
|
|
|
#include <limits> |
|
|
|
|
#include <string> |
|
|
|
|
#include <stdexcept> |
|
|
|
|
#include <type_traits> |
|
|
|
|
|
|
|
|
|
namespace amt { |
|
|
|
|
|
|
|
|
@ -54,74 +57,87 @@ namespace amt { |
|
|
|
|
|
|
|
|
|
struct RGBA { |
|
|
|
|
using pixel_t = std::uint8_t; |
|
|
|
|
pixel_t r{}; // 0-255
|
|
|
|
|
pixel_t g{}; // 0-255
|
|
|
|
|
pixel_t b{}; // 0-255
|
|
|
|
|
pixel_t a{}; // 0-255
|
|
|
|
|
using pixels_t = std::uint32_t; |
|
|
|
|
|
|
|
|
|
constexpr RGBA() noexcept = default; |
|
|
|
|
constexpr RGBA(RGBA const&) noexcept = default; |
|
|
|
|
constexpr RGBA(RGBA &&) noexcept = default; |
|
|
|
|
RGBA& operator=(RGBA const& other) noexcept { |
|
|
|
|
// HACK: clang was unable to optimize the copy using a single move instruction.
|
|
|
|
|
auto& self = *reinterpret_cast<std::uint32_t*>(this); |
|
|
|
|
auto color = *reinterpret_cast<std::uint32_t const*>(&other); |
|
|
|
|
self = color; |
|
|
|
|
return *this; |
|
|
|
|
} |
|
|
|
|
RGBA& operator=(RGBA && other) noexcept { |
|
|
|
|
// HACK: clang was unable to optimize the copy using a single move instruction
|
|
|
|
|
auto& self = *reinterpret_cast<std::uint32_t*>(this); |
|
|
|
|
auto color = *reinterpret_cast<std::uint32_t const*>(&other); |
|
|
|
|
self = color; |
|
|
|
|
return *this; |
|
|
|
|
} |
|
|
|
|
constexpr RGBA& operator=(RGBA const& other) noexcept = default; |
|
|
|
|
constexpr RGBA& operator=(RGBA && other) noexcept = default; |
|
|
|
|
constexpr ~RGBA() noexcept = default; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE: Accepts RRGGBBAA
|
|
|
|
|
explicit constexpr RGBA(pixels_t color) noexcept |
|
|
|
|
: m_data({ .color = color }) |
|
|
|
|
{} |
|
|
|
|
|
|
|
|
|
constexpr RGBA(pixel_t r, pixel_t g, pixel_t b, pixel_t a = 0xff) noexcept |
|
|
|
|
: r(r) |
|
|
|
|
, g(g) |
|
|
|
|
, b(b) |
|
|
|
|
, a(a) |
|
|
|
|
: m_data({ .rgba = { r, g, b, a } }) |
|
|
|
|
{} |
|
|
|
|
|
|
|
|
|
constexpr RGBA(pixel_t color, pixel_t a = 0xff) noexcept |
|
|
|
|
: RGBA(color, color, color, a) |
|
|
|
|
{} |
|
|
|
|
|
|
|
|
|
// NOTE: RRGGBBAA
|
|
|
|
|
constexpr static auto from_hex(std::uint32_t color) noexcept -> RGBA { |
|
|
|
|
return RGBA( |
|
|
|
|
((color >> (8 * 3)) & 0xff), |
|
|
|
|
((color >> (8 * 2)) & 0xff), |
|
|
|
|
((color >> (8 * 1)) & 0xff), |
|
|
|
|
((color >> (8 * 0)) & 0xff) |
|
|
|
|
); |
|
|
|
|
// NOTE: Returns RRGGBBAA
|
|
|
|
|
constexpr auto to_hex() const noexcept -> pixels_t { |
|
|
|
|
if constexpr (std::endian::native == std::endian::little) { |
|
|
|
|
auto r = static_cast<pixels_t>(this->r()); |
|
|
|
|
auto b = static_cast<pixels_t>(this->b()); |
|
|
|
|
auto g = static_cast<pixels_t>(this->g()); |
|
|
|
|
auto a = static_cast<pixels_t>(this->a()); |
|
|
|
|
return (r << (8 * 3)) | (g << (8 * 2)) | (b << (8 * 1)) | (a << (8 * 0)); |
|
|
|
|
} else { |
|
|
|
|
return m_data.color; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NOTE: RRGGBBAA
|
|
|
|
|
constexpr auto to_hex() const noexcept -> std::uint32_t { |
|
|
|
|
auto r = static_cast<std::uint32_t>(this->r); |
|
|
|
|
auto b = static_cast<std::uint32_t>(this->b); |
|
|
|
|
auto g = static_cast<std::uint32_t>(this->g); |
|
|
|
|
auto a = static_cast<std::uint32_t>(this->a); |
|
|
|
|
return (r << (8 * 3)) | (g << (8 * 2)) | (b << (8 * 1)) | (a << (8 * 0)); |
|
|
|
|
|
|
|
|
|
constexpr auto r() const noexcept -> pixel_t { return m_data.rgba.r; } |
|
|
|
|
constexpr auto g() const noexcept -> pixel_t { return m_data.rgba.g; } |
|
|
|
|
constexpr auto b() const noexcept -> pixel_t { return m_data.rgba.b; } |
|
|
|
|
constexpr auto a() const noexcept -> pixel_t { return m_data.rgba.a; } |
|
|
|
|
|
|
|
|
|
constexpr auto r() noexcept -> pixel_t& { return m_data.rgba.r; } |
|
|
|
|
constexpr auto g() noexcept -> pixel_t& { return m_data.rgba.g; } |
|
|
|
|
constexpr auto b() noexcept -> pixel_t& { return m_data.rgba.b; } |
|
|
|
|
constexpr auto a() noexcept -> pixel_t& { return m_data.rgba.a; } |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @returns the value is between 0 and 1 |
|
|
|
|
*/ |
|
|
|
|
constexpr auto brightness() const noexcept -> float { |
|
|
|
|
// 0.299*R + 0.587*G + 0.114*B
|
|
|
|
|
auto tr = normalize(r()); |
|
|
|
|
auto tg = normalize(g()); |
|
|
|
|
auto tb = normalize(b()); |
|
|
|
|
return (0.299 * tr + 0.587 * tg + 0.114 * tb);
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
constexpr auto blend(RGBA color, BlendMode mode) const noexcept -> RGBA { |
|
|
|
|
auto ab = normalize(a); |
|
|
|
|
auto as = normalize(color.a); |
|
|
|
|
// αs x 1 + αb x (1 – αs)
|
|
|
|
|
auto alpha = to_pixel(as + ab * (1 - as)); |
|
|
|
|
auto nr = blend_helper(normalize(r), normalize(color.r), normalize(a), mode); |
|
|
|
|
auto ng = blend_helper(normalize(g), normalize(color.g), normalize(a), mode); |
|
|
|
|
auto nb = blend_helper(normalize(b), normalize(color.b), normalize(a), mode); |
|
|
|
|
template <typename T> |
|
|
|
|
requires std::is_arithmetic_v<T> |
|
|
|
|
constexpr auto operator/(T val) const noexcept { |
|
|
|
|
auto d = static_cast<float>(val); |
|
|
|
|
auto tr = float(r()); |
|
|
|
|
auto tg = float(g()); |
|
|
|
|
auto tb = float(b()); |
|
|
|
|
return RGBA( |
|
|
|
|
static_cast<pixel_t>(tr / d), |
|
|
|
|
static_cast<pixel_t>(tg / d), |
|
|
|
|
static_cast<pixel_t>(tb / d), |
|
|
|
|
a() |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
template <typename T> |
|
|
|
|
requires std::is_arithmetic_v<T> |
|
|
|
|
constexpr auto operator*(T val) const noexcept { |
|
|
|
|
auto d = static_cast<float>(val); |
|
|
|
|
return RGBA( |
|
|
|
|
to_pixel(nr), |
|
|
|
|
to_pixel(ng), |
|
|
|
|
to_pixel(nb), |
|
|
|
|
alpha |
|
|
|
|
static_cast<pixel_t>(r() * d), |
|
|
|
|
static_cast<pixel_t>(g() * d), |
|
|
|
|
static_cast<pixel_t>(b() * d), |
|
|
|
|
a() |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
@ -130,118 +146,253 @@ namespace amt { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static constexpr auto to_pixel(float p) noexcept -> pixel_t { |
|
|
|
|
return static_cast<pixel_t>(std::clamp(p, 0.f, 1.f) * 255); |
|
|
|
|
return static_cast<pixel_t>(p * 255); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static constexpr auto apply_op(pixel_t l, pixel_t r, auto&& fn) noexcept -> pixel_t { |
|
|
|
|
return RGBA::to_pixel(fn(RGBA::normalize(l), RGBA::normalize(r))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static constexpr auto blend_helper(float bg, float fg, float alpha, BlendMode mode) noexcept -> float { |
|
|
|
|
template <BlendMode M> |
|
|
|
|
static constexpr auto blend_helper() noexcept { |
|
|
|
|
constexpr auto mix_helper = [](float s, float b, float a) -> float { |
|
|
|
|
// (1 - αb) x Cs + αb x B(Cb, Cs)
|
|
|
|
|
return (1 - a) * s + a * b; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
switch (mode) { |
|
|
|
|
case BlendMode::normal: return mix_helper(bg, fg, alpha); |
|
|
|
|
case BlendMode::multiply: { |
|
|
|
|
return mix_helper(bg, bg * fg, alpha); |
|
|
|
|
} |
|
|
|
|
case BlendMode::screen: { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
if constexpr (M == BlendMode::normal) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, fg, alpha); }; |
|
|
|
|
} else if constexpr (M == BlendMode::multiply) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, bg * fg, alpha); }; |
|
|
|
|
} else if constexpr (M == BlendMode::screen) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { |
|
|
|
|
// Cb + Cs -(Cb x Cs)
|
|
|
|
|
return b + s - (b * s); |
|
|
|
|
auto bf = bg + fg - (bg * fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
}; |
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
} |
|
|
|
|
case BlendMode::overlay: { |
|
|
|
|
// HardLight(Cs, Cb)
|
|
|
|
|
auto hl = blend_helper(bg, fg, alpha, BlendMode::hardLight); |
|
|
|
|
return mix_helper(bg, hl, alpha); |
|
|
|
|
} |
|
|
|
|
case BlendMode::darken: return mix_helper(bg, std::min(bg, fg), alpha); |
|
|
|
|
case BlendMode::lighten: return mix_helper(bg, std::max(bg, fg), alpha); |
|
|
|
|
case BlendMode::colorDodge: { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
if (b == 0) return 0; |
|
|
|
|
if (s == 255) return 255; |
|
|
|
|
return std::min(1.f, b / (1.f - s)); |
|
|
|
|
} else if constexpr (M == BlendMode::overlay) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha, auto&& hard_light_fn, auto&& multiply_fn, auto&& screen_fn) { |
|
|
|
|
// HardLight(Cs, Cb)
|
|
|
|
|
auto hl = hard_light_fn(bg, fg, alpha, multiply_fn, screen_fn); |
|
|
|
|
return mix_helper(bg, hl, alpha); |
|
|
|
|
}; |
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
} |
|
|
|
|
case BlendMode::colorBurn: { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
if (b == 255) return 255; |
|
|
|
|
if (s == 0) return 0; |
|
|
|
|
return 1.f - std::min(1.f, (1.f - b) / s); |
|
|
|
|
} else if constexpr (M == BlendMode::darken) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { |
|
|
|
|
return mix_helper(bg, std::min(bg, fg), alpha); |
|
|
|
|
}; |
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
} |
|
|
|
|
case BlendMode::hardLight: { |
|
|
|
|
constexpr auto fn = [](float b, float s, float a) -> float { |
|
|
|
|
if (s <= 0.5f) { |
|
|
|
|
return RGBA::blend_helper(b, 2.f * s, a, BlendMode::multiply); |
|
|
|
|
} else { |
|
|
|
|
return RGBA::blend_helper(b, 2.f * s - 1.f, a, BlendMode::screen); |
|
|
|
|
} |
|
|
|
|
} else if constexpr (M == BlendMode::lighten) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { |
|
|
|
|
return mix_helper(bg, std::max(bg, fg), alpha); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
auto bf = fn(bg, fg, alpha); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
} |
|
|
|
|
case BlendMode::softLight: { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
if (s <= 0.5f) { |
|
|
|
|
// B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
|
|
|
|
|
return b - (1.f - 2.f * s) * b * (1 - b); |
|
|
|
|
} else { |
|
|
|
|
float d{}; |
|
|
|
|
|
|
|
|
|
if (b <= 0.5f) { |
|
|
|
|
// D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
|
|
|
|
|
d = ((16 * b - 12) * b + 4) * b; |
|
|
|
|
} else if constexpr (M == BlendMode::colorDodge) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
if (b == 0) return 0; |
|
|
|
|
if (s == 255) return 255; |
|
|
|
|
return std::min(1.f, b / (1.f - s)); |
|
|
|
|
}; |
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
}; |
|
|
|
|
} else if constexpr (M == BlendMode::colorBurn) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
if (b == 255) return 255; |
|
|
|
|
if (s == 0) return 0; |
|
|
|
|
return 1.f - std::min(1.f, (1.f - b) / s); |
|
|
|
|
}; |
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
}; |
|
|
|
|
} else if constexpr (M == BlendMode::hardLight) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha, auto&& multiply_fn, auto&& screen_fn) { |
|
|
|
|
auto fn = [&multiply_fn, &screen_fn](float b, float s, float a) -> float { |
|
|
|
|
if (s <= 0.5f) { |
|
|
|
|
return multiply_fn(b, 2.f * s, a); |
|
|
|
|
} else { |
|
|
|
|
// D(Cb) = sqrt(Cb)
|
|
|
|
|
d = std::sqrt(b); |
|
|
|
|
return screen_fn(b, 2.f * s - 1.f, a); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
|
|
|
|
|
return b + (2 * s - 1) * (d - b); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
auto bf = fn(bg, fg, alpha); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
} |
|
|
|
|
case BlendMode::difference: { |
|
|
|
|
// B(Cb, Cs) = | Cb - Cs |
|
|
|
|
|
return mix_helper(bg, (bg > fg ? (bg - fg) : (fg - bg)), alpha); |
|
|
|
|
} |
|
|
|
|
case BlendMode::exclusion: { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
// B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs
|
|
|
|
|
return b + s - 2 * b * s; |
|
|
|
|
} else if constexpr (M == BlendMode::softLight) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
if (s <= 0.5f) { |
|
|
|
|
// B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
|
|
|
|
|
return b - (1.f - 2.f * s) * b * (1 - b); |
|
|
|
|
} else { |
|
|
|
|
float d{}; |
|
|
|
|
|
|
|
|
|
if (b <= 0.5f) { |
|
|
|
|
// D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
|
|
|
|
|
d = ((16 * b - 12) * b + 4) * b; |
|
|
|
|
} else { |
|
|
|
|
// D(Cb) = sqrt(Cb)
|
|
|
|
|
d = std::sqrt(b); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
|
|
|
|
|
return b + (2 * s - 1) * (d - b); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
}; |
|
|
|
|
} else if constexpr (M == BlendMode::difference) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { |
|
|
|
|
// B(Cb, Cs) = | Cb - Cs |
|
|
|
|
|
return mix_helper(bg, (bg > fg ? (bg - fg) : (fg - bg)), alpha); |
|
|
|
|
}; |
|
|
|
|
} else if constexpr (M == BlendMode::exclusion) { |
|
|
|
|
return [mix_helper](float bg, float fg, float alpha) { |
|
|
|
|
constexpr auto fn = [](float b, float s) -> float { |
|
|
|
|
// B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs
|
|
|
|
|
return b + s - 2 * b * s; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
auto bf = fn(bg, fg); |
|
|
|
|
return mix_helper(bg, bf, alpha); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
public: |
|
|
|
|
template <BlendMode M> |
|
|
|
|
constexpr auto blend(RGBA color) const noexcept -> RGBA { |
|
|
|
|
auto ab = normalize(a()); |
|
|
|
|
auto as = normalize(color.a()); |
|
|
|
|
// αs x 1 + αb x (1 – αs)
|
|
|
|
|
auto alpha = to_pixel(as + ab * (1 - as)); |
|
|
|
|
auto lr = normalize(r()); |
|
|
|
|
auto lg = normalize(g()); |
|
|
|
|
auto lb = normalize(b()); |
|
|
|
|
auto rr = normalize(color.r()); |
|
|
|
|
auto rg = normalize(color.g()); |
|
|
|
|
auto rb = normalize(color.b()); |
|
|
|
|
|
|
|
|
|
auto nr = 0.f; |
|
|
|
|
auto ng = 0.f; |
|
|
|
|
auto nb = 0.f; |
|
|
|
|
|
|
|
|
|
if constexpr (M == BlendMode::normal) { |
|
|
|
|
auto fn = blend_helper<BlendMode::normal>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::multiply) { |
|
|
|
|
auto fn = blend_helper<BlendMode::multiply>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::screen) { |
|
|
|
|
auto fn = blend_helper<BlendMode::screen>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::overlay) { |
|
|
|
|
auto fn = blend_helper<BlendMode::overlay>(); |
|
|
|
|
auto hard_light_fn = blend_helper<BlendMode::hardLight>(); |
|
|
|
|
auto multiply_fn = blend_helper<BlendMode::multiply>(); |
|
|
|
|
auto screen_fn = blend_helper<BlendMode::screen>(); |
|
|
|
|
nr = fn(lr, rr, ab, hard_light_fn, multiply_fn, screen_fn); |
|
|
|
|
ng = fn(lg, rg, ab, hard_light_fn, multiply_fn, screen_fn); |
|
|
|
|
nb = fn(lb, rb, ab, hard_light_fn, multiply_fn, screen_fn); |
|
|
|
|
} else if constexpr (M == BlendMode::darken) { |
|
|
|
|
auto fn = blend_helper<BlendMode::darken>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::lighten) { |
|
|
|
|
auto fn = blend_helper<BlendMode::lighten>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::colorDodge) { |
|
|
|
|
auto fn = blend_helper<BlendMode::colorDodge>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::colorBurn) { |
|
|
|
|
auto fn = blend_helper<BlendMode::colorDodge>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::hardLight) { |
|
|
|
|
auto fn = blend_helper<BlendMode::hardLight>(); |
|
|
|
|
auto multiply_fn = blend_helper<BlendMode::multiply>(); |
|
|
|
|
auto screen_fn = blend_helper<BlendMode::screen>(); |
|
|
|
|
nr = fn(lr, rr, ab, multiply_fn, screen_fn); |
|
|
|
|
ng = fn(lg, rg, ab, multiply_fn, screen_fn); |
|
|
|
|
nb = fn(lb, rb, ab, multiply_fn, screen_fn); |
|
|
|
|
} else if constexpr (M == BlendMode::softLight) { |
|
|
|
|
auto fn = blend_helper<BlendMode::softLight>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::difference) { |
|
|
|
|
auto fn = blend_helper<BlendMode::difference>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} else if constexpr (M == BlendMode::exclusion) { |
|
|
|
|
auto fn = blend_helper<BlendMode::exclusion>(); |
|
|
|
|
nr = fn(lr, rr, ab); |
|
|
|
|
ng = fn(lg, rg, ab); |
|
|
|
|
nb = fn(lb, rb, ab); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return RGBA( |
|
|
|
|
to_pixel(nr), |
|
|
|
|
to_pixel(ng), |
|
|
|
|
to_pixel(nb), |
|
|
|
|
alpha |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
constexpr auto blend(RGBA color, BlendMode mode) const noexcept -> RGBA { |
|
|
|
|
switch (mode) { |
|
|
|
|
case BlendMode::normal: return blend<BlendMode::normal>(color); |
|
|
|
|
case BlendMode::multiply: return blend<BlendMode::multiply>(color); |
|
|
|
|
case BlendMode::screen: return blend<BlendMode::screen>(color); |
|
|
|
|
case BlendMode::overlay: return blend<BlendMode::overlay>(color); |
|
|
|
|
case BlendMode::darken: return blend<BlendMode::darken>(color); |
|
|
|
|
case BlendMode::lighten: return blend<BlendMode::lighten>(color); |
|
|
|
|
case BlendMode::colorDodge: return blend<BlendMode::colorDodge>(color); |
|
|
|
|
case BlendMode::colorBurn: return blend<BlendMode::colorBurn>(color); |
|
|
|
|
case BlendMode::hardLight: return blend<BlendMode::hardLight>(color); |
|
|
|
|
case BlendMode::softLight: return blend<BlendMode::softLight>(color); |
|
|
|
|
case BlendMode::difference: return blend<BlendMode::difference>(color); |
|
|
|
|
case BlendMode::exclusion: return blend<BlendMode::exclusion>(color); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
|
union { |
|
|
|
|
struct { |
|
|
|
|
pixel_t r; |
|
|
|
|
pixel_t g; |
|
|
|
|
pixel_t b; |
|
|
|
|
pixel_t a; |
|
|
|
|
} rgba; |
|
|
|
|
pixels_t color; |
|
|
|
|
} m_data {}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
struct HSLA { |
|
|
|
|
using pixel_t = float; |
|
|
|
|
pixel_t h{}; // hue: 0-360
|
|
|
|
|
pixel_t s{}; // saturation: 0-100%
|
|
|
|
|
pixel_t l{}; // lightness: 0-100%
|
|
|
|
|
pixel_t a{}; // alpha: 0-100%
|
|
|
|
|
using pixels_t = float[4]; |
|
|
|
|
|
|
|
|
|
// ensures pixel to be in range
|
|
|
|
|
template <unsigned min, unsigned max> |
|
|
|
|
struct PixelWrapper { |
|
|
|
|
pixel_t& p; |
|
|
|
|
|
|
|
|
|
constexpr PixelWrapper& operator=(float val) noexcept { |
|
|
|
|
p = std::clamp(val, float(min), float(max));
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
constexpr operator pixel_t() const noexcept { return p; } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
constexpr HSLA() noexcept = default; |
|
|
|
|
constexpr HSLA(HSLA const&) noexcept = default; |
|
|
|
|
constexpr HSLA(HSLA &&) noexcept = default; |
|
|
|
@ -250,33 +401,29 @@ namespace amt { |
|
|
|
|
constexpr ~HSLA() noexcept = default; |
|
|
|
|
|
|
|
|
|
constexpr HSLA(pixel_t h, pixel_t s, pixel_t l, pixel_t a = 100) noexcept |
|
|
|
|
: h(h) |
|
|
|
|
, s(s) |
|
|
|
|
, l(l) |
|
|
|
|
, a(a) |
|
|
|
|
: m_data({ .hsla = { .h = h, .s = s, .l = l, .a = a } }) |
|
|
|
|
{} |
|
|
|
|
|
|
|
|
|
constexpr HSLA(RGBA color) noexcept { |
|
|
|
|
auto tr = float(color.r) / 255; |
|
|
|
|
auto tg = float(color.g) / 255; |
|
|
|
|
auto tb = float(color.b) / 255; |
|
|
|
|
auto ta = float(color.a) / 255; |
|
|
|
|
auto min = std::min({color.r(), color.g(), color.b()}); |
|
|
|
|
auto max = std::max({color.r(), color.g(), color.b()}); |
|
|
|
|
auto c = (max - min) / 255.f; |
|
|
|
|
|
|
|
|
|
auto min = std::min({tr, tg, tb}); |
|
|
|
|
auto max = std::max({tr, tg, tb}); |
|
|
|
|
auto c = max - min; |
|
|
|
|
auto tr = float(color.r()) / 255; |
|
|
|
|
auto tg = float(color.g()) / 255; |
|
|
|
|
auto tb = float(color.b()) / 255; |
|
|
|
|
auto ta = float(color.a()) / 255; |
|
|
|
|
|
|
|
|
|
float hue = 0; |
|
|
|
|
float s = 0; |
|
|
|
|
auto l = (max + min) / 2; |
|
|
|
|
auto l = ((max + min) / 2.f) / 255.f; |
|
|
|
|
|
|
|
|
|
if (!detail::compare_float(c, 0)) { |
|
|
|
|
auto temp_max = std::max({color.r, color.g, color.b}); |
|
|
|
|
if (temp_max == color.r) { |
|
|
|
|
if (min == max) { |
|
|
|
|
if (max == color.r()) { |
|
|
|
|
auto seg = (tg - tb) / c; |
|
|
|
|
auto shift = (seg < 0 ? 360.f : 0.f) / 60.f; |
|
|
|
|
hue = seg + shift; |
|
|
|
|
} else if (temp_max == color.g) { |
|
|
|
|
} else if (max == color.g()) { |
|
|
|
|
auto seg = (tb - tr) / c; |
|
|
|
|
auto shift = 120.f / 60.f; |
|
|
|
|
hue = seg + shift;
|
|
|
|
@ -292,27 +439,28 @@ namespace amt { |
|
|
|
|
auto q = static_cast<float>(static_cast<int>(hue / 360.f)); |
|
|
|
|
hue -= q * 360.f; |
|
|
|
|
|
|
|
|
|
this->h = hue; |
|
|
|
|
this->s = s * 100.f; |
|
|
|
|
this->l = l * 100.f; |
|
|
|
|
this->a = ta * 100.f; |
|
|
|
|
m_data.hsla.h = hue; |
|
|
|
|
m_data.hsla.s = s * 100.f; |
|
|
|
|
m_data.hsla.l = l * 100.f; |
|
|
|
|
m_data.hsla.a = ta * 100.f; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
constexpr operator RGBA() const noexcept { |
|
|
|
|
auto th = std::clamp(h, 0.f, 360.f) / 360; |
|
|
|
|
auto ts = std::clamp(s, 0.f, 100.f) / 100; |
|
|
|
|
auto tl = std::clamp(l, 0.f, 100.f) / 100; |
|
|
|
|
auto ta = std::clamp(a, 0.f, 100.f) / 100; |
|
|
|
|
if (detail::compare_float(ts, 0)) return RGBA(to_int(tl), to_int(ta)); |
|
|
|
|
auto ts = s() / 100.f; |
|
|
|
|
auto tl = l() / 100.f; |
|
|
|
|
auto ta = a() / 100.f; |
|
|
|
|
if (s() == 0) return RGBA(to_pixel(tl), to_pixel(ta)); |
|
|
|
|
|
|
|
|
|
auto th = h() / 360.f; |
|
|
|
|
|
|
|
|
|
float const q = tl < 0.5 ? tl * (1 + ts) : tl + ts - tl * ts; |
|
|
|
|
float const p = 2 * tl - q; |
|
|
|
|
|
|
|
|
|
return RGBA( |
|
|
|
|
to_int(convert_hue(p, q, th + 1.f / 3)), |
|
|
|
|
to_int(convert_hue(p, q, th)), |
|
|
|
|
to_int(convert_hue(p, q, th - 1.f / 3)), |
|
|
|
|
to_int(ta) |
|
|
|
|
to_pixel(convert_hue(p, q, th + 1.f / 3)), |
|
|
|
|
to_pixel(convert_hue(p, q, th)), |
|
|
|
|
to_pixel(convert_hue(p, q, th - 1.f / 3)), |
|
|
|
|
to_pixel(ta) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -321,51 +469,70 @@ namespace amt { |
|
|
|
|
auto rhs = RGBA(color); |
|
|
|
|
return HSLA(lhs.blend(rhs, mode)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
constexpr auto h() const noexcept -> pixel_t { return m_data.hsla.h; } |
|
|
|
|
constexpr auto s() const noexcept -> pixel_t { return m_data.hsla.s; } |
|
|
|
|
constexpr auto l() const noexcept -> pixel_t { return m_data.hsla.l; } |
|
|
|
|
constexpr auto a() const noexcept -> pixel_t { return m_data.hsla.a; } |
|
|
|
|
|
|
|
|
|
constexpr auto h() noexcept -> PixelWrapper<0, 360> { return { m_data.hsla.h }; } |
|
|
|
|
constexpr auto s() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.s }; } |
|
|
|
|
constexpr auto l() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.l }; } |
|
|
|
|
constexpr auto a() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.a }; } |
|
|
|
|
private: |
|
|
|
|
|
|
|
|
|
static constexpr auto to_int(float a, float max = 1) noexcept -> std::uint8_t { |
|
|
|
|
return static_cast<std::uint8_t>((a / max) * 255); |
|
|
|
|
static constexpr auto to_pixel(float a) noexcept -> RGBA::pixel_t { |
|
|
|
|
return static_cast<RGBA::pixel_t>(a * 255); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static constexpr auto convert_hue(float p, float q, float t) noexcept -> float { |
|
|
|
|
if (t < 0) t += 1; |
|
|
|
|
if (t > 1) t -= 1; |
|
|
|
|
t = t - (t > 1) + (t < 0); |
|
|
|
|
if (t * 6 < 1) return p + (q - p) * 6 * t; |
|
|
|
|
if (t * 2 < 1) return q; |
|
|
|
|
if (t * 3 < 2) return p + (q - p) * (2.f / 3 - t) * 6; |
|
|
|
|
return p; |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
|
union { |
|
|
|
|
struct { |
|
|
|
|
pixel_t h{}; // hue: 0-360
|
|
|
|
|
pixel_t s{}; // saturation: 0-100%
|
|
|
|
|
pixel_t l{}; // lightness: 0-100%
|
|
|
|
|
pixel_t a{}; // alpha: 0-100%
|
|
|
|
|
} hsla; |
|
|
|
|
pixels_t color; |
|
|
|
|
} m_data{}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
namespace detail { |
|
|
|
|
template <PixelFormat F> |
|
|
|
|
inline static constexpr auto parse_pixel_helper(RGBA color, std::uint8_t* out_ptr) noexcept { |
|
|
|
|
if constexpr (F == PixelFormat::rgba) { |
|
|
|
|
out_ptr[0] = color.r; |
|
|
|
|
out_ptr[1] = color.g; |
|
|
|
|
out_ptr[2] = color.b; |
|
|
|
|
out_ptr[3] = color.a; |
|
|
|
|
out_ptr[0] = color.r(); |
|
|
|
|
out_ptr[1] = color.g(); |
|
|
|
|
out_ptr[2] = color.b(); |
|
|
|
|
out_ptr[3] = color.a(); |
|
|
|
|
} else if constexpr (F == PixelFormat::abgr) { |
|
|
|
|
out_ptr[0] = color.a; |
|
|
|
|
out_ptr[1] = color.b; |
|
|
|
|
out_ptr[2] = color.g; |
|
|
|
|
out_ptr[3] = color.r; |
|
|
|
|
out_ptr[0] = color.a(); |
|
|
|
|
out_ptr[1] = color.b(); |
|
|
|
|
out_ptr[2] = color.g(); |
|
|
|
|
out_ptr[3] = color.r(); |
|
|
|
|
} else if constexpr (F == PixelFormat::rgb) { |
|
|
|
|
out_ptr[0] = color.r; |
|
|
|
|
out_ptr[1] = color.g; |
|
|
|
|
out_ptr[2] = color.b; |
|
|
|
|
out_ptr[0] = color.r(); |
|
|
|
|
out_ptr[1] = color.g(); |
|
|
|
|
out_ptr[2] = color.b(); |
|
|
|
|
} else if constexpr (F == PixelFormat::bgr) { |
|
|
|
|
out_ptr[0] = color.b; |
|
|
|
|
out_ptr[1] = color.g; |
|
|
|
|
out_ptr[2] = color.r; |
|
|
|
|
out_ptr[0] = color.b(); |
|
|
|
|
out_ptr[1] = color.g(); |
|
|
|
|
out_ptr[2] = color.r(); |
|
|
|
|
} else if constexpr (F == PixelFormat::ga) { |
|
|
|
|
out_ptr[0] = color.r; |
|
|
|
|
out_ptr[1] = color.a; |
|
|
|
|
out_ptr[0] = color.r(); |
|
|
|
|
out_ptr[1] = color.a(); |
|
|
|
|
} else if constexpr (F == PixelFormat::ag) { |
|
|
|
|
out_ptr[0] = color.a; |
|
|
|
|
out_ptr[1] = color.r; |
|
|
|
|
out_ptr[0] = color.a(); |
|
|
|
|
out_ptr[1] = color.r(); |
|
|
|
|
} else { |
|
|
|
|
out_ptr[0] = color.r; |
|
|
|
|
out_ptr[0] = color.r(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -548,7 +715,7 @@ namespace std { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto format(amt::RGBA const& color, auto& ctx) const { |
|
|
|
|
return format_to(ctx.out(), "rgba({}, {}, {}, {})", color.r, color.g, color.b, color.a); |
|
|
|
|
return format_to(ctx.out(), "rgba({}, {}, {}, {})", color.r(), color.g(), color.b(), color.a()); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -559,7 +726,7 @@ namespace std { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto format(amt::HSLA const& color, auto& ctx) const { |
|
|
|
|
return format_to(ctx.out(), "hsla({:.1f}deg, {:.1f}%, {:.1f}%, {:.1f}%)", color.h, color.s, color.l, color.a); |
|
|
|
|
return format_to(ctx.out(), "hsla({:.1f}deg, {:.1f}%, {:.1f}%, {:.1f}%)", color.h(), color.s(), color.l(), color.a()); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -592,4 +759,4 @@ namespace std { |
|
|
|
|
}; |
|
|
|
|
} // namespace std
|
|
|
|
|
|
|
|
|
|
#endif // AMT_PIXEL_HPP
|
|
|
|
|
#endif // AMT_PIXEL_HPP
|
|
|
|
|