raycaster/amt/pixel.hpp

582 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef AMT_PIXEL_HPP
#define AMT_PIXEL_HPP
#include "matrix.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <stdexcept>
namespace amt {
enum class PixelFormat {
rgba,
abgr,
rgb ,
bgr ,
ga , // gray scale and alpha
ag , // alpha and gray scale
g // gray scale
};
inline static constexpr auto get_pixel_format_from_channel(std::size_t c, bool little_endian = false) -> PixelFormat {
switch (c) {
case 1: return PixelFormat::g;
case 2: return little_endian ? PixelFormat::ag : PixelFormat::ga;
case 3: return little_endian ? PixelFormat::bgr : PixelFormat::rgb;
case 4: return little_endian ? PixelFormat::abgr : PixelFormat::abgr;
}
throw std::runtime_error(std::string("get_pixel_format_from_channel: unknown channel ") + std::to_string(c));
}
namespace detail {
static constexpr auto compare_float(float l, float r) noexcept -> bool {
return std::abs(l - r) < std::numeric_limits<float>::epsilon();
}
} // namespace detail
enum class BlendMode {
normal,
multiply,
screen,
overlay,
darken,
lighten,
colorDodge,
colorBurn,
hardLight,
softLight,
difference,
exclusion
};
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
constexpr RGBA() noexcept = default;
constexpr RGBA(RGBA const&) noexcept = default;
constexpr RGBA(RGBA &&) noexcept = default;
constexpr RGBA& operator=(RGBA const&) noexcept = default;
constexpr RGBA& operator=(RGBA &&) noexcept = default;
constexpr ~RGBA() noexcept = default;
constexpr RGBA(pixel_t r, pixel_t g, pixel_t b, pixel_t a = 0xff) noexcept
: r(r)
, g(g)
, b(b)
, a(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: 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 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);
return RGBA(
to_pixel(nr),
to_pixel(ng),
to_pixel(nb),
alpha
);
}
private:
static constexpr auto normalize(pixel_t p) noexcept -> float {
return float(p) / 255;
}
static constexpr auto to_pixel(float p) noexcept -> pixel_t {
return static_cast<pixel_t>(std::clamp(p, 0.f, 1.f) * 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 {
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 {
// Cb + Cs -(Cb x Cs)
return b + s - (b * s);
};
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));
};
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);
};
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);
}
};
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 {
// 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);
}
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;
};
auto bf = fn(bg, fg);
return mix_helper(bg, bf, alpha);
}
}
}
};
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%
constexpr HSLA() noexcept = default;
constexpr HSLA(HSLA const&) noexcept = default;
constexpr HSLA(HSLA &&) noexcept = default;
constexpr HSLA& operator=(HSLA const&) noexcept = default;
constexpr HSLA& operator=(HSLA &&) noexcept = default;
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)
{}
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({tr, tg, tb});
auto max = std::max({tr, tg, tb});
auto c = max - min;
float hue = 0;
float s = 0;
auto l = (max + min) / 2;
if (!detail::compare_float(c, 0)) {
auto temp_max = std::max({color.r, color.g, color.b});
if (temp_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) {
auto seg = (tb - tr) / c;
auto shift = 120.f / 60.f;
hue = seg + shift;
} else {
auto seg = (tr - tg) / c;
auto shift = 240.f / 60.f;
hue = seg + shift;
}
s = c / (1 - std::abs(2 * l - 1));
}
hue = hue * 60.f + 360.f;
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;
}
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));
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)
);
}
constexpr auto blend(HSLA color, BlendMode mode) const noexcept -> HSLA {
auto lhs = RGBA(*this);
auto rhs = RGBA(color);
return HSLA(lhs.blend(rhs, mode));
}
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 convert_hue(float p, float q, float t) noexcept -> float {
if (t < 0) t += 1;
if (t > 1) t -= 1;
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;
}
};
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;
} 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;
} else if constexpr (F == PixelFormat::rgb) {
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;
} else if constexpr (F == PixelFormat::ga) {
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;
} else {
out_ptr[0] = color.r;
}
}
template <PixelFormat F>
inline static constexpr auto parse_pixel_helper(std::uint8_t const* in_ptr) noexcept -> RGBA {
if constexpr (F == PixelFormat::rgba) {
return {
in_ptr[0],
in_ptr[1],
in_ptr[2],
in_ptr[3]
};
} else if constexpr (F == PixelFormat::abgr) {
return {
in_ptr[3],
in_ptr[2],
in_ptr[1],
in_ptr[0]
};
} else if constexpr (F == PixelFormat::rgb) {
return {
in_ptr[0],
in_ptr[1],
in_ptr[2],
0xff
};
} else if constexpr (F == PixelFormat::bgr) {
return {
in_ptr[2],
in_ptr[1],
in_ptr[0],
0xff
};
} else if constexpr (F == PixelFormat::ga) {
return {
in_ptr[0],
in_ptr[1],
};
} else if constexpr (F == PixelFormat::ag) {
return {
in_ptr[1],
in_ptr[0],
};
} else {
return { in_ptr[0], 0xff };
}
}
} // namespace detail
inline static constexpr auto get_pixel_format_channel(PixelFormat format) noexcept -> std::size_t {
switch (format) {
case PixelFormat::rgba: case PixelFormat::abgr: return 4u;
case PixelFormat::rgb: case PixelFormat::bgr: return 3u;
case PixelFormat::ga: case PixelFormat::ag: return 2u;
case PixelFormat::g: return 1u;
}
assert(false && "unreachable");
}
struct PixelBuf {
private:
public:
using value_type = RGBA;
using base_type = Matrix<value_type>;
using pointer = typename base_type::pointer;
using const_pointer = typename base_type::const_pointer;
using reference = typename base_type::reference;
using const_reference = typename base_type::const_reference;
using iterator = typename base_type::iterator;
using const_iterator = typename base_type::const_iterator;
using reverse_iterator = typename base_type::reverse_iterator;
using const_reverse_iterator = typename base_type::const_reverse_iterator;
using difference_type = typename base_type::difference_type;
using size_type = typename base_type::size_type;
PixelBuf(size_type r, size_type c)
: m_data(r, c)
{}
PixelBuf(size_type r, size_type c, RGBA color)
: m_data(r, c, color)
{}
PixelBuf(std::uint8_t const* in, size_type r, size_type c, PixelFormat format = PixelFormat::rgba)
: PixelBuf(r, c)
{
assert(in != nullptr);
switch (format) {
case PixelFormat::rgba: from_helper<PixelFormat::rgba>(in, data(), size()); break;
case PixelFormat::abgr: from_helper<PixelFormat::abgr>(in, data(), size()); break;
case PixelFormat::rgb: from_helper<PixelFormat::rgb >(in, data(), size()); break;
case PixelFormat::bgr: from_helper<PixelFormat::bgr >(in, data(), size()); break;
case PixelFormat::ga: from_helper<PixelFormat::ga >(in, data(), size()); break;
case PixelFormat::ag: from_helper<PixelFormat::ag >(in, data(), size()); break;
case PixelFormat::g: from_helper<PixelFormat::g >(in, data(), size()); break;
}
}
PixelBuf(PixelBuf const&) = default;
PixelBuf(PixelBuf &&) noexcept = default;
PixelBuf& operator=(PixelBuf const&) = default;
PixelBuf& operator=(PixelBuf &&) noexcept = default;
~PixelBuf() = default;
constexpr auto size() const noexcept -> size_type { return m_data.size(); }
constexpr auto rows() const noexcept -> size_type { return m_data.rows(); }
constexpr auto cols() const noexcept -> size_type { return m_data.cols(); }
constexpr auto data() noexcept -> pointer { return m_data.data(); }
constexpr auto data() const noexcept -> const_pointer { return m_data.data(); }
auto to_raw_buf() noexcept -> std::uint8_t* { return reinterpret_cast<std::uint8_t*>(data()); }
auto to_raw_buf() const noexcept -> std::uint8_t const* { return reinterpret_cast<std::uint8_t const*>(data()); }
constexpr auto raw_buf_size() const noexcept { return size() * sizeof(RGBA); }
constexpr auto begin() noexcept -> iterator { return m_data.begin(); }
constexpr auto end() noexcept -> iterator { return m_data.end(); }
constexpr auto begin() const noexcept -> const_iterator { return m_data.begin(); }
constexpr auto end() const noexcept -> const_iterator { return m_data.end(); }
constexpr auto rbegin() noexcept -> reverse_iterator { return m_data.rbegin(); }
constexpr auto rend() noexcept -> reverse_iterator { return m_data.rend(); }
constexpr auto rbegin() const noexcept -> const_reverse_iterator { return m_data.rbegin(); }
constexpr auto rend() const noexcept -> const_reverse_iterator { return m_data.rend(); }
constexpr auto operator[](size_type r) noexcept { return m_data[r]; }
constexpr auto operator[](size_type r) const noexcept { return m_data[r]; }
constexpr auto operator()(size_type r, size_type c) noexcept -> reference { return m_data(r, c); }
constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference { return m_data(r, c); }
constexpr auto fill(RGBA color) noexcept -> void {
std::fill(begin(), end(), color);
}
constexpr auto copy_to(std::uint8_t* out, PixelFormat format = PixelFormat::rgba) const noexcept {
assert(out != nullptr);
switch (format) {
case PixelFormat::rgba: copy_to_helper<PixelFormat::rgba>(data(), out, size());return;
case PixelFormat::abgr: copy_to_helper<PixelFormat::abgr>(data(), out, size());return;
case PixelFormat::rgb: copy_to_helper<PixelFormat::rgb >(data(), out, size());return;
case PixelFormat::bgr: copy_to_helper<PixelFormat::bgr >(data(), out, size());return;
case PixelFormat::ga: copy_to_helper<PixelFormat::ga >(data(), out, size());return;
case PixelFormat::ag: copy_to_helper<PixelFormat::ag >(data(), out, size());return;
case PixelFormat::g: copy_to_helper<PixelFormat::g >(data(), out, size());return;
}
assert(false && "unreachable");
}
private:
template <PixelFormat F>
constexpr auto copy_to_helper(const_pointer in, std::uint8_t* out, size_type size) const noexcept -> void {
constexpr auto channels = get_pixel_format_channel(F);
for (auto i = size_type{}; i < size; ++i) {
detail::parse_pixel_helper<F>(in[i], out + i * channels);
}
}
template <PixelFormat F>
constexpr auto from_helper(std::uint8_t const* in, pointer out, size_type size) const noexcept -> void {
constexpr auto channels = get_pixel_format_channel(F);
for (auto i = size_type{}; i < size; ++i) {
out[i] = detail::parse_pixel_helper<F>(in + i * channels);
}
}
private:
base_type m_data;
};
} // namespace amt
#include <format>
namespace std {
template <>
struct formatter<amt::RGBA> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
auto format(amt::RGBA const& color, auto& ctx) const {
return format_to(ctx.out(), "rgba({}, {}, {}, {})", color.r, color.g, color.b, color.a);
}
};
template <>
struct formatter<amt::HSLA> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
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);
}
};
template <>
struct formatter<amt::PixelBuf> {
bool hsla = false;
constexpr auto parse(format_parse_context& ctx) {
auto it = ctx.begin();
while (it != ctx.end() && *it != '}') {
if (*it == 'h') hsla = true;
++it;
}
return it;
}
auto format(amt::PixelBuf const& buf, auto& ctx) const {
std::string s = "[\n";
for (auto r = std::size_t{}; r < buf.rows(); ++r) {
for (auto c = std::size_t{}; c < buf.cols(); ++c) {
auto color = buf(r, c);
if (hsla) s += std::format("{}, ", amt::HSLA(color));
else s += std::format("{}, ", color);
}
s += '\n';
}
s += "]";
return format_to(ctx.out(), "{}", s);
}
};
} // namespace std
#endif // AMT_PIXEL_HPP