You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
583 lines
18 KiB
583 lines
18 KiB
2 months ago
|
#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
|