42#if defined(__ARM_NEON)
55[[nodiscard]]
static consteval double consteval_cos(
double x, int32_t terms = 10) {
56 x = x - 6.283185307179586 *
static_cast<int32_t
>(x / 6.283185307179586);
59 const double x2 = x * x;
60 for (int32_t i = 1; i < terms; ++i) {
61 term *= -x2 / ((2 * i - 1) * (2 * i));
67[[nodiscard]]
static consteval double consteval_sin(
double x, int32_t terms = 10) {
68 x = x - 6.283185307179586 *
static_cast<int32_t
>(x / 6.283185307179586);
71 const double x2 = x * x;
72 for (int32_t i = 1; i < terms; ++i) {
73 term *= -x2 / ((2 * i) * (2 * i + 1));
79static constexpr float fast_sqrtf(
const float x) {
80 auto i = std::bit_cast<int32_t>(x);
81 const int k = i & 0x00800000;
84 i = 0x5ed9d098 - (i >> 1);
85 y = std::bit_cast<float>(i);
86 y = 2.33139729f * y * ((-x * y * y) + 1.07492042f);
88 i = 0x5f19d352 - (i >> 1);
89 y = std::bit_cast<float>(i);
90 y = 0.82420468f * y * ((-x * y * y) + 2.14996147f);
92 const float c = x * y;
93 const float r = ((y * -c) + 1.0f);
94 y = ((0.5f * c * r) + c);
98[[nodiscard]]
static constexpr float fast_exp2(
const float p) {
99 const float offset = (p < 0) ? 1.0f : 0.0f;
100 const float clipp = (p < -126) ? -126.0f : p;
101 const float z = clipp -
static_cast<float>(
static_cast<int32_t
>(clipp)) + offset;
102 return std::bit_cast<float>(
103 static_cast<uint32_t
>((1 << 23) * (clipp + 121.2740575f + 27.7280233f / (4.84252568f - z) - 1.49012907f * z)));
106[[nodiscard]]
static constexpr float fast_log2(
const float x) {
107 const auto xi = std::bit_cast<uint32_t>(x);
108 const auto xf = std::bit_cast<float>((xi & 0x007FFFFF) | 0x3f000000);
109 const float y =
static_cast<float>(xi) * 1.1920928955078125e-7f;
110 return y - 124.22551499f - 1.498030302f * xf - 1.72587999f / (0.3520887068f + xf);
113[[nodiscard]]
static constexpr float fast_pow(
const float x,
const float p) {
114 return fast_exp2(p * fast_log2(x));
117[[nodiscard]]
static constexpr float linear_to_srgb(
float c) {
118 if (c <= 0.0031308f) {
121 if (std::is_constant_evaluated()) {
122 return 1.055f * fast_pow(c, 1.0f / 2.4f) - 0.055f;
124 return 1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f;
127[[nodiscard]]
static constexpr float srgb_to_linear(
float s) {
128 if (s <= 0.040449936f) {
131 if (std::is_constant_evaluated()) {
132 return fast_pow((s + 0.055f) / 1.055f, 2.4f);
134 return std::pow((s + 0.055f) / 1.055f, 2.4f);
137#if defined(__ARM_NEON)
138inline float32x4_t fast_log2_f32_neon(float32x4_t x) {
139 uint32x4_t xi = vreinterpretq_u32_f32(x);
140 float32x4_t y = vmulq_n_f32(vcvtq_f32_u32(xi), 1.1920928955078125e-7f);
141 uint32x4_t mantissa = vorrq_u32(vandq_u32(xi, vdupq_n_u32(0x007FFFFF)), vdupq_n_u32(0x3f000000));
142 float32x4_t xf = vreinterpretq_f32_u32(mantissa);
143 float32x4_t a = vdupq_n_f32(124.22551499f);
144 float32x4_t b = vdupq_n_f32(1.498030302f);
145 float32x4_t c = vdupq_n_f32(1.72587999f);
146 float32x4_t d = vdupq_n_f32(0.3520887068f);
147 return vsubq_f32(vsubq_f32(y, vmlaq_f32(a, b, xf)), vdivq_f32(c, vaddq_f32(d, xf)));
150inline float32x4_t fast_exp2_f32_neon(float32x4_t p) {
151 float32x4_t one = vdupq_n_f32(1.0f);
152 float32x4_t zero = vdupq_n_f32(0.0f);
153 float32x4_t neg126 = vdupq_n_f32(-126.0f);
154 float32x4_t offset = vbslq_f32(vcltq_f32(p, zero), one, zero);
155 float32x4_t clipp = vmaxq_f32(p, neg126);
156 int32x4_t ipart = vcvtq_s32_f32(clipp);
157 float32x4_t fpart = vsubq_f32(clipp, vcvtq_f32_s32(ipart));
158 float32x4_t z = vaddq_f32(fpart, offset);
159 float32x4_t a = vdupq_n_f32(121.2740575f);
160 float32x4_t b = vdupq_n_f32(27.7280233f);
161 float32x4_t c = vdupq_n_f32(4.84252568f);
162 float32x4_t d = vdupq_n_f32(1.49012907f);
163 float32x4_t t = vaddq_f32(clipp, vsubq_f32(a, vmulq_f32(d, z)));
164 t = vaddq_f32(t, vdivq_f32(b, vsubq_f32(c, z)));
165 int32x4_t res = vcvtq_s32_f32(vmulq_n_f32(t,
static_cast<float>(1 << 23)));
166 return vreinterpretq_f32_s32(res);
169inline float32x4_t pow_1_over_2_4_f32_neon(float32x4_t x) {
170 return fast_exp2_f32_neon(vmulq_n_f32(fast_log2_f32_neon(x), 1.0f / 2.4f));
173static inline float32x4_t linear_to_srgb_approx_neon(float32x4_t l) {
174 const float32x4_t cutoff = vdupq_n_f32(0.0031308f);
175 const float32x4_t scale = vdupq_n_f32(12.92f);
176 const float32x4_t a = vdupq_n_f32(1.055f);
177 const float32x4_t b = vdupq_n_f32(-0.055f);
178 uint32x4_t mask = vcltq_f32(l, cutoff);
179 float32x4_t srgb = vmulq_f32(l, scale);
180 float32x4_t approx = vmlaq_f32(b, a, pow_1_over_2_4_f32_neon(l));
181 return vbslq_f32(mask, srgb, approx);
187inline __m128 fast_exp2_ps(__m128 p) {
188 const __m128 one = _mm_set1_ps(1.0f);
189 const __m128 zero = _mm_setzero_ps();
190 const __m128 neg126 = _mm_set1_ps(-126.0f);
191 __m128 offset = _mm_and_ps(_mm_cmplt_ps(p, zero), one);
192 __m128 clipp = _mm_max_ps(p, neg126);
193 __m128i ipart = _mm_cvttps_epi32(clipp);
194 __m128 fpart = _mm_sub_ps(clipp, _mm_cvtepi32_ps(ipart));
195 __m128 z = _mm_add_ps(fpart, offset);
196 const __m128 c1 = _mm_set1_ps(121.2740575f);
197 const __m128 c2 = _mm_set1_ps(27.7280233f);
198 const __m128 c3 = _mm_set1_ps(4.84252568f);
199 const __m128 c4 = _mm_set1_ps(1.49012907f);
200 const __m128 bias = _mm_set1_ps(1 << 23);
201 __m128 t = _mm_add_ps(clipp, _mm_sub_ps(c1, _mm_mul_ps(c4, z)));
202 t = _mm_add_ps(t, _mm_div_ps(c2, _mm_sub_ps(c3, z)));
203 __m128i result = _mm_cvtps_epi32(_mm_mul_ps(t, bias));
204 return _mm_castsi128_ps(result);
207inline __m128 fast_log2_ps(__m128 x) {
208 const __m128i xi = _mm_castps_si128(x);
209 const __m128i mant_mask = _mm_set1_epi32(0x007FFFFF);
210 const __m128i one_bits = _mm_set1_epi32(0x3f000000);
211 const __m128 y = _mm_mul_ps(_mm_cvtepi32_ps(xi), _mm_set1_ps(1.1920928955078125e-7f));
212 __m128i mant_bits = _mm_or_si128(_mm_and_si128(xi, mant_mask), one_bits);
213 __m128 xf = _mm_castsi128_ps(mant_bits);
214 const __m128 c0 = _mm_set1_ps(124.22551499f);
215 const __m128 c1 = _mm_set1_ps(1.498030302f);
216 const __m128 c2 = _mm_set1_ps(1.72587999f);
217 const __m128 c3 = _mm_set1_ps(0.3520887068f);
218 return _mm_sub_ps(_mm_sub_ps(y, _mm_add_ps(c0, _mm_mul_ps(c1, xf))), _mm_div_ps(c2, _mm_add_ps(c3, xf)));
221inline __m128 fast_pow_ps(__m128 x, __m128 p) {
222 return fast_exp2_ps(_mm_mul_ps(p, fast_log2_ps(x)));
225inline __m128 pow_1_over_2_4_ps(__m128 x) {
226 return fast_pow_ps(x, _mm_set1_ps(1.0f / 2.4f));
229inline __m128 linear_to_srgb_approx_sse(__m128 l) {
230 const __m128 cutoff = _mm_set1_ps(0.0031308f);
231 const __m128 scale = _mm_set1_ps(12.92f);
232 const __m128 a = _mm_set1_ps(1.055f);
233 const __m128 b = _mm_set1_ps(-0.055f);
234 __m128 below = _mm_mul_ps(l, scale);
235 __m128 powed = pow_1_over_2_4_ps(l);
236 __m128 above = _mm_add_ps(_mm_mul_ps(a, powed), b);
237 __m128 mask = _mm_cmplt_ps(l, cutoff);
238 return _mm_or_ps(_mm_and_ps(mask, below), _mm_andnot_ps(mask, above));
260[[nodiscard]]
static consteval srgb oklab_to_srgb_consteval(
const oklab &oklab) {
261 const double l = oklab.l;
262 const double a = oklab.a;
263 const double b = oklab.b;
265 const double l_ = l + 0.3963377774 * a + 0.2158037573 * b;
266 const double m_ = l - 0.1055613458 * a - 0.0638541728 * b;
267 const double s_ = l - 0.0894841775 * a - 1.2914855480 * b;
269 const double r = 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_;
270 const double g = -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_;
271 const double bl = -0.0041960863 * l_ - 0.7034186168 * m_ + 1.7076147031 * s_;
273 return {.r =
static_cast<double>(linear_to_srgb(std::max(0.0f, std::min(1.0f,
static_cast<float>(r))))),
274 .g =
static_cast<double>(linear_to_srgb(std::max(0.0f, std::min(1.0f,
static_cast<float>(g))))),
275 .b =
static_cast<double>(linear_to_srgb(std::max(0.0f, std::min(1.0f,
static_cast<float>(bl)))))};
278[[nodiscard]]
static consteval oklab oklch_to_oklab_consteval(
const oklch &oklch) {
279 return {.l = oklch.l,
280 .a = oklch.c * consteval_cos(oklch.h * 3.14159265358979323846 / 180.0),
281 .b = oklch.c * consteval_sin(oklch.h * 3.14159265358979323846 / 180.0)};
284static constexpr const float epsilon_low = srgb_to_linear(0.5f / 255.0f);
285static constexpr const float epsilon_high = srgb_to_linear(254.5f / 255.0f);
287static consteval auto gen_a2al_4bit_consteval() {
288 std::array<float, 16> a2al{};
289 for (
size_t c = 0; c < 16; c++) {
290 a2al[c] = srgb_to_linear(
static_cast<float>(c) * (1.0f / 15.0f));
295static constexpr const std::array<float, 16> a2al_4bit = gen_a2al_4bit_consteval();
297static consteval auto gen_a2al_8bit_consteval() {
298 std::array<float, 256> a2al{};
299 for (
size_t c = 0; c < 256; c++) {
300 a2al[c] = srgb_to_linear(
static_cast<float>(c) * (1.0f / 255.0f));
305static constexpr const std::array<float, 256> a2al_8bit = gen_a2al_8bit_consteval();
309 static constexpr size_t palette_size = S;
311 std::array<float, palette_size * 3> linearpal{};
312#if defined(__ARM_NEON)
314 std::array<float, palette_size * 3> linearpal_neon{};
318 alignas(32) std::array<float, palette_size * 3> linearpal_avx2{};
321 std::array<uint32_t, palette_size> pal{};
324 ~quantize() =
default;
325 quantize(
const quantize &&) =
delete;
326 quantize(
const quantize &) =
delete;
327 quantize &operator=(
const quantize &) =
delete;
328 quantize &operator=(quantize &&) =
delete;
330 explicit consteval quantize(
const std::array<uint32_t, palette_size> &palette) : pal(palette) {
331 for (
size_t i = 0; i < pal.size(); i++) {
332 pal[i] = (pal[i] & 0xFF000000) | (pal[i] & 0x0000FF00) | ((pal[i] >> 16) & 0x000000FF) |
333 ((pal[i] << 16) & 0x00FF0000);
335 for (
size_t i = 0; i < pal.size(); i++) {
336 linearpal.at(i * 3 + 0) =
337 hidden::srgb_to_linear(
static_cast<float>((pal[i] >> 0) & 0xFF) * (1.0f / 255.0f));
338 linearpal.at(i * 3 + 1) =
339 hidden::srgb_to_linear(
static_cast<float>((pal[i] >> 8) & 0xFF) * (1.0f / 255.0f));
340 linearpal.at(i * 3 + 2) =
341 hidden::srgb_to_linear(
static_cast<float>((pal[i] >> 16) & 0xFF) * (1.0f / 255.0f));
343#if defined(__ARM_NEON)
344 if (pal.size() >= 4) {
345 for (size_t i = 0; i < pal.size(); i += 4) {
346 for (size_t j = 0; j < 4; j++) {
347 linearpal_neon.at(i * 3 + j) = linearpal.at((i + j) * 3 + 0);
348 linearpal_neon.at(i * 3 + j + 4) = linearpal.at((i + j) * 3 + 1);
349 linearpal_neon.at(i * 3 + j + 8) = linearpal.at((i + j) * 3 + 2);
355 if (pal.size() >= 8) {
356 for (size_t i = 0; i < pal.size(); i += 8) {
357 for (size_t j = 0; j < 8; j++) {
358 linearpal_avx2.at(i * 3 + j) = linearpal.at((i + j) * 3 + 0);
359 linearpal_avx2.at(i * 3 + j + 8) = linearpal.at((i + j) * 3 + 1);
360 linearpal_avx2.at(i * 3 + j + 16) = linearpal.at((i + j) * 3 + 2);
367 [[nodiscard]]
constexpr uint8_t nearest(int32_t r, int32_t g, int32_t b)
const {
369 int32_t bestd = 1UL << 30;
370 for (
size_t i = 0; i < pal.size(); ++i) {
371 const int32_t dr = r -
static_cast<int32_t
>((pal[i] >> 0) & 0xFF);
372 const int32_t dg = g -
static_cast<int32_t
>((pal[i] >> 8) & 0xFF);
373 const int32_t db = b -
static_cast<int32_t
>((pal[i] >> 16) & 0xFF);
374 const int32_t d = dr * dr + dg * dg + db * db;
377 best =
static_cast<int32_t
>(i);
380 return static_cast<uint8_t
>(best);
383 [[nodiscard]]
constexpr auto &palette()
const {
387 [[nodiscard]]
constexpr auto &linear_palette()
const {
391 [[nodiscard]]
constexpr uint8_t nearest_linear(
float r,
float g,
float b)
const {
392 const float epsilon = 0.0001f;
393#if defined(__ARM_NEON)
394 if (!std::is_constant_evaluated() && pal.size() >= 4) {
395 const float32x4_t vR = vdupq_n_f32(r);
396 const float32x4_t vG = vdupq_n_f32(g);
397 const float32x4_t vB = vdupq_n_f32(b);
399 float best = std::numeric_limits<float>::infinity();
400 std::size_t bestIdx = 0;
402 for (
size_t i = 0; i < pal.size(); i += 4) {
403 const float32x4_t dr = vsubq_f32(vld1q_f32(&linearpal_neon[i * 3]), vR);
404 const float32x4_t dg = vsubq_f32(vld1q_f32(&linearpal_neon[i * 3 + 4]), vG);
405 const float32x4_t db = vsubq_f32(vld1q_f32(&linearpal_neon[i * 3 + 8]), vB);
407#if defined(__aarch64__) && defined(__ARM_FEATURE_FMA)
408 const float32x4_t dist = vfmaq_f32(vfmaq_f32(vmulq_f32(dr, dr), dg, dg), db, db);
410 const float32x4_t dist = vaddq_f32(vaddq_f32(vmulq_f32(dr, dr), vmulq_f32(dg, dg)), vmulq_f32(db, db));
413 const float32x2_t lo = vget_low_f32(dist);
414 const float32x2_t hi = vget_high_f32(dist);
415 const float d0 = vget_lane_f32(lo, 0);
423 const float d1 = vget_lane_f32(lo, 1);
431 const float d2 = vget_lane_f32(hi, 0);
439 const float d3 = vget_lane_f32(hi, 1);
448 return static_cast<uint8_t
>(bestIdx);
452 if (!std::is_constant_evaluated() && pal.size() >= 8) {
453 const __m256 vR = _mm256_set1_ps(r);
454 const __m256 vG = _mm256_set1_ps(g);
455 const __m256 vB = _mm256_set1_ps(b);
457 float best = std::numeric_limits<float>::infinity();
458 std::uint8_t bestIdx = 0;
460 for (
size_t i = 0; i < pal.size(); i += 8) {
461 __m256 pr = _mm256_load_ps(&linearpal_avx2[i * 3]);
462 __m256 pg = _mm256_load_ps(&linearpal_avx2[i * 3 + 8]);
463 __m256 pb = _mm256_load_ps(&linearpal_avx2[i * 3 + 16]);
466 _mm256_fmadd_ps(_mm256_sub_ps(pr, vR), _mm256_sub_ps(pr, vR),
467 _mm256_fmadd_ps(_mm256_sub_ps(pg, vG), _mm256_sub_ps(pg, vG),
468 _mm256_mul_ps(_mm256_sub_ps(pb, vB), _mm256_sub_ps(pb, vB))));
470 alignas(32)
float d[8];
471 _mm256_store_ps(d, dist);
473 for (
size_t lane = 0; lane < 8; lane++) {
474 if (d[lane] < best) {
476 bestIdx =
static_cast<uint8_t
>(i + lane);
477 if (d[lane] <= epsilon) {
478 return static_cast<uint8_t
>(bestIdx);
484 return static_cast<uint8_t
>(bestIdx);
488 float bestd = 100.0f;
489 for (
size_t i = 0; i < pal.size(); ++i) {
490 const float dr = r - linearpal.at(i * 3 + 0);
491 const float dg = g - linearpal.at(i * 3 + 1);
492 const float db = b - linearpal.at(i * 3 + 2);
493 const float d = dr * dr + dg * dg + db * db;
502 return static_cast<uint8_t
>(best);
510template <
size_t N,
typename T>
512 static constexpr T bitslices = ((
sizeof(T) * 8) / 4) - 1;
514 static constexpr size_t child_nodes_n = 1UL << 4;
515 static constexpr size_t child_nodes_n_mask = child_nodes_n - 1;
518 std::array<T, child_nodes_n> child{};
520 for (
auto &c : child) {
527 static constexpr T invalid = std::numeric_limits<T>::max();
529 std::array<node, N> nodes{};
531 [[nodiscard]]
consteval size_t byte_size()
const {
532 return sizeof(node) * nodes.size();
535 ~hextree() =
default;
536 hextree(
const hextree &&) =
delete;
537 hextree(
const hextree &) =
delete;
538 hextree &operator=(
const hextree &) =
delete;
539 hextree &operator=(hextree &&) =
delete;
541 explicit consteval hextree() =
default;
542 template <std::
size_t NS>
543 explicit consteval hextree(
const std::array<std::pair<T, T>, NS> &in) {
546 for (
auto [key, val] : in) {
548 for (T d = 0; d < bitslices; d++) {
549 T nib = (key >> ((bitslices - d) * 4)) & child_nodes_n_mask;
550 T next = nodes.at(idx).child[nib];
551 if (next == invalid) {
553 nodes.at(idx).child[nib] = next;
558 nodes.at(idx).child[key & child_nodes_n_mask] = val;
562 template <std::
size_t NS>
563 [[nodiscard]]
static consteval T size(
const std::array<std::pair<T, T>, NS> &in) {
564 std::vector<node> vnodes{1};
565 vnodes.assign(1, node{});
566 for (
auto [key, val] : in) {
568 for (T d = 0; d < bitslices; d++) {
569 T nib = (key >> ((bitslices - d) * 4)) & child_nodes_n_mask;
570 T next = vnodes.at(idx).child[nib];
571 if (next == invalid) {
572 next =
static_cast<T
>(vnodes.size());
573 vnodes[idx].child[nib] = next;
574 vnodes.emplace_back();
578 vnodes[idx].child[key & child_nodes_n_mask] = val;
580 return static_cast<T
>(vnodes.size());
583 [[nodiscard]]
constexpr T lookup(T key)
const {
585 for (T d = 0; d < bitslices; ++d) {
586 T nib = (key >> ((bitslices - d) * 4)) & child_nodes_n_mask;
587 T next = nodes.at(idx).child[nib];
588 if (next == invalid) {
593 return nodes.at(idx).child[key & child_nodes_n_mask];
625 T x1 = std::max(
x, other.
x);
626 T y1 = std::max(
y, other.
y);
627 T x2 = std::min(
x +
w, other.
x + other.
w);
628 T y2 = std::min(
y +
h, other.
y + other.
h);
633 if (nw <= 0 || nh <= 0) {
634 return {T{0}, T{0}, T{0}, T{0}};
637 return {x1, y1, nw, nh};
643 T x1 = std::max(
x, other.
x);
644 T y1 = std::max(
y, other.
y);
645 T x2 = std::min(
x +
w, other.
x + other.
w);
646 T y2 = std::min(
y +
h, other.
y + other.
h);
651 if (nw <= 0 || nh <= 0) {
652 *
this = {T{0}, T{0}, T{0}, T{0}};
668 [[nodiscard]]
static constexpr uint32_t adler32(
const uint8_t *data, std::size_t len, uint32_t adler32_sum) {
669 uint32_t adler32_s1 = adler32_sum & 0xFFFF;
670 uint32_t adler32_s2 = adler32_sum >> 16;
671 for (
size_t c = 0; c < len; c++) {
672 adler32_s1 = (adler32_s1 + data[c]) % 65521;
673 adler32_s2 = (adler32_s2 + adler32_s1) % 65521;
675 return ((adler32_s2 << 16) | adler32_s1);
678 template <
typename F>
679 static constexpr void png_write_be(F &&char_out, uint32_t value) {
680 std::forward<F>(char_out)(
static_cast<char>((value >> 24) & 0xFF));
681 std::forward<F>(char_out)(
static_cast<char>((value >> 16) & 0xFF));
682 std::forward<F>(char_out)(
static_cast<char>((value >> 8) & 0xFF));
683 std::forward<F>(char_out)(
static_cast<char>((value >> 0) & 0xFF));
686 template <
typename F,
typename A>
687 static constexpr void png_write_crc32(F &&char_out,
const A &array,
size_t bytes) {
689 uint32_t crc = 0xFFFFFFFF;
690 for (
size_t c = 0; c < bytes; c++) {
691 auto d =
static_cast<uint8_t
>(array.at(idx++));
693 for (
int i = 0; i < 8; ++i) {
694 crc = (crc & 1) ? (crc >> 1) ^ 0xEDB88320u : crc >> 1;
697 png_write_be(std::forward<F>(char_out), crc ^ 0xFFFFFFFF);
700 template <
typename F,
typename A>
701 static constexpr void png_write_array(F &&char_out,
const A &array,
size_t bytes) {
702 for (
size_t c = 0; c < bytes; c++) {
703 std::forward<F>(char_out)(
static_cast<char>(array.at(c)));
707 template <
typename F>
708 static constexpr void png_marker(F &&char_out) {
709 std::forward<F>(char_out)(
static_cast<char>(0x89));
710 std::forward<F>(char_out)(0x50);
711 std::forward<F>(char_out)(0x4E);
712 std::forward<F>(char_out)(0x47);
713 std::forward<F>(char_out)(0x0D);
714 std::forward<F>(char_out)(0x0A);
715 std::forward<F>(char_out)(0x1A);
716 std::forward<F>(char_out)(0x0A);
719 template <
typename F>
720 static constexpr void png_header(F &&char_out,
size_t w,
size_t h,
size_t depth) {
721 const size_t chunkLength = 17;
722 std::array<char, chunkLength> header{};
724 header.at(i++) =
'I';
725 header.at(i++) =
'H';
726 header.at(i++) =
'D';
727 header.at(i++) =
'R';
728 header.at(i++) =
static_cast<char>((w >> 24) & 0xFF);
729 header.at(i++) =
static_cast<char>((w >> 16) & 0xFF);
730 header.at(i++) =
static_cast<char>((w >> 8) & 0xFF);
731 header.at(i++) =
static_cast<char>((w >> 0) & 0xFF);
732 header.at(i++) =
static_cast<char>((h >> 24) & 0xFF);
733 header.at(i++) =
static_cast<char>((h >> 16) & 0xFF);
734 header.at(i++) =
static_cast<char>((h >> 8) & 0xFF);
735 header.at(i++) =
static_cast<char>((h >> 0) & 0xFF);
737 header.at(i++) =
static_cast<char>(depth);
739 }
else if (depth == 24) {
749 png_write_be(std::forward<F>(char_out),
static_cast<uint32_t
>(i - 4));
750 png_write_array(std::forward<F>(char_out), header, i);
751 png_write_crc32(std::forward<F>(char_out), header, i);
754 template <
typename F,
typename P>
755 static constexpr void png_palette(F &&char_out,
const P &palette) {
756 std::array<char, 256 * 3 + 4> header{};
758 header.at(i++) =
'P';
759 header.at(i++) =
'L';
760 header.at(i++) =
'T';
761 header.at(i++) =
'E';
762 for (
size_t c = 0; c < palette.size(); c++) {
763 header.at(i++) =
static_cast<char>((palette[c] >> 0) & 0xFF);
764 header.at(i++) =
static_cast<char>((palette[c] >> 8) & 0xFF);
765 header.at(i++) =
static_cast<char>((palette[c] >> 16) & 0xFF);
767 png_write_be(std::forward<F>(char_out),
static_cast<uint32_t
>(i - 4));
768 png_write_array(std::forward<F>(char_out), header, i);
769 png_write_crc32(std::forward<F>(char_out), header, i);
772 template <
typename F>
773 static constexpr void png_end(F &&char_out) {
774 std::array<char, 4> header{};
776 header.at(i++) =
'I';
777 header.at(i++) =
'E';
778 header.at(i++) =
'N';
779 header.at(i++) =
'D';
780 png_write_be(std::forward<F>(char_out),
static_cast<uint32_t
>(i - 4));
781 png_write_array(std::forward<F>(char_out), header, i);
782 png_write_crc32(std::forward<F>(char_out), header, i);
785 template <
typename F>
786 static constexpr void png_idat_zlib_header(F &&char_out) {
787 std::array<char, 6> header{};
789 header.at(i++) =
'I';
790 header.at(i++) =
'D';
791 header.at(i++) =
'A';
792 header.at(i++) =
'T';
793 header.at(i++) = 0x78;
794 header.at(i++) = 0x01;
795 png_write_be(std::forward<F>(char_out),
static_cast<uint32_t
>(i - 4));
796 png_write_array(std::forward<F>(char_out), header, i);
797 png_write_crc32(std::forward<F>(char_out), header, i);
800 template <
typename F>
801 [[nodiscard]]
static constexpr uint32_t png_idat_zlib_stream(F &&char_out,
const uint8_t *line,
size_t bytes,
802 uint32_t adler32_sum,
bool last_line) {
803 const auto max_data_use =
size_t{1024};
804 const auto extra_data =
size_t{10};
805 const size_t max_stack_use = max_data_use + extra_data;
806 std::array<uint8_t, max_stack_use> header{};
807 size_t filter_first = 1;
810 header.at(i++) =
'I';
811 header.at(i++) =
'D';
812 header.at(i++) =
'A';
813 header.at(i++) =
'T';
815 const size_t bytes_to_copy = std::min(max_data_use, bytes);
817 header.at(i++) = ((bytes_to_copy == bytes) && last_line) ? 0x01 : 0x00;
819 header.at(i++) = (((bytes_to_copy + filter_first) >> 0) & 0xFF);
820 header.at(i++) = (((bytes_to_copy + filter_first) >> 8) & 0xFF);
821 header.at(i++) = ((((bytes_to_copy + filter_first) ^ 0xffff) >> 0) & 0xFF);
822 header.at(i++) = ((((bytes_to_copy + filter_first) ^ 0xffff) >> 8) & 0xFF);
824 const size_t adlersum32_start_pos = i;
825 if (filter_first != 0) {
829 for (
size_t d = 0; d < bytes_to_copy; d++) {
830 header.at(i++) = *line++;
832 adler32_sum = adler32(&header[adlersum32_start_pos], i - adlersum32_start_pos, adler32_sum);
834 png_write_be(std::forward<F>(char_out),
static_cast<uint32_t
>(i - 4));
835 png_write_array(std::forward<F>(char_out), header, i);
836 png_write_crc32(std::forward<F>(char_out), header, i);
838 bytes -= bytes_to_copy;
844 template <
typename F>
845 static constexpr void png_idat_zlib_trailer(F &&char_out, uint32_t adler32_sum) {
846 std::array<char, 8> header{};
848 header.at(i++) =
'I';
849 header.at(i++) =
'D';
850 header.at(i++) =
'A';
851 header.at(i++) =
'T';
852 header.at(i++) =
static_cast<char>((adler32_sum >> 24) & 0xFF);
853 header.at(i++) =
static_cast<char>((adler32_sum >> 16) & 0xFF);
854 header.at(i++) =
static_cast<char>((adler32_sum >> 8) & 0xFF);
855 header.at(i++) =
static_cast<char>((adler32_sum >> 0) & 0xFF);
856 png_write_be(std::forward<F>(char_out),
static_cast<uint32_t
>(i - 4));
857 png_write_array(std::forward<F>(char_out), header, i);
858 png_write_crc32(std::forward<F>(char_out), header, i);
861 template <
typename F>
862 static constexpr void sixel_header(F &&char_out) {
863 std::forward<F>(char_out)(0x1b);
864 std::forward<F>(char_out)(
'P');
865 std::forward<F>(char_out)(
'q');
868 template <
size_t W,
size_t H,
size_t S,
typename F>
869 static constexpr void sixel_raster_attributes(F &&char_out) {
870 std::forward<F>(char_out)(
'\"');
871 sixel_number(std::forward<F>(char_out), 1);
872 std::forward<F>(char_out)(
';');
873 sixel_number(std::forward<F>(char_out), 1);
874 std::forward<F>(char_out)(
';');
875 sixel_number(std::forward<F>(char_out), W * S);
876 std::forward<F>(char_out)(
';');
877 sixel_number(std::forward<F>(char_out), H * S);
880 template <
typename F>
881 static constexpr void sixel_number(F &&char_out, uint16_t u) {
883 std::forward<F>(char_out)(
static_cast<char>(
'0' + u));
884 }
else if (u < 100) {
885 std::forward<F>(char_out)(
static_cast<char>(
'0' + (((u / 10) % 10))));
886 std::forward<F>(char_out)(
static_cast<char>(
'0' + (u % 10)));
887 }
else if (u < 1000) {
888 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 100) % 10)));
889 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 10) % 10)));
890 std::forward<F>(char_out)(
static_cast<char>(
'0' + (u % 10)));
891 }
else if (u < 10000) {
892 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 1000) % 10)));
893 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 100) % 10)));
894 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 10) % 10)));
895 std::forward<F>(char_out)(
static_cast<char>(
'0' + (u % 10)));
897 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 10000) % 10)));
898 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 1000) % 10)));
899 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 100) % 10)));
900 std::forward<F>(char_out)(
static_cast<char>(
'0' + ((u / 10) % 10)));
901 std::forward<F>(char_out)(
static_cast<char>(
'0' + (u % 10)));
905 template <
typename F>
906 static constexpr void sixel_color(F &&char_out, uint16_t i, uint32_t col) {
907 std::forward<F>(char_out)(
'#');
908 sixel_number(char_out, i);
909 std::forward<F>(char_out)(
';');
910 std::forward<F>(char_out)(
'2');
911 std::forward<F>(char_out)(
';');
912 for (
size_t c = 0; c < 3; c++) {
913 sixel_number(std::forward<F>(char_out),
static_cast<uint16_t
>((((col >> (8 * c)) & 0xFF) * 100) / 255));
915 std::forward<F>(char_out)(
';');
920 template <
typename F>
921 static constexpr void sixel_end(F &&char_out) {
922 std::forward<F>(char_out)(0x1b);
923 std::forward<F>(char_out)(
'\\');
926 template <
typename PBT,
size_t PBS>
927 struct palette_bitset {
928 constexpr void mark(PBT col) {
929 const PBT idx = (col >> 5) & set_idx_mask;
930 set[idx] |= 1UL << (col & 0x1F);
933 constexpr void clear() {
937 [[nodiscard]]
constexpr size_t genstack(std::array<PBT, PBS> &stack)
const {
939 for (
size_t c = 0; c < PBS / 32; c++) {
940 for (
size_t d = 0; d < 32; d++) {
941 if ((set[c] >> d) & 1) {
942 stack.at(count++) =
static_cast<PBT
>(c * 32 + d);
949 std::array<uint32_t, PBS / 32> set{};
950 static constexpr size_t set_idx_mask = (1UL <<
sizeof(set) / 4) - 1;
953 template <
size_t W,
size_t H,
typename PBT,
size_t PBS,
typename P,
typename F,
typename L>
954 static constexpr void png_image(
const uint8_t *data,
const P &palette, F &&char_out,
const L &line_ptr) {
955 png_marker(std::forward<F>(char_out));
956 png_header(std::forward<F>(char_out), W, H, PBS);
957 if constexpr (PBS <= 8) {
958 png_palette(std::forward<F>(char_out), palette);
960 png_idat_zlib_header(std::forward<F>(char_out));
961 uint32_t adler32_sum = 1;
962 for (
size_t y = 0; y < H; y++) {
964 const uint8_t *ptr = line_ptr(data, y, bpl);
965 adler32_sum = png_idat_zlib_stream(std::forward<F>(char_out), ptr, bpl, adler32_sum, y == H - 1);
967 png_idat_zlib_trailer(std::forward<F>(char_out), adler32_sum);
968 png_end(std::forward<F>(char_out));
971 template <
size_t W,
size_t H,
int32_t S,
typename PBT,
size_t PBS,
typename P,
typename F,
typename C,
typename D>
972 static constexpr void sixel_image(
const uint8_t *data,
const P &palette, F &&char_out,
const rect<int32_t> &_r,
973 const C &collect6,
const D &set6) {
974 sixel_header(std::forward<F>(char_out));
975 sixel_raster_attributes<W, H, S>(std::forward<F>(char_out));
976 for (
size_t c = 0; c < palette.size(); c++) {
977 sixel_color(std::forward<F>(char_out),
static_cast<uint16_t
>(c), palette[c]);
979 const auto r = rect<int32_t>{.x = _r.x * S, .y = _r.y * S, .w = _r.w * S, .h = _r.h * S} &
980 rect<int32_t>{.x = 0, .y = 0, .w = W * S, .h = H * S};
981 std::array<PBT, std::max(32UL, 1UL << PBS)> stack{};
982 palette_bitset<PBT, std::max(32UL, 1UL << PBS)> pset{};
983 for (int32_t y = r.y; y < (r.y + r.h); y += 6) {
985 set6(data,
static_cast<size_t>(r.x),
static_cast<size_t>(r.w),
static_cast<size_t>(y), pset);
986 const size_t stack_count = pset.genstack(stack);
987 for (
size_t s = 0; s < stack_count; s++) {
988 const PBT col = stack[s];
990 std::forward<F>(char_out)(
'$');
992 std::forward<F>(char_out)(
'#');
993 sixel_number(std::forward<F>(char_out),
static_cast<uint16_t
>(col));
994 for (int32_t x = r.x; x < (r.x + r.w); x++) {
995 PBT bits6 = collect6(data,
static_cast<size_t>(x), col,
static_cast<size_t>(y));
996 uint16_t repeat_count = 0;
997 for (int32_t xr = x + 1; xr < (std::min(x + int32_t{255},
static_cast<int32_t
>(W * S))); xr++) {
998 if (bits6 == collect6(data,
static_cast<size_t>(xr), col,
static_cast<size_t>(y))) {
1004 if (repeat_count > uint16_t{3}) {
1005 std::forward<F>(char_out)(
'!');
1006 sixel_number(std::forward<F>(char_out),
static_cast<uint16_t
>(repeat_count + 1));
1009 std::forward<F>(char_out)(
static_cast<char>(
'?' + bits6));
1012 std::forward<F>(char_out)(
'-');
1014 sixel_end(std::forward<F>(char_out));
1029template <
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
1033 static constexpr size_t bits_per_pixel = 1;
1034 static constexpr size_t bytes_per_line = (W * bits_per_pixel + 7) / 8;
1035 static constexpr size_t image_size = H * bytes_per_line;
1036 static constexpr size_t color_mask = 0x01;
1038 static consteval auto gen_palette_consteval() {
1039 return std::array<uint32_t, (1UL << bits_per_pixel)>({{0x00000000, 0x00ffffff}});
1041 static constexpr const auto quant = hidden::quantize<1UL << bits_per_pixel>(gen_palette_consteval());
1043 static constexpr uint8_t reverse(uint8_t b) {
1044 b =
static_cast<uint8_t
>((b & uint8_t{0xF0}) >> 4 | (b & uint8_t{0x0F}) << 4);
1045 b =
static_cast<uint8_t
>((b & uint8_t{0xCC}) >> 2 | (b & uint8_t{0x33}) << 2);
1046 b =
static_cast<uint8_t
>((b & uint8_t{0xAA}) >> 1 | (b & uint8_t{0x55}) << 1);
1050 static constexpr void transpose8x8(std::array<uint8_t, 8> &a) {
1052 uint32_t x = (
static_cast<uint32_t
>(a[0]) << 24) |
1053 (
static_cast<uint32_t
>(a[1]) << 16) |
1054 (
static_cast<uint32_t
>(a[2]) << 8) |
1055 (
static_cast<uint32_t
>(a[3]));
1056 uint32_t y = (
static_cast<uint32_t
>(a[4]) << 24) |
1057 (
static_cast<uint32_t
>(a[5]) << 16) |
1058 (
static_cast<uint32_t
>(a[6]) << 8) |
1059 (
static_cast<uint32_t
>(a[7]));
1061 uint32_t t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
1062 t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
1063 t = (x ^ (x >> 14)) & 0x0000CCCC; x = x ^ t ^ (t << 14);
1064 t = (y ^ (y >> 14)) & 0x0000CCCC; y = y ^ t ^ (t << 14);
1066 t = ((y >> 4) & 0x0F0F0F0F) | (x & 0xF0F0F0F0);
1067 y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
1069 a[0] =
static_cast<uint8_t
>(t >> 24);
1070 a[1] =
static_cast<uint8_t
>(t >> 16);
1071 a[2] =
static_cast<uint8_t
>(t >> 8);
1072 a[3] =
static_cast<uint8_t
>(t);
1073 a[4] =
static_cast<uint8_t
>(y >> 24);
1074 a[5] =
static_cast<uint8_t
>(y >> 16);
1075 a[6] =
static_cast<uint8_t
>(y >> 8);
1076 a[7] =
static_cast<uint8_t
>(y);
1080 template <
bool FLIP_H = false,
bool FLIP_V = false>
1081 static constexpr void transpose(
const uint8_t *src, uint8_t *dst) {
1082 std::array<uint8_t, 8> tmp{};
1083 const size_t src_stride = ((W + 7) / 8);
1084 const size_t dst_stride = ((H + 7) / 8);
1085 for (
size_t y = 0; y < dst_stride; y++) {
1086 for (
size_t x = 0; x < src_stride; x++) {
1087 size_t xl = std::min(
size_t{8}, H - (y * 8));
1088 for (
size_t c = 0; c < xl; c++) {
1089 if constexpr (FLIP_V) {
1090 tmp[c] = src[(H - 1 - (y * 8 + c)) * src_stride + x];
1092 tmp[c] = src[(y * 8 + c) * src_stride + x];
1095 for (
size_t c = xl; c < 8; c++) {
1099 size_t yl = std::min(
size_t{8}, W - (x * 8));
1100 for (
size_t c = 0; c < yl; c++) {
1101 if constexpr (FLIP_H) {
1102 dst[((src_stride - x - 1) * 8 + 7 - c) * dst_stride + y] = tmp[c];
1104 dst[(x * 8 + c) * dst_stride + y] = tmp[c];
1111 static constexpr void compose(std::span<uint8_t, image_size> & ,
size_t ,
size_t ,
float ,
1112 float ,
float ,
float ) {
1114 static_assert(
false,
"composing not supported on 1-bit format, use a mono font.");
1118 static constexpr void plot(std::span<uint8_t, image_size> data,
size_t x0,
size_t y, uint8_t col) {
1119 col &= (1UL << bits_per_pixel) - 1;
1120 const size_t x8 = x0 / 8;
1122 uint8_t *yptr = &data.data()[y * bytes_per_line];
1123 yptr[x8] &= ~static_cast<uint8_t>(1UL << (7 - x0));
1124 yptr[x8] |=
static_cast<uint8_t
>(col << (7 - x0));
1127 static constexpr void extent(std::span<uint8_t, image_size> data,
size_t xl0,
size_t xr0,
size_t y, uint8_t col) {
1128 col &= (1UL << bits_per_pixel) - 1;
1129 const size_t xl8 = xl0 / 8;
1131 const size_t xr8 = xr0 / 8;
1133 const size_t xs8 = xr8 - xl8;
1135 static_cast<uint8_t
>(col << 7 | col << 6 | col << 5 | col << 4 | col << 3 | col << 2 | col << 1 | col << 0);
1136 constexpr std::array<uint8_t, 8> ml = {
1137 {0b11111111, 0b01111111, 0b00111111, 0b00011111, 0b00001111, 0b00000111, 0b00000011, 0b00000001}};
1138 constexpr std::array<uint8_t, 8> mr = {
1139 {0b00000000, 0b10000000, 0b11000000, 0b11100000, 0b11110000, 0b11111000, 0b11111100, 0b11111110}};
1140 uint8_t *yptr = &data.data()[y * bytes_per_line];
1142 yptr[xl8] &=
static_cast<uint8_t
>(~ml[xl0]);
1143 yptr[xl8] |=
static_cast<uint8_t
>(ml[xl0] & c8);
1144 for (
size_t x = xl8 + 1; x < xr8; x++) {
1148 yptr[xr8] &=
static_cast<uint8_t
>(~mr[xr0]);
1149 yptr[xr8] |=
static_cast<uint8_t
>(mr[xr0] & c8);
1152 yptr[xl8] &=
static_cast<uint8_t
>(~(ml[xl0] & mr[xr0]));
1153 yptr[xl8] |=
static_cast<uint8_t
>((ml[xl0] & mr[xr0] & c8));
1157 [[nodiscard]]
static constexpr uint8_t get_col(
const uint8_t *line,
size_t x) {
1158 const size_t x8 = x / 8;
1159 const size_t xb = x % 8;
1160 return static_cast<uint8_t
>((line[x8] >> (7 - xb)) & 1);
1163 static constexpr void RGBA_uint32(std::array<uint32_t, W * H> &dst,
1164 const std::span<const uint8_t, image_size> &src) {
1165 const uint8_t *ptr = src.data();
1166 for (
size_t y = 0; y < H; y++) {
1167 for (
size_t x = 0; x < W; x++) {
1168 const uint8_t col = get_col(ptr, x);
1169 dst[y * W + x] = quant.palette().at(col) | ((col != 0) ? 0xFF000000 : 0x00000000);
1171 ptr += bytes_per_line;
1175 static constexpr void RGBA_uint8(std::array<uint8_t, W * H * 4> &dst,
1176 const std::span<const uint8_t, image_size> &src) {
1177 const uint8_t *ptr = src.data();
1178 for (
size_t y = 0; y < H; y++) {
1179 for (
size_t x = 0; x < W; x++) {
1180 const uint8_t col = get_col(ptr, x);
1181 dst[y * W * 4 + x * 4 + 0] =
static_cast<uint8_t
>((quant.palette().at(col) >> 0) & 0xFF);
1182 dst[y * W * 4 + x * 4 + 1] =
static_cast<uint8_t
>((quant.palette().at(col) >> 8) & 0xFF);
1183 dst[y * W * 4 + x * 4 + 2] =
static_cast<uint8_t
>((quant.palette().at(col) >> 16) & 0xFF);
1184 dst[y * W * 4 + x * 4 + 3] = ((col != 0) ? 0xFF : 0x00);
1186 ptr += bytes_per_line;
1190 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
const uint8_t *ptr,
1192 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1193 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1194 for (
size_t y = 0; y < r_h; y++) {
1195 for (
size_t x = 0; x < r_w; x++) {
1196 const uint32_t R = ptr[y *
static_cast<size_t>(stride) + x * 4 + 0];
1197 const uint32_t G = ptr[y *
static_cast<size_t>(stride) + x * 4 + 1];
1198 const uint32_t B = ptr[y *
static_cast<size_t>(stride) + x * 4 + 2];
1199 plot(data, x +
static_cast<size_t>(r.
x), y +
static_cast<size_t>(r.
y),
1200 (R * 2 + G * 3 + B * 1) > 768 ? 1 : 0);
1205 static constexpr void blit_RGBA_diffused(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
1206 const uint8_t *ptr, int32_t stride) {
1207 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1208 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1209 for (
size_t y = 0; y < r_h; y++) {
1211 for (
size_t x = 0; x < r_w; x++) {
1212 const int32_t R = ptr[y *
static_cast<size_t>(stride) + x * 4 + 0];
1213 const int32_t G = ptr[y *
static_cast<size_t>(stride) + x * 4 + 1];
1214 const int32_t B = ptr[y *
static_cast<size_t>(stride) + x * 4 + 2];
1215 const int32_t V = (R * 2 + G * 3 + B * 1) + err;
1216 const uint8_t n = V > 768 ? 1 : 0;
1217 plot(data, (x +
static_cast<size_t>(r.
x)), (y +
static_cast<size_t>(r.
y)), n);
1218 err = std::clamp(V - ((n != 0) ? int32_t{0xFF * 6} : int32_t{0x00}), int32_t{-0xFF * 6},
1224 static constexpr void blit_RGBA_diffused_linear(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
1225 const uint8_t *ptr, int32_t stride) {
1226 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1227 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1228 for (
size_t y = 0; y < r_h; y++) {
1232 for (
size_t x = 0; x < r_w; x++) {
1233 const auto R =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 0]) * (1.0f / 255.0f);
1234 const auto G =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 1]) * (1.0f / 255.0f);
1235 const auto B =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 2]) * (1.0f / 255.0f);
1236 float Rl = hidden::srgb_to_linear(R);
1237 float Gl = hidden::srgb_to_linear(G);
1238 float Bl = hidden::srgb_to_linear(B);
1242 uint8_t n = (Rl * 2 * +Gl * 3 + Bl * 1) > 3.0f ? 1 : 0;
1243 plot(data, (x +
static_cast<size_t>(r.
x)), (y +
static_cast<size_t>(r.
y)), n);
1244 const float c = 0.75;
1245 err_r = std::clamp(Rl - ((n != uint8_t{0}) ? 1.0f : 0.0f), -c, c);
1246 err_g = std::clamp(Gl - ((n != uint8_t{0}) ? 1.0f : 0.0f), -c, c);
1247 err_b = std::clamp(Bl - ((n != uint8_t{0}) ? 1.0f : 0.0f), -c, c);
1252 template <
typename F>
1253 static constexpr void png(
const std::span<const uint8_t, image_size> data, F &&char_out) {
1254 png_image<W, H, uint8_t, bits_per_pixel>(data.data(), quant.palette(), std::forward<F>(char_out),
1255 [](
const uint8_t *data_raw,
size_t y,
size_t &bpl) {
1256 bpl = bytes_per_line;
1257 return data_raw + y * bytes_per_line;
1261 template <
size_t S,
typename F>
1262 static constexpr void sixel(
const std::span<const uint8_t, image_size> data, F &&char_out,
const rect<int32_t> &r) {
1263 sixel_image<W, H, S, uint8_t, bits_per_pixel>(
1264 data.data(), quant.palette(), std::forward<F>(char_out), r,
1265 [](
const uint8_t *data_raw,
size_t x,
size_t col,
size_t y) {
1266 const uint8_t *ptr = &data_raw[(y / S) * bytes_per_line + (x / S) / 8];
1267 const size_t x8 = (x / S) % 8;
1270 for (size_t y6 = 0; y6 < 6; y6++) {
1272 if ((y + y6) < H * S) {
1273 out |= static_cast<uint8_t>((static_cast<uint8_t>(((*ptr) >> (7 - x8)) & 1) == col) ? (1UL << 5)
1275 if ((y + y6) != ((H * S) - 1)) {
1278 ptr += bytes_per_line;
1285 [](
const uint8_t *data_raw,
size_t x,
size_t w,
size_t y, palette_bitset<uint8_t, 32> &set) {
1287 for (
size_t y6 = 0; y6 < 6; y6++) {
1288 if ((y + y6) < H * S) {
1289 for (
size_t xx = 0; xx < (w + S - 1) / S; xx++) {
1290 const uint8_t *ptr = &data_raw[((y + y6) / S) * bytes_per_line + (xx + x) / 8];
1291 const size_t x8 = (xx + x) % 8;
1292 set.mark(
static_cast<uint8_t
>(((*ptr) >> (7 - x8)) & 1));
1315template <
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
1319 static constexpr size_t bits_per_pixel = 2;
1320 static constexpr size_t bytes_per_line = (W * bits_per_pixel + 7) / 8;
1321 static constexpr size_t image_size = H * bytes_per_line;
1322 static constexpr size_t color_mask = 0x03;
1323 static consteval auto gen_palette_consteval() {
1325 return std::array<uint32_t, (1UL << bits_per_pixel)>({{0x000000, 0x444444, 0x888888, 0xffffff}});
1327 return std::array<uint32_t, (1UL << bits_per_pixel)>({{0x000000, 0xffffff, 0xff0000, 0x0077ff}});
1329 static constexpr const auto quant = hidden::quantize<1UL << bits_per_pixel>(gen_palette_consteval());
1331 static constexpr uint8_t reverse(uint8_t b) {
1332 b =
static_cast<uint8_t
>((b & uint8_t{0xF0}) >> 4 | (b & uint8_t{0x0F}) << 4);
1333 b =
static_cast<uint8_t
>((b & uint8_t{0xCC}) >> 2 | (b & uint8_t{0x33}) << 2);
1337 static constexpr void transpose2x8(std::array<uint8_t, 8> &data) {
1338 for (
size_t i = 0; i < 4; i++) {
1339 for (
size_t j = 0; j < 4; j++) {
1340 auto shift =
static_cast<uint32_t
>(6U - j * 2U);
1341 uint8_t pixel = (data[i] >> shift) & 0x03U;
1342 auto ishift =
static_cast<uint32_t
>(6U - i * 2U);
1344 static_cast<uint8_t
>((data[j] & ~(0x03U << ishift)) | (
static_cast<uint32_t
>(pixel) << ishift));
1347 for (
size_t i = 0; i < 4; i++) {
1348 for (
size_t j = 0; j < 4; j++) {
1349 auto shift =
static_cast<uint32_t
>(6U - j * 2U);
1350 uint8_t pixel = (data[i + 4] >> shift) & 0x03U;
1351 auto ishift =
static_cast<uint32_t
>(6U - i * 2U);
1353 static_cast<uint8_t
>((data[j + 4] & ~(0x03U << ishift)) | (
static_cast<uint32_t
>(pixel) << ishift));
1358 template <
bool FLIP_H = false,
bool FLIP_V = false>
1359 static constexpr void transpose(
const uint8_t *src, uint8_t *dst) {
1360 for (
size_t y = 0; y < H; y++) {
1361 for (
size_t x = 0; x < W; x++) {
1362 size_t src_byte = y * bytes_per_line + (x / 4);
1363 size_t src_shift = (3 - (x % 4)) * 2;
1364 uint8_t pixel = (src[src_byte] >> src_shift) & 0x03;
1366 size_t dst_x = FLIP_H ? (H - 1 - y) : y;
1367 size_t dst_y = FLIP_V ? (W - 1 - x) : x;
1368 size_t dst_byte = dst_y * ((H + 3) / 4) + (dst_x / 4);
1369 size_t dst_shift = (3 - (dst_x % 4)) * 2;
1371 dst[dst_byte] &= ~(0x03 << dst_shift);
1372 dst[dst_byte] |= pixel << dst_shift;
1377 static constexpr void compose(std::span<uint8_t, image_size> & ,
size_t ,
size_t ,
float ,
1378 float ,
float ,
float ) {
1380 static_assert(
false,
"composing not supported on 2-bit format, use a mono font.");
1384 static constexpr void plot(std::span<uint8_t, image_size> data,
size_t x0,
size_t y, uint8_t col) {
1385 col &= (1UL << bits_per_pixel) - 1;
1386 const size_t x4 = x0 / 4;
1388 uint8_t *yptr = &data.data()[y * bytes_per_line];
1389 yptr[x4] &= ~static_cast<uint8_t>(3UL << (6 - x0 * 2));
1390 yptr[x4] |=
static_cast<uint8_t
>(col << (6 - x0 * 2));
1393 static constexpr void extent(std::span<uint8_t, image_size> data,
size_t xl0,
size_t xr0,
size_t y, uint8_t col) {
1394 col &= (1UL << bits_per_pixel) - 1;
1395 const size_t xl4 = xl0 / 4;
1397 const size_t xr4 = xr0 / 4;
1399 const size_t xs4 = xr4 - xl4;
1400 const auto c4 =
static_cast<uint8_t
>(col << 6 | col << 4 | col << 2 | col << 0);
1401 constexpr std::array<uint8_t, 4> ml = {{0b11111111, 0b00111111, 0b00001111, 0b00000011}};
1402 constexpr std::array<uint8_t, 4> mr = {{0b00000000, 0b11000000, 0b11110000, 0b11111100}};
1403 uint8_t *yptr = &data.data()[y * bytes_per_line];
1405 yptr[xl4] &=
static_cast<uint8_t
>(~ml[xl0]);
1406 yptr[xl4] |=
static_cast<uint8_t
>(ml[xl0] & c4);
1407 for (
size_t x = xl4 + 1; x < xr4; x++) {
1411 yptr[xr4] &=
static_cast<uint8_t
>(~mr[xr0]);
1412 yptr[xr4] |=
static_cast<uint8_t
>(mr[xr0] & c4);
1415 yptr[xl4] &=
static_cast<uint8_t
>(~(ml[xl0] & mr[xr0]));
1416 yptr[xl4] |=
static_cast<uint8_t
>(ml[xl0] & mr[xr0] & c4);
1420 static constexpr uint8_t get_col(
const uint8_t *line,
size_t x) {
1421 const size_t x4 = x / 4;
1422 const size_t xb = x % 4;
1423 return static_cast<uint8_t
>((line[x4] >> ((3 - xb) * 2)) & 0x3);
1426 static constexpr void RGBA_uint32(std::array<uint32_t, W * H> &dst,
1427 const std::span<const uint8_t, image_size> &src) {
1428 const uint8_t *ptr = src.data();
1429 for (
size_t y = 0; y < H; y++) {
1430 for (
size_t x = 0; x < W; x++) {
1431 const uint8_t col = get_col(ptr, x);
1432 dst[y * W + x] = quant.palette().at(col) | ((col != 0) ? 0xFF000000 : 0x00000000);
1434 ptr += bytes_per_line;
1438 static constexpr void RGBA_uint8(std::array<uint8_t, W * H * 4> &dst,
1439 const std::span<const uint8_t, image_size> &src) {
1440 const uint8_t *ptr = src.data();
1441 for (
size_t y = 0; y < H; y++) {
1442 for (
size_t x = 0; x < W; x++) {
1443 const uint8_t col = get_col(ptr, x);
1444 dst[y * W * 4 + x * 4 + 0] =
static_cast<uint8_t
>((quant.palette().at(col) >> 0) & 0xFF);
1445 dst[y * W * 4 + x * 4 + 1] =
static_cast<uint8_t
>((quant.palette().at(col) >> 8) & 0xFF);
1446 dst[y * W * 4 + x * 4 + 2] =
static_cast<uint8_t
>((quant.palette().at(col) >> 16) & 0xFF);
1447 dst[y * W * 4 + x * 4 + 3] = ((col != 0) ? 0xFF : 0x00);
1449 ptr += bytes_per_line;
1453 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
const uint8_t *ptr,
1455 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1456 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1457 for (
size_t y = 0; y < r_h; y++) {
1458 for (
size_t x = 0; x < r_w; x++) {
1459 plot(data, (x +
static_cast<uint32_t
>(r.
x)), (y +
static_cast<uint32_t
>(r.
y)),
1460 quant.nearest(ptr[y *
static_cast<size_t>(stride) + x * 4 + 0],
1461 ptr[y *
static_cast<size_t>(stride) + x * 4 + 1],
1462 ptr[y *
static_cast<size_t>(stride) + x * 4 + 2]));
1467 static constexpr void blit_RGBA_diffused(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
1468 const uint8_t *ptr, int32_t stride) {
1469 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1470 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1471 for (
size_t y = 0; y < r_h; y++) {
1475 for (
size_t x = 0; x < r_w; x++) {
1476 int32_t R = ptr[y *
static_cast<size_t>(stride) + x * 4 + 0];
1477 int32_t G = ptr[y *
static_cast<size_t>(stride) + x * 4 + 1];
1478 int32_t B = ptr[y *
static_cast<size_t>(stride) + x * 4 + 2];
1482 uint8_t n = quant.nearest(R, G, B);
1483 plot(data, (x +
static_cast<size_t>(r.
x)), (y +
static_cast<size_t>(r.
y)), n);
1484 err_r = std::clamp(R -
static_cast<int32_t
>((quant.palette().at(n) >> 0) & 0xFF), int32_t{-255},
1486 err_g = std::clamp(G -
static_cast<int32_t
>((quant.palette().at(n) >> 8) & 0xFF), int32_t{-255},
1488 err_b = std::clamp(B -
static_cast<int32_t
>((quant.palette().at(n) >> 16) & 0xFF), int32_t{-255},
1494 static constexpr void blit_RGBA_diffused_linear(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
1495 const uint8_t *ptr, int32_t stride) {
1496 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1497 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1498 for (
size_t y = 0; y < r_h; y++) {
1502 for (
size_t x = 0; x < r_w; x++) {
1503 const auto R =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 0]) * (1.0f / 255.0f);
1504 const auto G =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 1]) * (1.0f / 255.0f);
1505 const auto B =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 2]) * (1.0f / 255.0f);
1506 float Rl = hidden::srgb_to_linear(R);
1507 float Gl = hidden::srgb_to_linear(G);
1508 float Bl = hidden::srgb_to_linear(B);
1512 uint8_t n = quant.nearest_linear(Rl, Gl, Bl);
1513 plot(data, (x +
static_cast<size_t>(r.
x)), (y +
static_cast<size_t>(r.
y)), n);
1514 err_r = std::clamp(Rl - quant.linear_palette().at(n *
size_t{3} +
size_t{0}), -1.0f, 1.0f);
1515 err_g = std::clamp(Gl - quant.linear_palette().at(n *
size_t{3} +
size_t{1}), -1.0f, 1.0f);
1516 err_b = std::clamp(Bl - quant.linear_palette().at(n *
size_t{3} +
size_t{2}), -1.0f, 1.0f);
1521 template <
typename F>
1522 static constexpr void png(
const std::span<const uint8_t, image_size> data, F &&char_out) {
1523 png_image<W, H, uint8_t, bits_per_pixel>(data.data(), quant.palette(), std::forward<F>(char_out),
1524 [](
const uint8_t *data_raw,
size_t y,
size_t &bpl) {
1525 bpl = bytes_per_line;
1526 return data_raw + y * bytes_per_line;
1530 template <
size_t S,
typename F>
1531 static constexpr void sixel(
const std::span<const uint8_t, image_size> data, F &&char_out,
const rect<int32_t> &r) {
1532 sixel_image<W, H, S, uint8_t, bits_per_pixel>(
1533 data.data(), quant.palette(), std::forward<F>(char_out), r,
1534 [](
const uint8_t *data_raw,
size_t x,
size_t col,
size_t y) {
1535 const uint8_t *ptr = &data_raw[(y / S) * bytes_per_line + (x / S) / 4];
1536 const size_t x4 = (x / S) % 4;
1539 for (size_t y6 = 0; y6 < 6; y6++) {
1541 if ((y + y6) < H * S) {
1542 out |= static_cast<uint8_t>(
1543 (static_cast<uint8_t>(((*ptr) >> (6 - x4 * 2)) & 3) == col) ? (1UL << 5) : 0);
1544 if ((y + y6) != ((H * S) - 1)) {
1547 ptr += bytes_per_line;
1554 [](
const uint8_t *data_raw,
size_t x,
size_t w,
size_t y, palette_bitset<uint8_t, 32> &set) {
1556 for (
size_t y6 = 0; y6 < 6; y6++) {
1557 if ((y + y6) < H * S) {
1558 for (
size_t xx = 0; xx < (w + S - 1) / S; xx++) {
1559 const uint8_t *ptr = &data_raw[((y + y6) / S) * bytes_per_line + (xx + x) / 4];
1560 const size_t x4 = (xx + x) % 4;
1561 set.mark(
static_cast<uint8_t
>(((*ptr) >> (6 - x4 * 2)) & 3));
1584template <
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
1588 static constexpr size_t bits_per_pixel = 4;
1589 static constexpr size_t bytes_per_line = (W * bits_per_pixel + 7) / 8;
1590 static constexpr size_t image_size = H * bytes_per_line;
1591 static constexpr size_t color_mask = 0x0f;
1593 static consteval auto gen_palette_consteval() {
1595 return std::array<uint32_t, (1UL << bits_per_pixel)>(
1596 {{0x000000, 0x111111, 0x222222, 0x333333, 0x444444, 0x555555, 0x666666, 0x777777, 0x888888, 0x999999,
1597 0xaaaaaa, 0xbbbbbb, 0xcccccc, 0xdddddd, 0xeeeeee, 0xffffff}});
1599 return std::array<uint32_t, (1UL << bits_per_pixel)>(
1600 {{0x000000, 0xffffff, 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0x00ffff, 0xff00ff, 0x333333, 0x666666,
1601 0x999999, 0xcccccc, 0x7f0000, 0x007f00, 0x00007f, 0x7f7f00}});
1603 static constexpr const auto quant = hidden::quantize<1UL << bits_per_pixel>(gen_palette_consteval());
1605 static constexpr uint8_t reverse(uint8_t b) {
1606 b =
static_cast<uint8_t
>((b & uint8_t{0xF0}) >> 4 | (b & uint8_t{0x0F}) << 4);
1610 static constexpr void transpose4x8(std::array<uint8_t, 8> &data) {
1611 for (
size_t i = 0; i < 2; i++) {
1612 for (
size_t j = 0; j < 2; j++) {
1613 auto shift =
static_cast<uint32_t
>(4U - j * 4U);
1614 uint8_t pixel = (data[i] >> shift) & 0x0FU;
1615 auto ishift =
static_cast<uint32_t
>(4U - i * 4U);
1617 static_cast<uint8_t
>((data[j] & ~(0x0FU << ishift)) | (
static_cast<uint32_t
>(pixel) << ishift));
1620 for (
size_t i = 0; i < 2; i++) {
1621 for (
size_t j = 0; j < 2; j++) {
1622 auto shift =
static_cast<uint32_t
>(4U - j * 4U);
1623 uint8_t pixel = (data[i + 2] >> shift) & 0x0FU;
1624 auto ishift =
static_cast<uint32_t
>(4U - i * 4U);
1626 static_cast<uint8_t
>((data[j + 2] & ~(0x0FU << ishift)) | (
static_cast<uint32_t
>(pixel) << ishift));
1629 for (
size_t i = 0; i < 2; i++) {
1630 for (
size_t j = 0; j < 2; j++) {
1631 auto shift =
static_cast<uint32_t
>(4U - j * 4U);
1632 uint8_t pixel = (data[i + 4] >> shift) & 0x0FU;
1633 auto ishift =
static_cast<uint32_t
>(4U - i * 4U);
1635 static_cast<uint8_t
>((data[j + 4] & ~(0x0FU << ishift)) | (
static_cast<uint32_t
>(pixel) << ishift));
1638 for (
size_t i = 0; i < 2; i++) {
1639 for (
size_t j = 0; j < 2; j++) {
1640 auto shift =
static_cast<uint32_t
>(4U - j * 4U);
1641 uint8_t pixel = (data[i + 6] >> shift) & 0x0FU;
1642 auto ishift =
static_cast<uint32_t
>(4U - i * 4U);
1644 static_cast<uint8_t
>((data[j + 6] & ~(0x0FU << ishift)) | (
static_cast<uint32_t
>(pixel) << ishift));
1649 template <
bool FLIP_H = false,
bool FLIP_V = false>
1650 static constexpr void transpose(
const uint8_t *src, uint8_t *dst) {
1651 for (
size_t y = 0; y < H; y++) {
1652 for (
size_t x = 0; x < W; x++) {
1653 size_t src_byte = y * bytes_per_line + (x / 2);
1654 size_t src_shift = (1 - (x % 2)) * 4;
1655 uint8_t pixel = (src[src_byte] >> src_shift) & 0x0F;
1657 size_t dst_x = FLIP_H ? (H - 1 - y) : y;
1658 size_t dst_y = FLIP_V ? (W - 1 - x) : x;
1659 size_t dst_byte = dst_y * ((H + 1) / 2) + (dst_x / 2);
1660 size_t dst_shift = (1 - (dst_x % 2)) * 4;
1662 dst[dst_byte] &= ~(0x0F << dst_shift);
1663 dst[dst_byte] |= pixel << dst_shift;
1668 static constexpr void compose(std::span<uint8_t, image_size> data,
size_t x,
size_t y,
float cola,
float colr,
1669 float colg,
float colb) {
1670 const auto bg =
static_cast<size_t>(get_col(data, x, y));
1671 const float Rl = colr * cola + quant.linear_palette().at(bg * 3 + 0) * (1.0f - cola);
1672 const float Gl = colg * cola + quant.linear_palette().at(bg * 3 + 1) * (1.0f - cola);
1673 const float Bl = colb * cola + quant.linear_palette().at(bg * 3 + 2) * (1.0f - cola);
1674 plot(data, x, y, quant.nearest_linear(Rl, Gl, Bl));
1677 static constexpr void plot(std::span<uint8_t, image_size> data,
size_t x0,
size_t y, uint8_t col) {
1678 col &= (1UL << bits_per_pixel) - 1;
1679 const size_t x2 = x0 / 2;
1681 uint8_t *yptr = &data.data()[y * bytes_per_line];
1682 yptr[x2] &= ~static_cast<uint8_t>(0xFUL << (4 - x0 * 4));
1683 yptr[x2] |=
static_cast<uint8_t
>(col << (4 - x0 * 4));
1686 static constexpr void extent(std::span<uint8_t, image_size> data,
size_t xl0,
size_t xr0,
size_t y, uint8_t col) {
1687 col &= (1UL << bits_per_pixel) - 1;
1688 const size_t xl2 = xl0 / 2;
1690 const size_t xr2 = xr0 / 2;
1692 const size_t xs2 = xr2 - xl2;
1693 const auto c2 =
static_cast<uint8_t
>(col << 4 | col << 0);
1694 constexpr std::array<uint8_t, 2> ml = {{0b11111111, 0b00001111}};
1695 constexpr std::array<uint8_t, 2> mr = {{0b00000000, 0b11110000}};
1696 uint8_t *yptr = &data.data()[y * bytes_per_line];
1698 yptr[xl2] &=
static_cast<uint8_t
>(~ml[xl0]);
1699 yptr[xl2] |=
static_cast<uint8_t
>(ml[xl0] & c2);
1700 for (
size_t x = xl2 + 1; x < xr2; x++) {
1704 yptr[xr2] &=
static_cast<uint8_t
>(~mr[xr0]);
1705 yptr[xr2] |=
static_cast<uint8_t
>(mr[xr0] & c2);
1708 yptr[xl2] &=
static_cast<uint8_t
>(~(ml[xl0] & mr[xr0]));
1709 yptr[xl2] |=
static_cast<uint8_t
>(ml[xl0] & mr[xr0] & c2);
1713 [[nodiscard]]
static constexpr uint8_t get_col(
const uint8_t *line,
size_t x) {
1714 const size_t x2 = x / 2;
1715 const size_t xb = x % 2;
1716 return static_cast<uint8_t
>((line[x2] >> ((1 - xb) * 4)) & 0xF);
1719 [[nodiscard]]
static constexpr uint8_t get_col(
const std::span<const uint8_t, image_size> data,
size_t x,
1721 const uint8_t *ptr = data.data() + y * bytes_per_line;
1722 const size_t x2 = x / 2;
1723 const size_t xb = x % 2;
1724 return static_cast<uint8_t
>((ptr[x2] >> ((1 - xb) * 4)) & 0xF);
1727 static constexpr void RGBA_uint32(std::array<uint32_t, W * H> &dst,
1728 const std::span<const uint8_t, image_size> &src) {
1729 const uint8_t *ptr = src.data();
1730 for (
size_t y = 0; y < H; y++) {
1731 for (
size_t x = 0; x < W; x++) {
1732 const uint8_t col = get_col(ptr, x);
1733 dst[y * W + x] = quant.palette().at(col) | ((col != 0) ? 0xFF000000 : 0x00000000);
1735 ptr += bytes_per_line;
1739 static constexpr void RGBA_uint8(std::array<uint8_t, W * H * 4> &dst,
1740 const std::span<const uint8_t, image_size> &src) {
1741 const uint8_t *ptr = src.data();
1742 for (
size_t y = 0; y < H; y++) {
1743 for (
size_t x = 0; x < W; x++) {
1744 const uint8_t col = get_col(ptr, x);
1745 dst[y * W * 4 + x * 4 + 0] =
static_cast<uint8_t
>((quant.palette().at(col) >> 0) & 0xFF);
1746 dst[y * W * 4 + x * 4 + 1] =
static_cast<uint8_t
>((quant.palette().at(col) >> 8) & 0xFF);
1747 dst[y * W * 4 + x * 4 + 2] =
static_cast<uint8_t
>((quant.palette().at(col) >> 16) & 0xFF);
1748 dst[y * W * 4 + x * 4 + 3] = ((col != 0) ? 0xFF : 0x00);
1750 ptr += bytes_per_line;
1754 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
const uint8_t *ptr,
1756 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1757 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1758 for (
size_t y = 0; y < r_h; y++) {
1759 for (
size_t x = 0; x < r_w; x++) {
1760 plot(data, (x +
static_cast<size_t>(r.
x)), (y +
static_cast<size_t>(r.
y)),
1761 quant.nearest(ptr[y *
static_cast<size_t>(stride) + x * 4 + 0],
1762 ptr[y *
static_cast<size_t>(stride) + x * 4 + 1],
1763 ptr[y *
static_cast<size_t>(stride) + x * 4 + 2]));
1768 static constexpr void blit_RGBA_diffused(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
1769 const uint8_t *ptr, int32_t stride) {
1770 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1771 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1772 for (
size_t y = 0; y < r_h; y++) {
1776 for (
size_t x = 0; x < r_w; x++) {
1777 int32_t R = ptr[y *
static_cast<size_t>(stride) + x * 4 + 0];
1778 int32_t G = ptr[y *
static_cast<size_t>(stride) + x * 4 + 1];
1779 int32_t B = ptr[y *
static_cast<size_t>(stride) + x * 4 + 2];
1783 uint8_t n = quant.nearest(R, G, B);
1784 plot(data, (x +
static_cast<size_t>(r.
x)), (y +
static_cast<size_t>(r.
y)), n);
1785 err_r = std::clamp(R -
static_cast<int32_t
>((quant.palette().at(n) >> 0) & 0xFF), int32_t{-255},
1787 err_g = std::clamp(G -
static_cast<int32_t
>((quant.palette().at(n) >> 8) & 0xFF), int32_t{-255},
1789 err_b = std::clamp(B -
static_cast<int32_t
>((quant.palette().at(n) >> 16) & 0xFF), int32_t{-255},
1795 static constexpr void blit_RGBA_diffused_linear(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
1796 const uint8_t *ptr, int32_t stride) {
1797 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
1798 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
1799 for (
size_t y = 0; y < r_h; y++) {
1803 for (
size_t x = 0; x < r_w; x++) {
1804 const auto R =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 0]) * (1.0f / 255.0f);
1805 const auto G =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 1]) * (1.0f / 255.0f);
1806 const auto B =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 2]) * (1.0f / 255.0f);
1807 float Rl = hidden::srgb_to_linear(R);
1808 float Gl = hidden::srgb_to_linear(G);
1809 float Bl = hidden::srgb_to_linear(B);
1813 uint8_t n = quant.nearest_linear(Rl, Gl, Bl);
1814 plot(data, (x +
static_cast<size_t>(r.
x)), (y +
static_cast<size_t>(r.
y)), n);
1815 err_r = std::clamp(Rl - quant.linear_palette().at(n *
size_t{3} +
size_t{0}), -1.0f, 1.0f);
1816 err_g = std::clamp(Gl - quant.linear_palette().at(n *
size_t{3} +
size_t{1}), -1.0f, 1.0f);
1817 err_b = std::clamp(Bl - quant.linear_palette().at(n *
size_t{3} +
size_t{2}), -1.0f, 1.0f);
1822 template <
typename F>
1823 static constexpr void png(
const std::span<const uint8_t, image_size> data, F &&char_out) {
1824 png_image<W, H, uint8_t, bits_per_pixel>(data.data(), quant.palette(), std::forward<F>(char_out),
1825 [](
const uint8_t *data_raw,
size_t y,
size_t &bpl) {
1826 bpl = bytes_per_line;
1827 return data_raw + y * bytes_per_line;
1831 template <
size_t S,
typename F>
1832 static constexpr void sixel(
const std::span<const uint8_t, image_size> data, F &&char_out,
const rect<int32_t> &r) {
1833 sixel_image<W, H, S, uint8_t, bits_per_pixel>(
1834 data.data(), quant.palette(), std::forward<F>(char_out), r,
1835 [](
const uint8_t *data_raw,
size_t x,
size_t col,
size_t y) {
1836 const uint8_t *ptr = &data_raw[(y / S) * bytes_per_line + (x / S) / 2];
1837 const size_t x2 = (x / S) % 2;
1840 for (size_t y6 = 0; y6 < 6; y6++) {
1842 if ((y + y6) < H * S) {
1843 out |= static_cast<uint8_t>(
1844 (static_cast<uint8_t>(((*ptr) >> (4 - x2 * 4)) & 0xF) == col) ? (1UL << 5) : 0);
1845 if ((y + y6) != ((H * S) - 1)) {
1848 ptr += bytes_per_line;
1855 [](
const uint8_t *data_raw,
size_t x,
size_t w,
size_t y, palette_bitset<uint8_t, 32> &set) {
1857 for (
size_t y6 = 0; y6 < 6; y6++) {
1858 if ((y + y6) < H * S) {
1859 for (
size_t xx = 0; xx < (w + S - 1) / S; xx++) {
1860 const uint8_t *ptr = &data_raw[((y + y6) / S) * bytes_per_line + (xx + x) / 2];
1861 const size_t x2 = (xx + x) % 2;
1862 set.mark(
static_cast<uint8_t
>(((*ptr) >> (4 - x2 * 4)) & 0xF));
1885template <
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
1889 static constexpr size_t bits_per_pixel = 8;
1890 static constexpr size_t bytes_per_line = W;
1891 static constexpr size_t image_size = H * bytes_per_line;
1892 static constexpr size_t color_mask = 0xFF;
1894 static consteval auto gen_palette_consteval() {
1895 std::array<uint32_t, (1UL << bits_per_pixel)> pal{};
1897 for (
size_t c = 0; c < 256; c++) {
1898 pal[c] =
static_cast<uint32_t
>((c << 16) | (c << 8) | c);
1917 for (
size_t c = 0; c < 16; c++) {
1918 const uint32_t y = (0xff *
static_cast<uint32_t
>(c)) / 15;
1919 pal[0x10 + c] = (y << 16) | (y << 8) | (y << 0);
1921 for (
size_t c = 0; c < 8; c++) {
1922 const uint32_t y = (0xff *
static_cast<uint32_t
>(c)) / 7;
1923 const uint32_t x = (0xff * (
static_cast<uint32_t
>(c) + 1)) / 8;
1924 pal[0x20 + c + 0] = (y << 16) | (0 << 8) | (0 << 0);
1925 pal[0x20 + c + 8] = (255 << 16) | (x << 8) | (x << 0);
1926 pal[0x30 + c + 0] = (0 << 16) | (y << 8) | (0 << 0);
1927 pal[0x30 + c + 8] = (x << 16) | (255 << 8) | (x << 0);
1928 pal[0x40 + c + 0] = (0 << 16) | (0 << 8) | (y << 0);
1929 pal[0x40 + c + 8] = (x << 16) | (x << 8) | (255 << 0);
1930 pal[0x50 + c + 0] = (y << 16) | (y << 8) | (0 << 0);
1931 pal[0x50 + c + 8] = (255 << 16) | (255 << 8) | (x << 0);
1932 pal[0x60 + c + 0] = (0 << 16) | (y << 8) | (y << 0);
1933 pal[0x60 + c + 8] = (x << 16) | (255 << 8) | (255 << 0);
1934 pal[0x70 + c + 0] = (y << 16) | (0 << 8) | (y << 0);
1935 pal[0x70 + c + 8] = (255 << 16) | (x << 8) | (255 << 0);
1937 for (
size_t c = 0; c < 8; c++) {
1938 const hidden::oklab lft{.l =
static_cast<double>(c) / 7 - 0.2, .a = 0.2, .b = 0.0};
1939 const hidden::oklab rgh{.l =
static_cast<double>(c) / 7 - 0.2, .a = 0.2, .b = 337.5};
1940 for (
size_t d = 0; d < 16; d++) {
1941 auto res = hidden::oklab_to_srgb_consteval(hidden::oklch_to_oklab_consteval(hidden::oklch{
1942 .l = std::lerp(lft.l, rgh.l,
static_cast<double>(d) / 15.0),
1943 .c = std::lerp(lft.a, rgh.a,
static_cast<double>(d) / 15.0),
1944 .h = std::lerp(lft.b, rgh.b,
static_cast<double>(d) / 15.0),
1946 pal[0x80 + c * 16 + d] =
1947 (
static_cast<uint32_t
>(std::max(0.0, std::min(1.0, res.r)) * 255.0) << 16) |
1948 (
static_cast<uint32_t
>(std::max(0.0, std::min(1.0, res.g)) * 255.0) << 8) |
1949 (
static_cast<uint32_t
>(std::max(0.0, std::min(1.0, res.b)) * 255.0) << 0);
1955 static constexpr const auto quant = hidden::quantize<1UL << bits_per_pixel>(gen_palette_consteval());
1957 static constexpr uint8_t reverse(uint8_t b) {
1961 template <
bool FLIP_H = false,
bool FLIP_V = false>
1962 static constexpr void transpose(
const uint8_t *src, uint8_t *dst) {
1963 for (
size_t y = 0; y < H; y++) {
1964 for (
size_t x = 0; x < W; x++) {
1965 if constexpr (FLIP_H) {
1966 if constexpr (FLIP_V) {
1967 dst[(W - x - 1) * H + (H - y - 1)] = *src++;
1969 dst[(W - x - 1) * H + y] = *src++;
1972 if constexpr (FLIP_V) {
1973 dst[x * H + (H - y - 1)] = *src++;
1975 dst[x * H + y] = *src++;
1982 static constexpr void plot(std::span<uint8_t, image_size> data,
size_t x,
size_t y, uint8_t col) {
1983 data.data()[y * bytes_per_line + x] = col;
1986 static constexpr void extent(std::span<uint8_t, image_size> data,
size_t xl0,
size_t xr0,
size_t y, uint8_t col) {
1987 uint8_t *yptr = &data.data()[y * bytes_per_line];
1988 for (
size_t x = xl0; x < xr0; x++) {
1993 static constexpr void compose(std::span<uint8_t, image_size> data,
size_t x,
size_t y,
float cola,
float colr,
1994 float colg,
float colb) {
1996#if defined(__ARM_NEON)
1997 if (!std::is_constant_evaluated()) {
1998 auto bg =
static_cast<size_t>(data.data()[y * bytes_per_line + x]);
1999 const float32x4_t cola_v = vdupq_n_f32(cola);
2000 const float32x4_t inv_cola_v = vdupq_n_f32(1.0f - cola);
2001 const float32x4_t col_rgb = {colr, colg, colb, 0.0f};
2003 const float32x4_t bg_rgb = vld1q_f32(&quant.linear_palette()[bg * 3]);
2004 const float32x4_t result_rgb = vaddq_f32(vmulq_f32(col_rgb, cola_v), vmulq_f32(bg_rgb, inv_cola_v));
2005 alignas(16) std::array<float, 4> result{};
2006 vst1q_f32(result.data(), result_rgb);
2007 plot(data, x, y, quant.nearest_linear(result[0], result[1], result[2]));
2010#if defined(__AVX2__)
2011 if (!std::is_constant_evaluated()) {
2012 __m128 cola_v = _mm_set1_ps(cola);
2013 __m128 inv_cola_v = _mm_set1_ps(1.0f - cola);
2014 __m128 col_rgb = _mm_set_ps(0.0f, colb, colg, colr);
2015 auto bg =
static_cast<size_t>(data.data()[y * bytes_per_line + x]);
2017 __m128 bg_rgb = _mm_loadu_ps(&quant.linear_palette()[bg * 3]);
2018 __m128 result_rgb = _mm_add_ps(_mm_mul_ps(col_rgb, cola_v), _mm_mul_ps(bg_rgb, inv_cola_v));
2019 alignas(16)
float result[4];
2020 _mm_store_ps(result, result_rgb);
2021 plot(data, x, y, quant.nearest_linear(result[0], result[1], result[2]));
2025 const auto bg =
static_cast<size_t>(data.data()[y * bytes_per_line + x]);
2026 const float Rl = colr * cola + quant.linear_palette().at(bg * 3 + 0) * (1.0f - cola);
2027 const float Gl = colg * cola + quant.linear_palette().at(bg * 3 + 1) * (1.0f - cola);
2028 const float Bl = colb * cola + quant.linear_palette().at(bg * 3 + 2) * (1.0f - cola);
2029 plot(data, x, y, quant.nearest_linear(Rl, Gl, Bl));
2034 static constexpr void RGBA_uint32(std::span<uint32_t, W * H> dst,
const std::span<const uint8_t, image_size> &src) {
2035 const uint8_t *ptr = src.data();
2036 for (
size_t y = 0; y < H; y++) {
2037 for (
size_t x = 0; x < W; x++) {
2038 const uint8_t col = ptr[x];
2039 dst[y * W + x] = quant.palette().at(col) | ((col != 0) ? 0xFF000000 : 0x00000000);
2041 ptr += bytes_per_line;
2045 static constexpr void RGBA_uint8(std::array<uint8_t, W * H * 4> &dst,
2046 const std::span<const uint8_t, image_size> &src) {
2047 const uint8_t *ptr = src.data();
2048 for (
size_t y = 0; y < H; y++) {
2049 for (
size_t x = 0; x < W; x++) {
2050 const uint8_t col = ptr[x];
2051 dst[y * W * 4 + x * 4 + 0] =
static_cast<uint8_t
>((quant.palette().at(col) >> 0) & 0xFF);
2052 dst[y * W * 4 + x * 4 + 1] =
static_cast<uint8_t
>((quant.palette().at(col) >> 8) & 0xFF);
2053 dst[y * W * 4 + x * 4 + 2] =
static_cast<uint8_t
>((quant.palette().at(col) >> 16) & 0xFF);
2054 dst[y * W * 4 + x * 4 + 3] = ((col != 0) ? 0xFF : 0x00);
2056 ptr += bytes_per_line;
2060 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
const uint8_t *ptr,
2062 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
2063 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
2064 for (
size_t y = 0; y < r_h; y++) {
2065 for (
size_t x = 0; x < r_w; x++) {
2066 data.data()[(y +
static_cast<size_t>(r.
y)) * bytes_per_line + (x +
static_cast<size_t>(r.
x))] =
2067 quant.nearest(ptr[y *
static_cast<size_t>(stride) + x * 4 + 0],
2068 ptr[y *
static_cast<size_t>(stride) + x * 4 + 1],
2069 ptr[y *
static_cast<size_t>(stride) + x * 4 + 2]);
2074 static constexpr void blit_RGBA_diffused(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
2075 const uint8_t *ptr, int32_t stride) {
2076 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
2077 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
2078 for (
size_t y = 0; y < r_h; y++) {
2082 for (
size_t x = 0; x < r_w; x++) {
2083 int32_t R = ptr[y *
static_cast<size_t>(stride) + x * 4 + 0];
2084 int32_t G = ptr[y *
static_cast<size_t>(stride) + x * 4 + 1];
2085 int32_t B = ptr[y *
static_cast<size_t>(stride) + x * 4 + 2];
2089 uint8_t n = quant.nearest(R, G, B);
2090 data.data()[(y +
static_cast<size_t>(r.
y)) * bytes_per_line + (x +
static_cast<size_t>(r.
x))] = n;
2091 err_r = std::clamp(R -
static_cast<int32_t
>((quant.palette().at(n) >> 0) & 0xFF), int32_t{-255},
2093 err_g = std::clamp(G -
static_cast<int32_t
>((quant.palette().at(n) >> 8) & 0xFF), int32_t{-255},
2095 err_b = std::clamp(B -
static_cast<int32_t
>((quant.palette().at(n) >> 16) & 0xFF), int32_t{-255},
2101 static constexpr void blit_RGBA_diffused_linear(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
2102 const uint8_t *ptr, int32_t stride) {
2103 auto r_w =
static_cast<size_t>(r.
w < 0 ? 0 : r.
w);
2104 auto r_h =
static_cast<size_t>(r.
h < 0 ? 0 : r.
h);
2105 for (
size_t y = 0; y < r_h; y++) {
2109 for (
size_t x = 0; x < r_w; x++) {
2110 const auto R =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 0]) * (1.0f / 255.0f);
2111 const auto G =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 1]) * (1.0f / 255.0f);
2112 const auto B =
static_cast<float>(ptr[y *
static_cast<size_t>(stride) + x * 4 + 2]) * (1.0f / 255.0f);
2113 float Rl = hidden::srgb_to_linear(R);
2114 float Gl = hidden::srgb_to_linear(G);
2115 float Bl = hidden::srgb_to_linear(B);
2119 uint8_t n = quant.nearest_linear(Rl, Gl, Bl);
2120 data.data()[(y +
static_cast<size_t>(r.
y)) * bytes_per_line + (x +
static_cast<size_t>(r.
x))] = n;
2121 err_r = std::clamp(Rl - quant.linear_palette().at(n *
size_t{3} +
size_t{0}), -1.0f, 1.0f);
2122 err_g = std::clamp(Gl - quant.linear_palette().at(n *
size_t{3} +
size_t{1}), -1.0f, 1.0f);
2123 err_b = std::clamp(Bl - quant.linear_palette().at(n *
size_t{3} +
size_t{2}), -1.0f, 1.0f);
2128 template <
typename F>
2129 static constexpr void png(
const std::span<const uint8_t, image_size> data, F &&char_out) {
2130 png_image<W, H, uint8_t, bits_per_pixel>(data.data(), quant.palette(), std::forward<F>(char_out),
2131 [](
const uint8_t *data_raw,
size_t y,
size_t &bpl) {
2132 bpl = bytes_per_line;
2133 return data_raw + y * bytes_per_line;
2137 template <
size_t S,
typename F>
2138 static constexpr void sixel(
const std::span<const uint8_t, image_size> data, F &&char_out,
const rect<int32_t> &r) {
2139 sixel_image<W, H, S, uint8_t, bits_per_pixel>(
2140 data.data(), quant.palette(), std::forward<F>(char_out), r,
2141 [](
const uint8_t *data_raw,
size_t x,
size_t col,
size_t y) {
2142 const uint8_t *ptr = &data_raw[(y / S) * bytes_per_line + x / S];
2145 for (size_t y6 = 0; y6 < 6; y6++) {
2147 if ((y + y6) < H * S) {
2148 out |= static_cast<uint8_t>((*ptr == col) ? (1UL << 5) : 0);
2149 if ((y + y6) != ((H * S) - 1)) {
2152 ptr += bytes_per_line;
2159 [](
const uint8_t *data_raw,
size_t x,
size_t w,
size_t y,
2160 palette_bitset<uint8_t, 1UL << bits_per_pixel> &set) {
2161 const uint8_t *ptr = &data_raw[(y / S) * bytes_per_line + x / S];
2163 for (
size_t y6 = 0; y6 < 6; y6++) {
2164 if ((y + y6) < H * S) {
2165 for (
size_t xx = 0; xx < (w + S - 1) / S; xx++) {
2166 set.mark(ptr[xx + x]);
2168 if ((y + y6) != ((H * S) - 1)) {
2171 ptr += bytes_per_line;
2192template <
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
2196 static constexpr size_t bits_per_pixel = 24;
2197 static constexpr size_t bytes_per_line = W * 3;
2198 static constexpr size_t image_size = H * bytes_per_line;
2199 static constexpr size_t color_mask = 0xFF;
2201 static consteval auto gen_palette_consteval() {
2204 static constexpr const auto quant = hidden::quantize<1UL << 8>(gen_palette_consteval());
2206 template <
bool FLIP_H = false,
bool FLIP_V = false>
2207 static constexpr void transpose(
const uint8_t *src, uint8_t *dst) {
2208 for (
size_t y = 0; y < H; y++) {
2209 for (
size_t x = 0; x < W; x++) {
2210 if constexpr (FLIP_H) {
2211 if constexpr (FLIP_V) {
2212 dst[(W - x - 1) * H * 3 + (H - y - 1) * 3 + 0] = *src++;
2213 dst[(W - x - 1) * H * 3 + (H - y - 1) * 3 + 1] = *src++;
2214 dst[(W - x - 1) * H * 3 + (H - y - 1) * 3 + 2] = *src++;
2216 dst[(W - x - 1) * H * 3 + y * 3 + 0] = *src++;
2217 dst[(W - x - 1) * H * 3 + y * 3 + 1] = *src++;
2218 dst[(W - x - 1) * H * 3 + y * 3 + 2] = *src++;
2221 if constexpr (FLIP_V) {
2222 dst[x * H * 3 + (H - y - 1) * 3 + 0] = *src++;
2223 dst[x * H * 3 + (H - y - 1) * 3 + 1] = *src++;
2224 dst[x * H * 3 + (H - y - 1) * 3 + 2] = *src++;
2226 dst[x * H * 3 + y * 3 + 0] = *src++;
2227 dst[x * H * 3 + y * 3 + 1] = *src++;
2228 dst[x * H * 3 + y * 3 + 2] = *src++;
2235 static constexpr void plot(std::span<uint8_t, image_size> data,
size_t x,
size_t y, uint8_t col) {
2236 uint8_t *ptr = &data.data()[y * bytes_per_line + x * 3];
2237 ptr[0] = (quant.palette().at(col) >> 0) & 0xFF;
2238 ptr[1] = (quant.palette().at(col) >> 8) & 0xFF;
2239 ptr[2] = (quant.palette().at(col) >> 16) & 0xFF;
2242 static constexpr void extent(std::span<uint8_t, image_size> data,
size_t xl0,
size_t xr0,
size_t y, uint8_t col) {
2243 uint8_t *yptr = &data.data()[y * bytes_per_line + xl0 * 3];
2244 uint32_t rgba = quant.palette().at(col);
2245 for (
size_t x = xl0; x < xr0; x++) {
2246 *yptr++ = (rgba >> 0) & 0xFF;
2247 *yptr++ = (rgba >> 8) & 0xFF;
2248 *yptr++ = (rgba >> 16) & 0xFF;
2252 static constexpr void compose(std::span<uint8_t, image_size> data,
size_t x,
size_t y,
float cola,
float colr,
2253 float colg,
float colb) {
2254#if defined(__ARM_NEON)
2255 if (!std::is_constant_evaluated()) {
2256 const size_t off = y * bytes_per_line + x * 3;
2257 uint8x8_t px = vld1_u8(&data[off]);
2258 float32x4_t src = {colr, colg, colb, cola};
2259 float32x4_t dst = {hidden::a2al_8bit[px[0]], hidden::a2al_8bit[px[1]], hidden::a2al_8bit[px[2]], 1.0f};
2260 float32x4_t one = vdupq_n_f32(1.0f);
2261 float32x4_t inv_srca = vsubq_f32(one, vdupq_n_f32(cola));
2262 float32x4_t blended = vmlaq_f32(vmulq_n_f32(src, cola), dst, inv_srca);
2264 float outa = cola + dst[3] * (1.0f - cola);
2265 float32x4_t norm = vmulq_f32(blended, vdupq_n_f32(1.0f / outa));
2266 float32x4_t
final = hidden::linear_to_srgb_approx_neon(norm);
2269 out[0] =
static_cast<uint8_t
>(
final[0] * 255.0f);
2270 out[1] =
static_cast<uint8_t
>(
final[1] * 255.0f);
2271 out[2] =
static_cast<uint8_t
>(
final[2] * 255.0f);
2272 vst1_lane_u32(
reinterpret_cast<uint32_t *
>(&data[off]), vreinterpret_u32_u8(out), 0);
2276#if defined(__AVX2__)
2277 if (!std::is_constant_evaluated()) {
2278 const size_t off = y * bytes_per_line + x * 3;
2280 const float lr = hidden::a2al_8bit[data[off + 0]];
2281 const float lg = hidden::a2al_8bit[data[off + 1]];
2282 const float lb = hidden::a2al_8bit[data[off + 2]];
2283 const float la = 1.0;
2285 const float as = cola + la * (1.0f - cola);
2286 const float inv_cola = 1.0f - cola;
2288 __m128 dst = _mm_set_ps(0.0f, lb, lg, lr);
2289 __m128 src = _mm_set_ps(0.0f, colb, colg, colr);
2291 __m128 blended = _mm_add_ps(_mm_mul_ps(src, _mm_set1_ps(cola)), _mm_mul_ps(dst, _mm_set1_ps(inv_cola)));
2293 __m128 denom = _mm_set_ss(as);
2294 __m128 inv_as = _mm_rcp_ss(denom);
2295 inv_as = _mm_shuffle_ps(inv_as, inv_as, _MM_SHUFFLE(0, 0, 0, 0));
2297 __m128 scaled = _mm_mul_ps(blended, inv_as);
2298 __m128 srgb = hidden::linear_to_srgb_approx_sse(scaled);
2300 alignas(16)
float out[4];
2301 _mm_store_ps(out, srgb);
2303 data[off + 0] =
static_cast<uint8_t
>(out[0] * 255.0f);
2304 data[off + 1] =
static_cast<uint8_t
>(out[1] * 255.0f);
2305 data[off + 2] =
static_cast<uint8_t
>(out[2] * 255.0f);
2309 const size_t off = y * bytes_per_line + x * 3;
2311 const float lr = hidden::a2al_8bit[data[off + 0]];
2312 const float lg = hidden::a2al_8bit[data[off + 1]];
2313 const float lb = hidden::a2al_8bit[data[off + 2]];
2314 const float la = 1.0f;
2316 const float as = cola + la * (1.0f - cola);
2317 const float rs = hidden::linear_to_srgb(colr * cola + lr * (1.0f - cola)) * (1.0f / as);
2318 const float gs = hidden::linear_to_srgb(colg * cola + lg * (1.0f - cola)) * (1.0f / as);
2319 const float bs = hidden::linear_to_srgb(colb * cola + lb * (1.0f - cola)) * (1.0f / as);
2321 data[off + 0] =
static_cast<uint8_t
>(rs * 255.0f);
2322 data[off + 1] =
static_cast<uint8_t
>(gs * 255.0f);
2323 data[off + 2] =
static_cast<uint8_t
>(bs * 255.0f);
2326 static constexpr void RGBA_uint32(std::array<uint32_t, W * H> &dst,
2327 const std::span<const uint8_t, image_size> &src) {
2328 for (
size_t y = 0; y < H; y++) {
2329 for (
size_t x = 0; x < W; x++) {
2330 dst.data()[y * W + x] =
static_cast<uint32_t
>(
2331 (src.data()[y * bytes_per_line + x * 3 + 0]) | (src.data()[y * bytes_per_line + x * 3 + 1] << 8) |
2332 (src.data()[y * bytes_per_line + x * 3 + 2] << 16) | 0xFF000000);
2337 static constexpr void RGBA_uint8(std::array<uint8_t, W * H * 4> &dst,
2338 const std::span<const uint8_t, image_size> &src) {
2339 const uint8_t *src_ptr = src.data();
2340 uint8_t *dst_ptr = dst.data();
2341 for (
size_t y = 0; y < H; y++) {
2342 for (
size_t x = 0; x < W; x++) {
2343 dst_ptr[x * 4 + 0] = src_ptr[x * 3 + 0];
2344 dst_ptr[x * 4 + 1] = src_ptr[x * 3 + 1];
2345 dst_ptr[x * 4 + 2] = src_ptr[x * 3 + 2];
2346 dst_ptr[x * 4 + 3] = 0xFF;
2348 src_ptr += bytes_per_line;
2353 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
const uint8_t *ptr,
2355 rect<int32_t> intersect_rect{.x = 0, .y = 0, .w = W, .h = H};
2357 auto const r_x =
static_cast<size_t>(intersect_rect.x);
2358 auto const r_y =
static_cast<size_t>(intersect_rect.y);
2359 auto const r_w =
static_cast<size_t>(intersect_rect.w);
2360 auto const r_h =
static_cast<size_t>(intersect_rect.h);
2361 const uint8_t *src = ptr;
2362 uint8_t *dst = data.data() + r_y * bytes_per_line + r_x * 3;
2363 for (
size_t y = 0; y < r_h; y++) {
2364 for (
size_t x = 0; x < r_w; x++) {
2365 dst[x * 3 + 0] = src[x * 4 + 0];
2366 dst[x * 3 + 1] = src[x * 4 + 1];
2367 dst[x * 3 + 2] = src[x * 4 + 2];
2369 dst += bytes_per_line;
2374 static constexpr void blit_RGBA_diffused(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
2375 const uint8_t *ptr, int32_t stride) {
2376 blit_RGBA(data, r, ptr, stride);
2379 static constexpr void blit_RGBA_diffused_linear(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
2380 const uint8_t *ptr, int32_t stride) {
2381 blit_RGBA(data, r, ptr, stride);
2384 template <
typename F>
2385 static constexpr void png(
const std::span<const uint8_t, image_size> data, F &&char_out) {
2386 png_image<W, H, uint8_t, bits_per_pixel>(data.data(), quant.palette(), std::forward<F>(char_out),
2387 [](
const uint8_t *data_raw,
size_t y,
size_t &bpl) {
2388 bpl = bytes_per_line;
2389 return data_raw + y * bytes_per_line;
2393 template <
size_t S,
typename F>
2394 static constexpr void sixel(
const std::span<const uint8_t, image_size> & , F && ,
2397 static_assert(
false,
"Sixel not available for format_24bit.");
2414template <
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
2418 static constexpr size_t bits_per_pixel = 32;
2419 static constexpr size_t bytes_per_line = W * 4;
2420 static constexpr size_t image_size = H * bytes_per_line;
2421 static constexpr size_t color_mask = 0xFF;
2423 static consteval auto gen_palette_consteval() {
2426 static constexpr const auto quant = hidden::quantize<1UL << 8>(gen_palette_consteval());
2428 template <
bool FLIP_H = false,
bool FLIP_V = false>
2429 static constexpr void transpose(
const uint8_t *src, uint8_t *dst) {
2430 for (
size_t y = 0; y < H; y++) {
2431 for (
size_t x = 0; x < W; x++) {
2432 if constexpr (FLIP_H) {
2433 if constexpr (FLIP_V) {
2434 dst[(W - x - 1) * H * 4 + (H - y - 1) * 4 + 0] = *src++;
2435 dst[(W - x - 1) * H * 4 + (H - y - 1) * 4 + 1] = *src++;
2436 dst[(W - x - 1) * H * 4 + (H - y - 1) * 4 + 2] = *src++;
2437 dst[(W - x - 1) * H * 4 + (H - y - 1) * 4 + 3] = *src++;
2439 dst[(W - x - 1) * H * 4 + y * 4 + 0] = *src++;
2440 dst[(W - x - 1) * H * 4 + y * 4 + 1] = *src++;
2441 dst[(W - x - 1) * H * 4 + y * 4 + 2] = *src++;
2442 dst[(W - x - 1) * H * 4 + y * 4 + 3] = *src++;
2445 if constexpr (FLIP_V) {
2446 dst[x * H * 4 + (H - y - 1) * 4 + 0] = *src++;
2447 dst[x * H * 4 + (H - y - 1) * 4 + 1] = *src++;
2448 dst[x * H * 4 + (H - y - 1) * 4 + 2] = *src++;
2449 dst[x * H * 4 + (H - y - 1) * 4 + 3] = *src++;
2451 dst[x * H * 4 + y * 4 + 0] = *src++;
2452 dst[x * H * 4 + y * 4 + 1] = *src++;
2453 dst[x * H * 4 + y * 4 + 2] = *src++;
2454 dst[x * H * 4 + y * 4 + 3] = *src++;
2461 static constexpr void plot(std::span<uint8_t, image_size> data,
size_t x,
size_t y, uint8_t col) {
2462 if (std::is_constant_evaluated()) {
2463 uint8_t *ptr = &data.data()[y * bytes_per_line + x * 4];
2464 ptr[0] = (quant.palette().at(col) >> 0) & 0xFF;
2465 ptr[1] = (quant.palette().at(col) >> 8) & 0xFF;
2466 ptr[2] = (quant.palette().at(col) >> 16) & 0xFF;
2469 uint32_t *yptr =
reinterpret_cast<uint32_t *
>(&data.data()[y * bytes_per_line + x * 4]);
2470 *yptr = quant.palette().at(col) | 0xFF000000;
2474 static constexpr void extent(std::span<uint8_t, image_size> data,
size_t xl0,
size_t xr0,
size_t y, uint8_t col) {
2475 if (std::is_constant_evaluated()) {
2476 uint8_t *yptr = &data.data()[y * bytes_per_line + xl0 * 4];
2477 uint32_t rgba = quant.palette().at(col);
2478 for (
size_t x = xl0; x < xr0; x++) {
2479 *yptr++ = (rgba >> 0) & 0xFF;
2480 *yptr++ = (rgba >> 8) & 0xFF;
2481 *yptr++ = (rgba >> 16) & 0xFF;
2485 uint32_t *yptr =
reinterpret_cast<uint32_t *
>(&data.data()[y * bytes_per_line + xl0 * 4]);
2486 uint32_t rgba = quant.palette().at(col) | 0xFF000000;
2487 for (
size_t x = xl0; x < xr0; x++) {
2493 static constexpr void compose(std::span<uint8_t, image_size> data,
size_t x,
size_t y,
float cola,
float colr,
2494 float colg,
float colb) {
2495#if defined(__ARM_NEON)
2496 if (!std::is_constant_evaluated()) {
2497 const size_t off = y * bytes_per_line + x * 4;
2498 uint8x8_t px = vld1_u8(&data[off]);
2499 float32x4_t src = {colr, colg, colb, cola};
2500 float32x4_t dst = {hidden::a2al_8bit[px[0]], hidden::a2al_8bit[px[1]], hidden::a2al_8bit[px[2]],
2501 hidden::a2al_8bit[px[3]]};
2502 float32x4_t one = vdupq_n_f32(1.0f);
2503 float32x4_t inv_srca = vsubq_f32(one, vdupq_n_f32(cola));
2504 float32x4_t blended = vmlaq_f32(vmulq_n_f32(src, cola), dst, inv_srca);
2506 float outa = cola + dst[3] * (1.0f - cola);
2507 float32x4_t norm = vmulq_f32(blended, vdupq_n_f32(1.0f / outa));
2508 float32x4_t
final = hidden::linear_to_srgb_approx_neon(norm);
2511 out[0] =
static_cast<uint8_t
>(
final[0] * 255.0f);
2512 out[1] =
static_cast<uint8_t
>(
final[1] * 255.0f);
2513 out[2] =
static_cast<uint8_t
>(
final[2] * 255.0f);
2514 out[3] =
static_cast<uint8_t
>(outa * 255.0f);
2516 vst1_lane_u32(
reinterpret_cast<uint32_t *
>(&data[off]), vreinterpret_u32_u8(out), 0);
2520#if defined(__AVX2__)
2521 if (!std::is_constant_evaluated()) {
2522 const size_t off = y * bytes_per_line + x * 4;
2524 const float lr = hidden::a2al_8bit[data[off + 0]];
2525 const float lg = hidden::a2al_8bit[data[off + 1]];
2526 const float lb = hidden::a2al_8bit[data[off + 2]];
2527 const float la = data[off + 3] * (1.0f / 255.0f);
2529 const float as = cola + la * (1.0f - cola);
2530 const float inv_cola = 1.0f - cola;
2532 __m128 dst = _mm_set_ps(0.0f, lb, lg, lr);
2533 __m128 src = _mm_set_ps(0.0f, colb, colg, colr);
2535 __m128 blended = _mm_add_ps(_mm_mul_ps(src, _mm_set1_ps(cola)), _mm_mul_ps(dst, _mm_set1_ps(inv_cola)));
2537 __m128 denom = _mm_set_ss(as);
2538 __m128 inv_as = _mm_rcp_ss(denom);
2539 inv_as = _mm_shuffle_ps(inv_as, inv_as, _MM_SHUFFLE(0, 0, 0, 0));
2541 __m128 scaled = _mm_mul_ps(blended, inv_as);
2542 __m128 srgb = hidden::linear_to_srgb_approx_sse(scaled);
2544 alignas(16)
float out[4];
2545 _mm_store_ps(out, srgb);
2547 data[off + 0] =
static_cast<uint8_t
>(out[0] * 255.0f);
2548 data[off + 1] =
static_cast<uint8_t
>(out[1] * 255.0f);
2549 data[off + 2] =
static_cast<uint8_t
>(out[2] * 255.0f);
2550 data[off + 3] =
static_cast<uint8_t
>(as * 255.0f);
2554 const size_t off = y * bytes_per_line + x * 4;
2556 const float lr = hidden::a2al_8bit[data[off + 0]];
2557 const float lg = hidden::a2al_8bit[data[off + 1]];
2558 const float lb = hidden::a2al_8bit[data[off + 2]];
2559 const float la = data[off + 3] * (1.0f / 255.0f);
2561 const float as = cola + la * (1.0f - cola);
2562 const float rs = hidden::linear_to_srgb(colr * cola + lr * (1.0f - cola)) * (1.0f / as);
2563 const float gs = hidden::linear_to_srgb(colg * cola + lg * (1.0f - cola)) * (1.0f / as);
2564 const float bs = hidden::linear_to_srgb(colb * cola + lb * (1.0f - cola)) * (1.0f / as);
2566 data[off + 0] =
static_cast<uint8_t
>(rs * 255.0f);
2567 data[off + 1] =
static_cast<uint8_t
>(gs * 255.0f);
2568 data[off + 2] =
static_cast<uint8_t
>(bs * 255.0f);
2569 data[off + 3] =
static_cast<uint8_t
>(as * 255.0f);
2572 static constexpr void RGBA_uint32(std::array<uint32_t, W * H> &dst,
2573 const std::span<const uint8_t, image_size> &src) {
2574 if (std::is_constant_evaluated()) {
2575 for (
size_t y = 0; y < H; y++) {
2576 for (
size_t x = 0; x < W; x++) {
2577 dst.data()[y * W + x] =
static_cast<uint32_t
>((src.data()[y * bytes_per_line + x * 4 + 0]) |
2578 (src.data()[y * bytes_per_line + x * 4 + 1] << 8) |
2579 (src.data()[y * bytes_per_line + x * 4 + 2] << 16) |
2580 (src.data()[y * bytes_per_line + x * 4 + 3] << 24));
2584 std::memcpy(dst.data(), src.data(), src.size());
2588 static constexpr void RGBA_uint8(std::array<uint8_t, W * H * 4> &dst,
2589 const std::span<const uint8_t, image_size> &src) {
2590 if (std::is_constant_evaluated()) {
2591 for (
size_t c = 0; c < src.size(); c++) {
2592 dst.data()[c] = src.data()[c];
2595 std::memcpy(dst.data(), src.data(), src.size());
2599 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
const uint8_t *ptr,
2601 rect<int32_t> intersect_rect{.x = 0, .y = 0, .w = W, .h = H};
2603 auto const r_x =
static_cast<size_t>(intersect_rect.x);
2604 auto const r_y =
static_cast<size_t>(intersect_rect.y);
2605 auto const r_w =
static_cast<size_t>(intersect_rect.w);
2606 auto const r_h =
static_cast<size_t>(intersect_rect.h);
2607 const uint8_t *src = ptr;
2608 uint8_t *dst = data.data() + r_y * bytes_per_line + r_x * 4;
2609 for (
size_t y = 0; y < r_h; y++) {
2610 std::memcpy(dst, src, r_w * 4);
2611 dst += bytes_per_line;
2616 static constexpr void blit_RGBA_diffused(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
2617 const uint8_t *ptr, int32_t stride) {
2618 blit_RGBA(data, r, ptr, stride);
2621 static constexpr void blit_RGBA_diffused_linear(std::span<uint8_t, image_size> data,
const rect<int32_t> &r,
2622 const uint8_t *ptr, int32_t stride) {
2623 blit_RGBA(data, r, ptr, stride);
2626 template <
typename F>
2627 static constexpr void png(
const std::span<const uint8_t, image_size> data, F &&char_out) {
2628 png_image<W, H, uint8_t, bits_per_pixel>(data.data(), quant.palette(), std::forward<F>(char_out),
2629 [](
const uint8_t *data_raw,
size_t y,
size_t &bpl) {
2630 bpl = bytes_per_line;
2631 return data_raw + y * bytes_per_line;
2635 template <
size_t S,
typename F>
2636 static constexpr void sixel(
const std::span<const uint8_t, image_size> & , F && ,
2639 static_assert(
false,
"Sixel not available for format_32bit.");
2650enum color : uint8_t {
2710 GREEN_MEDIUM_1 = 51,
2711 GREEN_MEDIUM_2 = 52,
2712 GREEN_BRIGHT_1 = 53,
2713 GREEN_BRIGHT_2 = 54,
2746 YELLOW_MEDIUM_1 = 83,
2747 YELLOW_MEDIUM_2 = 84,
2748 YELLOW_BRIGHT_1 = 85,
2749 YELLOW_BRIGHT_2 = 86,
2751 YELLOW_LIGHT_1 = 88,
2752 YELLOW_LIGHT_2 = 89,
2753 YELLOW_LIGHT_3 = 90,
2754 YELLOW_LIGHT_4 = 91,
2765 CYAN_MEDIUM_2 = 100,
2766 CYAN_BRIGHT_1 = 101,
2767 CYAN_BRIGHT_2 = 102,
2779 MAGENTA_DARK_1 = 112,
2780 MAGENTA_DARK_2 = 113,
2781 MAGENTA_DARK_3 = 114,
2782 MAGENTA_MEDIUM_1 = 115,
2783 MAGENTA_MEDIUM_2 = 116,
2784 MAGENTA_BRIGHT_1 = 117,
2785 MAGENTA_BRIGHT_2 = 118,
2787 MAGENTA_LIGHT_1 = 120,
2788 MAGENTA_LIGHT_2 = 121,
2789 MAGENTA_LIGHT_3 = 122,
2790 MAGENTA_LIGHT_4 = 123,
2791 MAGENTA_PALE_1 = 124,
2792 MAGENTA_PALE_2 = 125,
2793 MAGENTA_PALE_3 = 126,
2794 MAGENTA_WHITE = 127,
2805 GRAY_RAMP_START = 16,
2806 GRAY_RAMP_COUNT = 16,
2807 GRAY_RAMP_STOP = GRAY_RAMP_START + 15,
2809 RED_LUMA_RAMP_START = 32,
2810 RED_LUMA_RAMP_COUNT = 16,
2811 RED_LUMA_RAMP_STOP = RED_LUMA_RAMP_START + 15,
2813 GREEN_LUMA_RAMP_START = 48,
2814 GREEN_LUMA_RAMP_COUNT = 16,
2815 GREEN_LUMA_RAMP_STOP = GREEN_LUMA_RAMP_START + 15,
2817 BLUE_LUMA_RAMP_START = 64,
2818 BLUE_LUMA_RAMP_COUNT = 16,
2819 BLUE_LUMA_RAMP_STOP = BLUE_LUMA_RAMP_START + 15,
2821 YELLOW_LUMA_RAMP_START = 80,
2822 YELLOW_LUMA_RAMP_COUNT = 16,
2823 YELLOW_LUMA_RAMP_STOP = YELLOW_LUMA_RAMP_START + 15,
2825 CYAN_LUMA_RAMP_START = 96,
2826 CYAN_LUMA_RAMP_COUNT = 16,
2827 CYAN_LUMA_RAMP_STOP = CYAN_LUMA_RAMP_START + 15,
2829 MAGENTA_LUMA_RAMP_START = 112,
2830 MAGENTA_LUMA_RAMP_COUNT = 16,
2831 MAGENTA_LUMA_RAMP_STOP = MAGENTA_LUMA_RAMP_START + 15,
2833 OKLCH_RAMP_START = 128,
2834 OKLCH_RAMP_COUNT = 128,
2835 OKLCH_RAMP_STOP = 255
2858 RGB666_8BIT_SERIAL_1,
2861 RGB666_8BIT_SERIAL_2
2934template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
2947template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
2962 constexpr rect(image_type &
image, int32_t x_, int32_t y_, int32_t w_, int32_t h_)
2963 : img(
image), x(x_), y(y_), w(w_), h(h_) {
2972 img.fill_rect(x, y, w, h, col);
2981 template <
typename shader_func>
2983 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
2985 img.fill_rect(x, y, w, h, shader);
2996 img.stroke_rect(x, y, w, h, col, stroke_width);
3006template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
3020 constexpr circle(image_type &
image, int32_t cx_, int32_t cy_, int32_t r_) : img(
image), cx(cx_), cy(cy_), r(r_) {
3029 img.fill_circle(cx, cy, r, col);
3040 img.stroke_circle(cx, cy, r, col, stroke_width);
3050template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
3064 constexpr circle_aa(image_type &
image, int32_t cx_, int32_t cy_, int32_t r_) : img(
image), cx(cx_), cy(cy_), r(r_) {
3073 img.fill_circle_aa(cx, cy, r, col);
3082 template <
typename shader_func>
3084 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
3086 img.fill_circle_aa(cx, cy, r, shader);
3097 img.stroke_circle_aa(cx, cy, r, col, stroke_width);
3107template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
3111 int32_t x, y, w, h, radius;
3123 constexpr round_rect(image_type &
image, int32_t x_, int32_t y_, int32_t w_, int32_t h_, int32_t radius_)
3124 : img(
image), x(x_), y(y_), w(w_), h(h_), radius(radius_) {
3133 img.fill_round_rect(x, y, w, h, radius, col);
3144 img.stroke_round_rect(x, y, w, h, radius, col, stroke_width);
3154template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
3158 int32_t x, y, w, h, radius;
3170 constexpr round_rect_aa(image_type &
image, int32_t x_, int32_t y_, int32_t w_, int32_t h_, int32_t radius_)
3171 : img(
image), x(x_), y(y_), w(w_), h(h_), radius(radius_) {
3180 img.fill_round_rect_aa(x, y, w, h, radius, col);
3189 template <
typename shader_func>
3191 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
3193 img.fill_round_rect_aa(x, y, w, h, radius, shader);
3204 img.stroke_round_rect_aa(x, y, w, h, radius, col, stroke_width);
3214template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
3218 int32_t x0, y0, x1, y1;
3229 constexpr line(image_type &
image, int32_t x0_, int32_t y0_, int32_t x1_, int32_t y1_)
3230 : img(
image), x0(x0_), y0(y0_), x1(x1_), y1(y1_) {
3240 img.draw_line(x0, y0, x1, y1, col, stroke_width);
3250template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
3254 int32_t x0, y0, x1, y1;
3265 constexpr line_aa(image_type &
image, int32_t x0_, int32_t y0_, int32_t x1_, int32_t y1_)
3266 : img(
image), x0(x0_), y0(y0_), x1(x1_), y1(y1_) {
3276 img.draw_line_aa(x0, y0, x1, y1, col, stroke_width);
3286template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
bool USE_SPAN>
3308 img.
plot(x, y, col);
3318template <
typename FONT,
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
3319 bool USE_SPAN,
bool KERNING =
false, text_rotation ROTATION = DEGREE_0>
3335 : img(
image), x(x_), y(y_), str(str_) {
3345 constexpr int32_t
draw(uint8_t col,
size_t character_count = std::numeric_limits<size_t>::max(),
3346 size_t *character_actual =
nullptr) {
3347 return img.template draw_string_mono<FONT, KERNING, ROTATION>(x, y, str, col, character_count,
3357 img.template draw_string_mono<FONT, KERNING, ROTATION>(x, y, str, col);
3367template <
typename FONT,
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
3368 bool USE_SPAN,
bool KERNING =
false, text_rotation ROTATION = DEGREE_0>
3383 constexpr text_aa(image_type &
image, int32_t x_, int32_t y_,
const char *str_)
3384 : img(
image), x(x_), y(y_), str(str_) {
3394 constexpr int32_t
draw(uint8_t col,
size_t character_count = std::numeric_limits<size_t>::max(),
3395 size_t *character_actual =
nullptr) {
3396 return img.template draw_string_aa<FONT, KERNING, ROTATION>(x, y, str, col, character_count, character_actual);
3405 img.template draw_string_aa<FONT, KERNING, ROTATION>(x, y, str, col);
3415template <
typename FONT,
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
3416 bool USE_SPAN,
bool KERNING =
false, text_rotation ROTATION = DEGREE_0>
3432 : img(
image), x(x_), y(y_), str(str_) {
3441 img.template draw_string_centered_mono<FONT, KERNING, ROTATION>(x, y, str, col);
3451template <
typename FONT,
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE,
3452 bool USE_SPAN,
bool KERNING =
false, text_rotation ROTATION = DEGREE_0>
3468 : img(
image), x(x_), y(y_), str(str_) {
3477 img.template draw_string_centered_aa<FONT, KERNING, ROTATION>(x, y, str, col);
3500template <
template <
size_t,
size_t,
bool,
bool>
class T,
size_t W,
size_t H,
bool GRAYSCALE =
false,
3501 bool USE_SPAN =
false>
3503 static_assert(
sizeof(W) >=
sizeof(uint32_t));
3504 static_assert(
sizeof(H) >=
sizeof(uint32_t));
3506 static_assert(W > 0 && H > 0);
3507 static_assert(W <= 65535 && H <= 65535);
3509#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__)
3510 static constexpr int32_t min_coord = -int32_t{1 << 28};
3511 static constexpr int32_t max_coord = +int32_t{1 << 28};
3512 using calc_square_type = int64_t;
3514 static constexpr int32_t min_coord = -int32_t{1 << 13} + int32_t{1};
3515 static constexpr int32_t max_coord = +int32_t{1 << 13} - int32_t{1};
3516 using calc_square_type = int32_t;
3531 image(
const std::span<uint8_t, T<W, H, GRAYSCALE, USE_SPAN>::image_size> &other)
3549 return T<W, H, GRAYSCALE, USE_SPAN>::bits_per_pixel;
3556 [[nodiscard]]
static constexpr size_t size() {
3557 return T<W, H, GRAYSCALE, USE_SPAN>::image_size;
3565 return T<W, H, GRAYSCALE, USE_SPAN>::bytes_per_line;
3572 [[nodiscard]]
static constexpr int32_t
width() {
3573 return static_cast<int32_t
>(W);
3580 [[nodiscard]]
static constexpr int32_t
height() {
3581 return static_cast<int32_t
>(H);
3595 [[nodiscard]]
constexpr std::array<uint8_t, T<W, H, GRAYSCALE, USE_SPAN>::image_size> &
data_ref() {
3619 template <
size_t BYTE_SIZE>
3620 constexpr void copy(
const uint8_t *src) {
3621 static_assert(
size() == BYTE_SIZE,
"Copied length much match the image size");
3622 for (
size_t c = 0; c < BYTE_SIZE; c++) {
3623 data.data()[c] = src[c];
3635 return format.quant.nearest(r, g, b);
3644 constexpr void plot(int32_t x, int32_t y, uint8_t col) {
3645 auto wS =
static_cast<int32_t
>(W);
3646 auto hS =
static_cast<int32_t
>(H);
3647 if (x >= wS || y >= hS || x < 0 || y < 0) {
3650 T<W, H, GRAYSCALE, USE_SPAN>::plot(data,
static_cast<uint32_t
>(x),
static_cast<uint32_t
>(y), col);
3676 constexpr void draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t col, int32_t stroke_width = 1) {
3677 auto minmax_check = std::minmax({x0, y0, x1, y1, stroke_width});
3678 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
3682 if (!clip_line(x0, y0, x1, y1, -stroke_width, -stroke_width,
static_cast<int32_t
>(W) + stroke_width,
3683 static_cast<int32_t
>(H) + stroke_width)) {
3687 if (stroke_width <= 0) {
3691 if (stroke_width == 1) {
3692 bool steep = abs(y1 - y0) > abs(x1 - x0);
3702 const int32_t dx = x1 - x0;
3703 const int32_t dy = abs(y1 - y0);
3704 int32_t err = dx / 2;
3712 for (; x0 <= x1; x0++) {
3727 auto line_thick = [&]<
typename I>() {
3728 const int32_t half_width = stroke_width / 2;
3730 const int32_t margin = half_width + 1;
3731 const int32_t min_x = std::max(int32_t{0}, std::min({x0, x1}) - margin);
3732 const int32_t max_x = std::min(
static_cast<int32_t
>(W) - 1, std::max({x0, x1}) + margin);
3733 const int32_t min_y = std::max(int32_t{0}, std::min({y0, y1}) - margin);
3734 const int32_t max_y = std::min(
static_cast<int32_t
>(H) - 1, std::max({y0, y1}) + margin);
3736 if (min_x > max_x || min_y > max_y) {
3740 const I line_dx = x1 - x0;
3741 const I line_dy = y1 - y0;
3742 const I line_length_sq = line_dx * line_dx + line_dy * line_dy;
3744 if (line_length_sq <= 1) {
3745 if (x0 >= 0 && x0 <
static_cast<int32_t
>(W) && y0 >= 0 && y0 <
static_cast<int32_t
>(H)) {
3751 for (int32_t py = min_y; py <= max_y; py++) {
3752 for (int32_t px = min_x; px <= max_x; px++) {
3753 const I px_dx = px - x0;
3754 const I px_dy = py - y0;
3756 const I dot_product = px_dx * line_dx + px_dy * line_dy;
3757 const I t_scaled = std::max(I{0}, std::min(line_length_sq, dot_product));
3759 const I closest_x = x0 + (t_scaled * line_dx) / line_length_sq;
3760 const I closest_y = y0 + (t_scaled * line_dy) / line_length_sq;
3762 const I dist_x = px - closest_x;
3763 const I dist_y = py - closest_y;
3764 const I distance_sq = dist_x * dist_x + dist_y * dist_y;
3766 if (distance_sq <=
static_cast<I
>(half_width) *
static_cast<I
>(half_width)) {
3772 line_thick.template operator()<calc_square_type>();
3803 constexpr void draw_line_aa(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t col,
3804 float stroke_width = 1.0f) {
3805 auto minmax_check = std::minmax({x0, y0, x1, y1});
3806 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
3810 const float Rl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 0);
3811 const float Gl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 1);
3812 const float Bl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 2);
3814 if (stroke_width <= 0.0f) {
3818 if (stroke_width <= 1.0f) {
3819 if (!clip_line(x0, y0, x1, y1, 0, 0, W, H)) {
3823 bool steep = abs(y1 - y0) > abs(x1 - x0);
3833 const auto dx =
static_cast<float>(x1 - x0);
3834 const auto dy =
static_cast<float>(y1 - y0);
3835 const float gradient = dx == 0.0f ? 1.0f : dy / dx;
3837 auto color_compose = [&](int32_t x, int32_t y,
float a) {
3838 if (a < hidden::epsilon_low) {
3841 if (a >= hidden::epsilon_high) {
3844 compose(x, y, a, Rl, Gl, Bl);
3848 auto ipart = [](
float x) {
3849 return std::floor(x);
3851 auto fpart = [](
float x) {
3852 return x - std::floor(x);
3854 auto rfpart = [&](
float x) {
3855 return 1.0f - fpart(x);
3859 auto xend =
static_cast<float>(x0);
3860 float yend =
static_cast<float>(y0) + gradient * (xend -
static_cast<float>(x0));
3861 float xgap = rfpart(
static_cast<float>(x0) + 0.5f);
3862 const auto xpxl1 =
static_cast<int32_t
>(xend);
3863 const auto ypxl1 =
static_cast<int32_t
>(ipart(yend));
3865 color_compose(ypxl1, xpxl1, rfpart(yend) * xgap);
3866 color_compose(ypxl1 + 1, xpxl1, fpart(yend) * xgap);
3868 color_compose(xpxl1, ypxl1, rfpart(yend) * xgap);
3869 color_compose(xpxl1, ypxl1 + 1, fpart(yend) * xgap);
3871 float intery = yend + gradient;
3873 xend =
static_cast<float>(x1);
3874 yend =
static_cast<float>(y1) + gradient * (xend -
static_cast<float>(x1));
3875 xgap = fpart(
static_cast<float>(x1) + 0.5f);
3876 const auto xpxl2 =
static_cast<int32_t
>(xend);
3877 const auto ypxl2 =
static_cast<int32_t
>(ipart(yend));
3879 color_compose(ypxl2, xpxl2, rfpart(yend) * xgap);
3880 color_compose(ypxl2 + 1, xpxl2, fpart(yend) * xgap);
3882 color_compose(xpxl2, ypxl2, rfpart(yend) * xgap);
3883 color_compose(xpxl2, ypxl2 + 1, fpart(yend) * xgap);
3886 for (int32_t x = xpxl1 + 1; x < xpxl2; ++x) {
3887 auto y =
static_cast<int32_t
>(ipart(intery));
3889 color_compose(y, x, rfpart(intery));
3890 color_compose(y + 1, x, fpart(intery));
3892 color_compose(x, y, rfpart(intery));
3893 color_compose(x, y + 1, fpart(intery));
3900 const float half_width = stroke_width * 0.5f;
3902 const auto margin =
static_cast<int32_t
>(std::ceil(half_width + 1.0f));
3903 const int32_t min_x = std::max(int32_t{0}, std::min({x0, x1}) - margin);
3904 const int32_t max_x = std::min(
static_cast<int32_t
>(W) - 1, std::max({x0, x1}) + margin);
3905 const int32_t min_y = std::max(int32_t{0}, std::min({y0, y1}) - margin);
3906 const int32_t max_y = std::min(
static_cast<int32_t
>(H) - 1, std::max({y0, y1}) + margin);
3908 if (min_x > max_x || min_y > max_y) {
3912 const auto dx =
static_cast<float>(x1 - x0);
3913 const auto dy =
static_cast<float>(y1 - y0);
3914 const float line_length_sq = dx * dx + dy * dy;
3916 if (line_length_sq <= 1.0f) {
3917 const float radius = half_width;
3918 const auto r_ceil =
static_cast<int32_t
>(std::ceil(radius));
3919 for (int32_t py = y0 - r_ceil; py <= y0 + r_ceil; py++) {
3920 for (int32_t px = x0 - r_ceil; px <= x0 + r_ceil; px++) {
3921 if (px >= 0 && px <
static_cast<int32_t
>(W) && py >= 0 && py <
static_cast<int32_t
>(H)) {
3922 auto dist =
static_cast<float>((px - x0) * (px - x0) + (py - y0) * (py - y0));
3923 if (std::is_constant_evaluated()) {
3924 dist = hidden::fast_sqrtf(dist);
3926 dist = std::sqrt(dist);
3928 const float coverage = std::max(0.0f, std::min(1.0f, radius + 0.5f - dist));
3929 if (coverage > hidden::epsilon_low) {
3930 if (coverage >= hidden::epsilon_high) {
3933 compose(px, py, coverage, Rl, Gl, Bl);
3942 for (int32_t py = min_y; py <= max_y; py++) {
3943 for (int32_t px = min_x; px <= max_x; px++) {
3944 const auto px_dx =
static_cast<float>(px - x0);
3945 const auto px_dy =
static_cast<float>(py - y0);
3946 const float t = std::max(0.0f, std::min(1.0f, (px_dx * dx + px_dy * dy) / line_length_sq));
3947 const float closest_x =
static_cast<float>(x0) + t * dx;
3948 const float closest_y =
static_cast<float>(y0) + t * dy;
3949 const float dist_x =
static_cast<float>(px) - closest_x;
3950 const float dist_y =
static_cast<float>(py) - closest_y;
3951 float dist = dist_x * dist_x + dist_y * dist_y;
3952 if (std::is_constant_evaluated()) {
3953 dist = hidden::fast_sqrtf(dist);
3955 dist = std::sqrt(dist);
3957 const float coverage = std::max(0.0f, std::min(1.0f, half_width + 0.5f - dist));
3958 if (coverage > hidden::epsilon_low) {
3959 if (coverage >= hidden::epsilon_high) {
3962 compose(px, py, coverage, Rl, Gl, Bl);
4000 template <
typename FONT,
bool KERNING = false>
4002 size_t character_count = std::numeric_limits<size_t>::max(),
4003 size_t *character_actual =
nullptr) {
4006 while (*str != 0 && count++ < character_count) {
4008 str = get_next_utf32(str, &utf32);
4010 if (lookup_glyph<FONT>(utf32, &index)) {
4011 const char_info<typename FONT::char_info_type> &ch_info = FONT::char_table.at(index);
4013 x +=
static_cast<int32_t
>(ch_info.width);
4015 x +=
static_cast<int32_t
>(ch_info.xadvance);
4016 if constexpr (KERNING) {
4017 x += get_kerning<FONT>(utf32, str);
4022 if (character_actual) {
4023 *character_actual = count;
4052 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4054 size_t character_count = std::numeric_limits<size_t>::max(),
4055 size_t *character_actual =
nullptr) {
4056 static_assert(FONT::mono ==
true,
"Can't use an antialiased font to draw mono/pixelized text.");
4057 auto minmax_check = std::minmax({x, y});
4058 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4061 if (str ==
nullptr) {
4065 while (*str != 0 && count++ < character_count) {
4067 str = get_next_utf32(str, &utf32);
4069 if (lookup_glyph<FONT>(utf32, &index)) {
4070 const char_info<typename FONT::char_info_type> &ch_info = FONT::char_table.at(index);
4071 draw_char_mono<FONT, ROTATION>(x, y, ch_info, col);
4072 if constexpr (ROTATION == DEGREE_0) {
4073 x +=
static_cast<int32_t
>(ch_info.xadvance);
4074 if constexpr (KERNING) {
4075 x += get_kerning<FONT>(utf32, str);
4077 }
else if constexpr (ROTATION == DEGREE_90) {
4078 y +=
static_cast<int32_t
>(ch_info.xadvance);
4079 if constexpr (KERNING) {
4080 y += get_kerning<FONT>(utf32, str);
4082 }
else if constexpr (ROTATION == DEGREE_180) {
4083 x -=
static_cast<int32_t
>(ch_info.xadvance);
4084 if constexpr (KERNING) {
4085 x -= get_kerning<FONT>(utf32, str);
4087 }
else if constexpr (ROTATION == DEGREE_270) {
4088 y -=
static_cast<int32_t
>(ch_info.xadvance);
4089 if constexpr (KERNING) {
4090 y -= get_kerning<FONT>(utf32, str);
4095 if (character_actual) {
4096 *character_actual = count;
4098 if constexpr (ROTATION == DEGREE_90 || ROTATION == DEGREE_270) {
4126 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4128 size_t character_count = std::numeric_limits<size_t>::max(),
4129 size_t *character_actual =
nullptr) {
4145 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4147 if constexpr (ROTATION == DEGREE_0) {
4149 }
else if constexpr (ROTATION == DEGREE_180) {
4151 }
else if constexpr (ROTATION == DEGREE_90) {
4153 }
else if constexpr (ROTATION == DEGREE_270) {
4182 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4184 size_t character_count = std::numeric_limits<size_t>::max(),
4185 size_t *character_actual =
nullptr) {
4186 static_assert(FONT::mono ==
false,
"Can't use a mono font to draw antialiased text.");
4187 auto minmax_check = std::minmax({x, y});
4188 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4191 if (str ==
nullptr) {
4195 while (*str != 0 && count++ < character_count) {
4197 str = get_next_utf32(str, &utf32);
4199 if (lookup_glyph<FONT>(utf32, &index)) {
4200 const char_info<typename FONT::char_info_type> &ch_info = FONT::char_table.at(index);
4201 draw_char_aa<FONT, ROTATION>(x, y, ch_info, col);
4202 if constexpr (ROTATION == DEGREE_0) {
4203 x +=
static_cast<int32_t
>(ch_info.xadvance);
4204 if constexpr (KERNING) {
4205 x += get_kerning<FONT>(utf32, str);
4207 }
else if constexpr (ROTATION == DEGREE_90) {
4208 y +=
static_cast<int32_t
>(ch_info.xadvance);
4209 if constexpr (KERNING) {
4210 y += get_kerning<FONT>(utf32, str);
4212 }
else if constexpr (ROTATION == DEGREE_180) {
4213 x -=
static_cast<int32_t
>(ch_info.xadvance);
4214 if constexpr (KERNING) {
4215 x -= get_kerning<FONT>(utf32, str);
4217 }
else if constexpr (ROTATION == DEGREE_270) {
4218 y -=
static_cast<int32_t
>(ch_info.xadvance);
4219 if constexpr (KERNING) {
4220 y -= get_kerning<FONT>(utf32, str);
4225 if (character_actual) {
4226 *character_actual = count;
4228 if constexpr (ROTATION == DEGREE_90 || ROTATION == DEGREE_270) {
4256 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4258 size_t character_count = std::numeric_limits<size_t>::max(),
4259 size_t *character_actual =
nullptr) {
4276 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4278 if constexpr (ROTATION == DEGREE_0) {
4280 }
else if constexpr (ROTATION == DEGREE_180) {
4282 }
else if constexpr (ROTATION == DEGREE_90) {
4284 }
else if constexpr (ROTATION == DEGREE_270) {
4302 constexpr void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col) {
4303 auto minmax_check = std::minmax({x, y, w, h});
4304 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4307 if (w <= 0 || h <= 0) {
4310 if (check_not_in_bounds(x, y, w, h)) {
4317 if (y + h >=
static_cast<int32_t
>(H)) {
4318 h =
static_cast<int32_t
>(H) - y;
4321 for (; y < h; y++) {
4322 extent(x, w, y, col);
4356 template <
typename shader_func>
4357 constexpr auto fill_rect(int32_t x, int32_t y, int32_t w, int32_t h,
const shader_func &shader) ->
void
4358 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
4360 auto minmax_check = std::minmax({x, y, w, h});
4361 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4364 if (w <= 0 || h <= 0) {
4367 if (check_not_in_bounds(x, y, w, h)) {
4371 int32_t x0 = std::max(x, int32_t{0});
4372 int32_t y0 = std::max(y, int32_t{0});
4373 int32_t x1 = std::min(x + w,
static_cast<int32_t
>(W));
4374 int32_t y1 = std::min(y + h,
static_cast<int32_t
>(H));
4376 for (int32_t py = y0; py < y1; py++) {
4377 for (int32_t px = x0; px < x1; px++) {
4378 float u =
static_cast<float>(px - x) /
static_cast<float>(w);
4379 float v =
static_cast<float>(py - y) /
static_cast<float>(h);
4380 auto rgba = shader(u, v, px, py);
4381 for (
auto &p : rgba) {
4382 p = std::clamp(p, 0.0f, 1.0f);
4384 compose(px, py, rgba[3], rgba[0], rgba[1], rgba[2]);
4403 constexpr void stroke_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col, int32_t stroke_width = 1) {
4404 auto minmax_check = std::minmax({x, y, w, h, stroke_width});
4405 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4408 if (w <= 0 || h <= 0) {
4411 stroke_width = std::min(abs(stroke_width), std::max(w, h) / 2);
4413 fill_rect(x + stroke_width, y, w - stroke_width * 2, stroke_width, col);
4414 fill_rect(x + w - stroke_width, y, stroke_width, h, col);
4415 fill_rect(x + stroke_width, y + h - stroke_width, w - stroke_width * 2, stroke_width, col);
4443 constexpr void fill_circle(int32_t cx, int32_t cy, int32_t radius, uint8_t col) {
4444 auto minmax_check = std::minmax({cx, cy, radius});
4445 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4448 radius = abs(radius);
4453 circle_int<false, false>(cx, cy, radius, 0, 0, col, 0);
4482 constexpr void stroke_circle(int32_t cx, int32_t cy, int32_t radius, uint8_t col, int32_t stroke_width = 1) {
4483 auto minmax_check = std::minmax({cx, cy, radius, stroke_width});
4484 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4487 radius = abs(radius);
4488 stroke_width = abs(stroke_width);
4493 if (stroke_width >= radius) {
4494 circle_int<false, false>(cx, cy, radius, 0, 0, col, 0);
4497 circle_int<false, true>(cx, cy, radius, 0, 0, col, stroke_width);
4527 constexpr void stroke_circle_aa(int32_t cx, int32_t cy, int32_t radius, uint8_t col, int32_t stroke_width = 1) {
4528 auto minmax_check = std::minmax({cx, cy, radius, stroke_width});
4529 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4532 radius = abs(radius);
4533 stroke_width = abs(stroke_width);
4538 if (stroke_width >= radius) {
4539 circle_int<true, false>(cx, cy, radius, 0, 0, col, 0);
4542 circle_int<true, true>(cx, cy, radius, 0, 0, col, stroke_width);
4573 auto minmax_check = std::minmax({cx, cy, radius});
4574 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4577 radius = abs(radius);
4578 circle_int<true, false>(cx, cy, radius, 0, 0, col, 0);
4613 template <
typename shader_func>
4614 constexpr auto fill_circle_aa(int32_t cx, int32_t cy, int32_t radius,
const shader_func &shader) ->
void
4615 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
4617 auto minmax_check = std::minmax({cx, cy, radius});
4618 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4621 radius = abs(radius);
4622 circle_int_shader(cx, cy, radius, 0, 0, shader);
4640 constexpr void stroke_round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col,
4641 int32_t stroke_width = 1) {
4642 auto minmax_check = std::minmax({x, y, w, h, radius, stroke_width});
4643 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4646 if (w <= 0 || h <= 0) {
4649 radius = abs(radius);
4650 stroke_width = abs(stroke_width);
4651 const int32_t cr = std::min({w / 2, h / 2, radius});
4652 const int32_t dx = w - cr * 2;
4653 const int32_t dy = h - cr * 2;
4657 if (radius > stroke_width) {
4658 circle_int<false, true>(x + cr, y + cr, cr, dx, dy, col, stroke_width);
4660 circle_int<false, false>(x + cr, y + cr, cr, dx, dy, col, 0);
4662 fill_rect(x, y + cr, stroke_width, dy, col);
4663 fill_rect(x + w - stroke_width, y + cr, stroke_width, dy, col);
4664 fill_rect(x + cr, y, w - cr * 2, stroke_width, col);
4665 fill_rect(x + cr, y + h - stroke_width, w - cr * 2, stroke_width, col);
4696 constexpr void fill_round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col) {
4697 auto minmax_check = std::minmax({x, y, w, h, radius});
4698 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4701 if (w <= 0 || h <= 0) {
4704 const int32_t cr = std::min({w / 2, h / 2, radius});
4705 const int32_t dx = w - cr * 2;
4706 const int32_t dy = h - cr * 2;
4707 circle_int<false, false>(x + cr, y + cr, cr, dx, dy, col, 0);
4709 fill_rect(x + w - cr, y + cr, cr, dy, col);
4741 constexpr void fill_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col) {
4742 auto minmax_check = std::minmax({x, y, w, h, radius});
4743 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4746 if (w <= 0 || h <= 0) {
4749 radius = abs(radius);
4750 int32_t cr = std::min({w / 2, h / 2, radius});
4751 int32_t dx = w - cr * 2;
4752 int32_t dy = h - cr * 2;
4753 circle_int<true, false>(x + cr, y + cr, cr, dx, dy, col, 0);
4755 fill_rect(x + w - cr, y + cr, cr, dy, col);
4791 template <
typename shader_func>
4793 const shader_func &shader) ->
void
4794 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
4796 auto minmax_check = std::minmax({x, y, w, h, radius});
4797 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4800 if (w <= 0 || h <= 0) {
4803 radius = abs(radius);
4804 int32_t cr = std::min({w / 2, h / 2, radius});
4805 int32_t dx = w - cr * 2;
4806 int32_t dy = h - cr * 2;
4808 circle_int_shader(x + cr, y + cr, cr, dx, dy, shader, x, y, w, h);
4810 fill_rect_int(x, y + cr, cr, dy, shader, x, y, w, h);
4811 fill_rect_int(x + w - cr, y + cr, cr, dy, shader, x, y, w, h);
4812 fill_rect_int(x + cr, y, dx, h, shader, x, y, w, h);
4831 int32_t stroke_width = 1) {
4832 auto minmax_check = std::minmax({x, y, w, h, radius, stroke_width});
4833 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4836 if (w <= 0 || h <= 0) {
4839 radius = abs(radius);
4840 stroke_width = abs(stroke_width);
4841 const int32_t cr = std::min({w / 2, h / 2, radius});
4842 const int32_t dx = w - cr * 2;
4843 const int32_t dy = h - cr * 2;
4847 if (radius > stroke_width) {
4848 circle_int<true, true>(x + cr, y + cr, cr, dx, dy, col, stroke_width);
4850 circle_int<true, false>(x + cr, y + cr, cr, dx, dy, col, 0);
4852 fill_rect(x, y + cr, stroke_width, dy, col);
4853 fill_rect(x + w - stroke_width, y + cr, stroke_width, dy, col);
4854 fill_rect(x + cr, y, w - cr * 2, stroke_width, col);
4855 fill_rect(x + cr, y + h - stroke_width, w - cr * 2, stroke_width, col);
4877 T<W, H, GRAYSCALE, USE_SPAN>::RGBA_uint32(dst, data);
4884 constexpr void RGBA_uint8(std::array<uint8_t, W * H * 4> &dst)
const {
4885 T<W, H, GRAYSCALE, USE_SPAN>::RGBA_uint8(dst, data);
4892 uint8_t *ptr = data.data();
4893 for (
size_t y = 0; y < H; y++) {
4895 const uint8_t a = T<W, H, GRAYSCALE, USE_SPAN>::reverse(ptr[x]);
4896 const uint8_t b = T<W, H, GRAYSCALE, USE_SPAN>::reverse(ptr[
bytes_per_line() - x - 1]);
4908 uint8_t *ptr = data.data();
4910 for (
size_t y = 0; y < H / 2; y++) {
4913 const uint8_t a = ptr_a[x];
4914 const uint8_t b = ptr_b[x];
4925 uint8_t *ptr = data.data();
4927 for (
size_t y = 0; y < H / 2; y++) {
4930 uint8_t a = T<W, H, GRAYSCALE, USE_SPAN>::reverse(ptr_a[x]);
4931 uint8_t b = T<W, H, GRAYSCALE, USE_SPAN>::reverse(ptr_b[
bytes_per_line() - x - 1]);
4943 template <
bool FLIP_H = false,
bool FLIP_V = false>
4955 template <
bool FLIP_H = false,
bool FLIP_V = false>
4971 constexpr void blit_RGBA(int32_t x, int32_t y, int32_t w, int32_t h,
const uint8_t *ptr, int32_t iw, int32_t ih,
4974 blitrect &= {.x = 0, .y = 0, .w = W, .h = H};
4975 blitrect &= {.x = x, .y = y, .w = iw, .h = ih};
4976 T<W, H, GRAYSCALE, USE_SPAN>::blit_RGBA(data, blitrect, ptr, stride);
4990 blitrect &= {.x = 0, .y = 0, .w = W, .h = H};
4991 blitrect &= {.x = dstrect.
x, .y = dstrect.
y, .w = iw, .h = ih};
4992 T<W, H, GRAYSCALE, USE_SPAN>::blit_RGBA(data, blitrect, ptr, stride);
5007 constexpr void blit_RGBA_diffused(int32_t x, int32_t y, int32_t w, int32_t h,
const uint8_t *ptr, int32_t iw,
5008 int32_t ih, int32_t stride) {
5010 blitrect &= {.x = 0, .y = 0, .w = W, .h = H};
5011 blitrect &= {.x = x, .y = y, .w = iw, .h = ih};
5012 T<W, H, GRAYSCALE, USE_SPAN>::blit_RGBA_diffused(data, blitrect, ptr, stride);
5028 blitrect &= {.x = 0, .y = 0, .w = W, .h = H};
5029 blitrect &= {.x = dstrect.
x, .y = dstrect.
y, .w = iw, .h = ih};
5030 T<W, H, GRAYSCALE, USE_SPAN>::blit_RGBA_diffused(data, blitrect, ptr, stride);
5046 int32_t ih, int32_t stride) {
5048 blitrect &= {.x = 0, .y = 0, .w = W, .h = H};
5049 blitrect &= {.x = x, .y = y, .w = iw, .h = ih};
5050 T<W, H, GRAYSCALE, USE_SPAN>::blit_RGBA_diffused_linear(data, blitrect, ptr, stride);
5066 blitrect &= {.x = 0, .y = 0, .w = W, .h = H};
5067 blitrect &= {.x = dstrect.
x, .y = dstrect.
y, .w = iw, .h = ih};
5068 T<W, H, GRAYSCALE, USE_SPAN>::blit_RGBA_diffused_linear(data, blitrect, ptr, stride);
5083 template <
typename F>
5084 constexpr void png(F &&char_out)
const {
5085 T<W, H, GRAYSCALE, USE_SPAN>::png(std::span{data}, std::forward<F>(char_out));
5101 template <
size_t S = 1,
typename F>
5102 constexpr void sixel(F &&char_out)
const {
5103 T<W, H, GRAYSCALE, USE_SPAN>::template
sixel<S>(data, std::forward<F>(char_out), {0, 0, W, H});
5120 template <
size_t S = 1,
typename F>
5122 T<W, H, GRAYSCALE, USE_SPAN>::template
sixel<S>(data, std::forward<F>(char_out),
rect);
5130 template <
size_t S = 1>
5133 T<W, H, GRAYSCALE, USE_SPAN>::template
sixel<S>(data,
5134 [&out](
char ch)
mutable {
5138 std::cout << out <<
'\n';
5145 std::cout <<
"\033[2J\033[3J\033[H";
5152 std::cout <<
"\033[3J";
5159 std::cout <<
"\033[H";
5167 output.append(
"\033]1337;File=inline=1:");
5168 append_png_as_base64(output);
5169 output.append(
"\07");
5170 std::cout << output <<
'\n';
5177 std::string base64{};
5178 std::string output{};
5179 append_png_as_base64(base64);
5181 for (; !base64.empty();) {
5184 output.append(
"\033_Ga=T,f=100,");
5186 output.append(
"\033_G");
5188 output.append(base64.length() <= 4096 ?
"m=0;" :
"m=1;");
5189 const size_t bytes_to_append = std::min(base64.length(),
static_cast<size_t>(4096));
5190 output.append(base64.substr(0, bytes_to_append));
5191 base64.erase(0, bytes_to_append);
5192 output.append(
"\033\\");
5194 std::cout << output <<
'\n';
5202 template <device_format dst_format,
typename F>
5204 if constexpr (dst_format == STRAIGHT_THROUGH) {
5205 for (
auto c : data) {
5206 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(c));
5208 }
else if constexpr (dst_format == RGB565_8BIT_SERIAL) {
5209 const uint8_t *ptr = data.data();
5210 for (
size_t y = 0; y < H; y++) {
5211 for (
size_t x = 0; x < W; x++) {
5212 const uint32_t col = format.get_col(ptr, x);
5213 const uint32_t a = ((((col >> 0) & 0xff) >> 3) << 3) | ((((col >> 8) & 0xff) >> 2) >> 3);
5214 const uint32_t b = (((((col >> 8) & 0xff) >> 2) & 0x7) << 5) | (((col >> 16) & 0xff) >> 3);
5215 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(a));
5216 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(b));
5218 ptr += format.bytes_per_line;
5220 }
else if constexpr (dst_format == RGB666_8BIT_SERIAL_1) {
5221 const uint8_t *ptr = data.data();
5222 for (
size_t y = 0; y < H; y++) {
5223 for (
size_t x = 0; x < W; x++) {
5224 const uint32_t col = format.get_col(ptr, x);
5225 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(((col >> 0) & 0xff) >> 2));
5226 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(((col >> 8) & 0xff) >> 2));
5227 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(((col >> 16) & 0xff) >> 2));
5229 ptr += format.bytes_per_line;
5231 }
else if constexpr (dst_format == RGB666_8BIT_SERIAL_2) {
5232 const uint8_t *ptr = data.data();
5233 for (
size_t y = 0; y < H; y++) {
5234 for (
size_t x = 0; x < W; x++) {
5235 const uint32_t col = format.get_col(ptr, x);
5236 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(((col >> 0) & 0xff) >> 2) << 2);
5237 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(((col >> 8) & 0xff) >> 2) << 2);
5238 std::forward<F>(uint8_out)(
static_cast<uint8_t
>(((col >> 16) & 0xff) >> 2) << 2);
5240 ptr += format.bytes_per_line;
5265 constexpr auto rect(int32_t x, int32_t y, int32_t w, int32_t h) {
5276 constexpr auto circle(int32_t cx, int32_t cy, int32_t r) {
5288 constexpr auto circle_aa(int32_t cx, int32_t cy, int32_t r) {
5301 constexpr auto round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius) {
5315 constexpr auto round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius) {
5327 constexpr auto line(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
5340 constexpr auto line_aa(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
5350 constexpr auto point(int32_t x, int32_t y) {
5369 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
5370 constexpr auto text_mono(int32_t x, int32_t y,
const char *str) {
5371 return shapes::text_mono<FONT, T, W, H, GRAYSCALE, USE_SPAN, KERNING, ROTATION>(*
this, x, y, str);
5390 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
5391 constexpr auto text_aa(int32_t x, int32_t y,
const char *str) {
5392 return shapes::text_aa<FONT, T, W, H, GRAYSCALE, USE_SPAN, KERNING, ROTATION>(*
this, x, y, str);
5411 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
5413 return shapes::text_centered_mono<FONT, T, W, H, GRAYSCALE, USE_SPAN, KERNING, ROTATION>(*
this, x, y, str);
5432 template <
typename FONT,
bool KERNING = false, text_rotation ROTATION = DEGREE_0>
5434 return shapes::text_centered_aa<FONT, T, W, H, GRAYSCALE, USE_SPAN, KERNING, ROTATION>(*
this, x, y, str);
5438#ifndef __INTELLISENSE__
5440 constexpr bool check_not_in_bounds(int32_t x, int32_t y, int32_t w, int32_t h) {
5443 if (intersect_rect.w <= 0 || intersect_rect.h <= 0) {
5449 constexpr bool clip_line(int32_t &x0, int32_t &y0, int32_t &x1, int32_t &y1, int32_t xmin, int32_t ymin,
5450 int32_t xmax, int32_t ymax) {
5459 auto calc_code = [&](int32_t x, int32_t y) {
5460 uint32_t code = INSIDE;
5463 }
else if (x > xmax) {
5468 }
else if (y > ymax) {
5474 uint32_t outcode0 = calc_code(x0, y0);
5475 uint32_t outcode1 = calc_code(x1, y1);
5476 auto clip_loop = [&]<
typename I>() ->
bool {
5477 for (
size_t i = 0; i < 4; i++) {
5478 if ((outcode0 | outcode1) == INSIDE) {
5481 if ((outcode0 & outcode1) != 0) {
5484 uint32_t outcode_out = outcode1 > outcode0 ? outcode1 : outcode0;
5487 if ((outcode_out & YMAX) != 0) {
5488 const auto x1x0 =
static_cast<I
>(x1 - x0);
5489 const auto w1y0 =
static_cast<I
>(ymax - y0);
5490 const auto y1y0 =
static_cast<I
>(y1 - y0);
5491 x = x0 +
static_cast<int32_t
>((x1x0 * w1y0) / y1y0);
5493 }
else if ((outcode_out & YMIN) != 0) {
5494 const auto x1x0 =
static_cast<I
>(x1 - x0);
5495 const auto ymy0 =
static_cast<I
>(ymin - y0);
5496 const auto y1y0 =
static_cast<I
>(y1 - y0);
5497 x = x0 +
static_cast<int32_t
>((x1x0 * ymy0) / y1y0);
5499 }
else if ((outcode_out & XMAX) != 0) {
5500 const auto y1y0 =
static_cast<I
>(y1 - y0);
5501 const auto w1x0 =
static_cast<I
>(xmax - x0);
5502 const auto x1x0 =
static_cast<I
>(x1 - x0);
5503 y = y0 +
static_cast<int32_t
>((y1y0 * w1x0) / x1x0);
5506 const auto y1y0 =
static_cast<I
>(y1 - y0);
5507 const auto xmx0 =
static_cast<I
>(xmin - x0);
5508 const auto x1x0 =
static_cast<I
>(x1 - x0);
5509 y = y0 +
static_cast<int32_t
>((y1y0 * xmx0) / x1x0);
5512 if (outcode_out == outcode0) {
5515 outcode0 = calc_code(x0, y0);
5519 outcode1 = calc_code(x1, y1);
5524 return clip_loop.template operator()<calc_square_type>();
5530 template <
typename abs_T>
5531 [[nodiscard]]
static constexpr abs_T abs(abs_T v) {
5532 return v < 0 ? -v : v;
5538 template <
typename FONT>
5539 constexpr bool lookup_glyph(uint32_t utf32,
size_t *glyph_index) {
5540 if (utf32 >=
static_cast<uint32_t
>(std::numeric_limits<typename FONT::lookup_type>::max()) - 1) {
5543 auto index = FONT::glyph_tree.lookup(
static_cast<FONT::lookup_type
>(utf32));
5544 if (index == FONT::glyph_tree.invalid) {
5545 index = FONT::glyph_tree.lookup(
static_cast<FONT::lookup_type
>(0xFFFD));
5546 if (index == FONT::glyph_tree.invalid) {
5547 index = FONT::glyph_tree.lookup(
static_cast<FONT::lookup_type
>(0x0000));
5548 if (index == FONT::glyph_tree.invalid) {
5553 *glyph_index = index;
5560 template <
typename FONT>
5561 constexpr int32_t get_kerning(uint32_t utf32,
const char *str)
const {
5562 if constexpr (FONT::kerning_tree.byte_size() > 0) {
5563 uint32_t utf_l = utf32;
5565 get_next_utf32(str, &utf_r);
5566 auto amount = FONT::kerning_tree.lookup(
5567 static_cast<FONT::kerning_lookup_type
>(utf_l << FONT::kerning_code_shift | utf_r));
5568 if (amount != FONT::kerning_tree.invalid) {
5569 return static_cast<int32_t
>(
static_cast<FONT::kerning_amount_type
>(amount) -
5570 static_cast<FONT::kerning_amount_type
>(FONT::kerning_amount_offset));
5579 constexpr const char *get_next_utf32(
const char *str, uint32_t *utf32)
const {
5580 const uint32_t lead =
static_cast<uint32_t
>(str[0]) & 0xFF;
5585 if ((lead >> 5) == 0x06 && str[1] !=
char{0}) {
5586 *utf32 = ((lead & 0x1F) << 6) | (
static_cast<uint32_t
>(str[1]) & 0x3F);
5589 if ((lead >> 4) == 0x0E && str[1] !=
char{0} && str[2] !=
char{0}) {
5590 *utf32 = ((lead & 0x0F) << 12) | ((
static_cast<uint32_t
>(str[1]) & 0x3F) << 6) |
5591 (
static_cast<uint32_t
>(str[2]) & 0x3F);
5594 if ((lead >> 3) == 0x1E && str[1] !=
char{0} && str[2] !=
char{0} && str[3] !=
char{0}) {
5595 *utf32 = ((lead & 0x07) << 18) | ((
static_cast<uint32_t
>(str[1]) & 0x3F) << 12) |
5596 ((
static_cast<uint32_t
>(str[2]) & 0x3F) << 6) | (
static_cast<uint32_t
>(str[3]) & 0x3F);
5606 constexpr void compose(int32_t x, int32_t y,
float cola,
float colr,
float colg,
float colb) {
5607 auto wS =
static_cast<int32_t
>(W);
5608 auto hS =
static_cast<int32_t
>(H);
5609 if (x >= wS || y >= hS || x < 0 || y < 0) {
5612 T<W, H, GRAYSCALE, USE_SPAN>::compose(data,
static_cast<uint32_t
>(x),
static_cast<uint32_t
>(y), cola, colr,
5619 constexpr void compose_unsafe(int32_t x, int32_t y,
float cola,
float colr,
float colg,
float colb) {
5620 T<W, H, GRAYSCALE, USE_SPAN>::compose(data,
static_cast<uint32_t
>(x),
static_cast<uint32_t
>(y), cola, colr,
5627 constexpr void plot_unsafe(int32_t x, int32_t y, uint8_t col) {
5628 T<W, H, GRAYSCALE, USE_SPAN>::plot(data,
static_cast<uint32_t
>(x),
static_cast<uint32_t
>(y), col);
5634 void append_png_as_base64(std::string &output)
const {
5636 size_t bits_collected = 0;
5637 T<W, H, GRAYSCALE, USE_SPAN>::png(std::span{data}, [&buffer, &bits_collected, &output](
char byte)
mutable {
5638 static constexpr const char *base64_chars =
5639 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5640 buffer = (buffer << 8) | static_cast<uint8_t>(
byte);
5641 bits_collected += 8;
5642 while (bits_collected >= 6) {
5643 bits_collected -= 6;
5644 output.push_back(base64_chars[(buffer >> bits_collected) & 0x3F]);
5646 return [&output]()
mutable {
5647 if (output.capacity() == 0) {
5650 if ((output.size() % 4) != 0) {
5651 const size_t padding = 4 - output.size() % 4;
5652 for (
size_t c = 0; c < padding; c++) {
5653 output.push_back(
'=');
5663 constexpr auto calculate_circle_bounds(int32_t cx, int32_t cy, int32_t r)
const {
5665 int32_t x0, y0, x0r, x0r2, y0r, y0r2;
5668 const int32_t x0 = std::max(cx - r - int32_t{1}, int32_t{0});
5669 const int32_t y0 = std::max(cy - r - int32_t{1}, int32_t{0});
5670 const int32_t x0r = std::clamp(x0 + r, int32_t{0},
static_cast<int32_t
>(W) - int32_t{1});
5671 const int32_t x0r2 = std::clamp(x0 + r * int32_t{2}, int32_t{0},
static_cast<int32_t
>(W) - int32_t{1});
5672 const int32_t y0r = std::clamp(y0 + r, int32_t{0},
static_cast<int32_t
>(H) - int32_t{1});
5673 const int32_t y0r2 = std::clamp(y0 + r * int32_t{2}, int32_t{0},
static_cast<int32_t
>(H) - int32_t{1});
5675 return bounds{x0, y0, x0r, x0r2, y0r, y0r2};
5681 template <
typename shader_func>
5682 constexpr auto fill_rect_int(int32_t x, int32_t y, int32_t w, int32_t h,
const shader_func &shader, int32_t parent_x,
5683 int32_t parent_y, int32_t parent_w, int32_t parent_h) ->
void
5684 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
5686 auto minmax_check = std::minmax({x, y, w, h});
5687 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
5690 if (w <= 0 || h <= 0) {
5693 if (check_not_in_bounds(x, y, w, h)) {
5697 int32_t x0 = std::max(x, int32_t{0});
5698 int32_t y0 = std::max(y, int32_t{0});
5699 int32_t x1 = std::min(x + w,
static_cast<int32_t
>(W));
5700 int32_t y1 = std::min(y + h,
static_cast<int32_t
>(H));
5702 const auto parent_wF =
static_cast<float>(parent_w);
5703 const auto parent_hF =
static_cast<float>(parent_h);
5705 for (int32_t py = y0; py < y1; py++) {
5706 for (int32_t px = x0; px < x1; px++) {
5707 float u = (
static_cast<float>(px) -
static_cast<float>(parent_x)) / parent_wF;
5708 float v = (
static_cast<float>(py) -
static_cast<float>(parent_y)) / parent_hF;
5710 u = std::clamp(u, 0.0f, 1.0f);
5711 v = std::clamp(v, 0.0f, 1.0f);
5713 auto rgba = shader(u, v, px, py);
5714 for (
auto &p : rgba) {
5715 p = std::clamp(p, 0.0f, 1.0f);
5717 compose(px, py, rgba[3], rgba[0], rgba[1], rgba[2]);
5725 template <
bool AA,
bool STROKE>
5726 constexpr void circle_int(int32_t cx, int32_t cy, int32_t r, int32_t ox, int32_t oy, uint8_t col, int32_t s) {
5727 auto bounds = calculate_circle_bounds(cx, cy, r);
5729 if (check_not_in_bounds(bounds.x0, bounds.y0, r * int32_t{2} + ox + int32_t{1},
5730 r * int32_t{2} + oy + int32_t{1})) {
5734 auto for_each_quadrant = [&]<
typename I>(
auto &&plot_arc) {
5735 plot_arc.template operator()<I>(bounds.x0, bounds.y0, bounds.x0r, bounds.y0r, 0, 0);
5736 plot_arc.template operator()<I>(bounds.x0r, bounds.y0, bounds.x0r2, bounds.y0r, ox, 0);
5737 plot_arc.template operator()<I>(bounds.x0, bounds.y0r, bounds.x0r, bounds.y0r2, 0, oy);
5738 plot_arc.template operator()<I>(bounds.x0r, bounds.y0r, bounds.x0r2, bounds.y0r2, ox, oy);
5741 auto limit_box = [](int32_t &xmin, int32_t &ymin, int32_t &xmax, int32_t &ymax, int32_t x_off, int32_t y_off) {
5742 xmin = std::max(xmin + x_off, int32_t{0}) - x_off;
5743 xmax = std::min(xmax + x_off,
static_cast<int32_t
>(W) - int32_t{1}) - x_off;
5744 ymin = std::max(ymin + y_off, int32_t{0}) - y_off;
5745 ymax = std::min(ymax + y_off,
static_cast<int32_t
>(H) - int32_t{1}) - y_off;
5748 if constexpr (!AA) {
5749 if constexpr (!STROKE) {
5750 auto plot_arc = [&,
this]<
typename I>(int32_t xx0, int32_t yy0, int32_t xx1, int32_t yy1, int32_t x_off,
5752 limit_box(xx0, yy0, xx1, yy1, x_off, y_off);
5753 for (int32_t y = yy0; y <= yy1; y++) {
5754 for (int32_t x = xx0; x <= xx1; x++) {
5755 const I dx = (
static_cast<I
>(x) * I{2} + I{1}) - (
static_cast<I
>(cx) * I{2});
5756 const I dy = (
static_cast<I
>(y) * I{2} + I{1}) - (
static_cast<I
>(cy) * I{2});
5757 const I dist_sq = dx * dx + dy * dy;
5758 if (dist_sq > (
static_cast<I
>(r) *
static_cast<I
>(r) * I{4} - I{3})) {
5761 plot_unsafe(x + x_off, y + y_off, col);
5765 for_each_quadrant.template operator()<calc_square_type>(plot_arc);
5767 auto plot_arc = [&,
this]<
typename I>(int32_t xx0, int32_t yy0, int32_t xx1, int32_t yy1, int32_t x_off,
5769 limit_box(xx0, yy0, xx1, yy1, x_off, y_off);
5770 for (int32_t y = yy0; y <= yy1; y++) {
5771 for (int32_t x = xx0; x <= xx1; x++) {
5772 const I dx = (
static_cast<I
>(x) * I{2} + I{1}) - (
static_cast<I
>(cx) * I{2});
5773 const I dy = (
static_cast<I
>(y) * I{2} + I{1}) - (
static_cast<I
>(cy) * I{2});
5774 const I dist_sq = dx * dx + dy * dy;
5775 if (dist_sq > (
static_cast<I
>(r) *
static_cast<I
>(r) * I{4} - I{3})) {
5778 if (dist_sq < (
static_cast<I
>(r - s) *
static_cast<I
>(r - s) * I{4} - I{3})) {
5781 plot_unsafe(x + x_off, y + y_off, col);
5785 for_each_quadrant.template operator()<calc_square_type>(plot_arc);
5788 const auto rF =
static_cast<float>(r);
5789 const float Rl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 0);
5790 const float Gl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 1);
5791 const float Bl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 2);
5792 if constexpr (!STROKE) {
5793 auto plot_arc = [&,
this]<
typename I>(int32_t xx0, int32_t yy0, int32_t xx1, int32_t yy1, int32_t x_off,
5795 limit_box(xx0, yy0, xx1, yy1, x_off, y_off);
5796 for (int32_t y = yy0; y <= yy1; y++) {
5797 for (int32_t x = xx0; x <= xx1; x++) {
5798 const float dx = (
static_cast<float>(x) + 0.5f) -
static_cast<float>(cx);
5799 const float dy = (
static_cast<float>(y) + 0.5f) -
static_cast<float>(cy);
5800 const float dist_sq = dx * dx + dy * dy;
5801 if (dist_sq > (rF + 0.5f) * (rF + 0.5f)) {
5804 if (dist_sq < (rF - 0.5f) * (rF - 0.5f)) {
5805 plot_unsafe(x + x_off, y + y_off, col);
5809 if (std::is_constant_evaluated()) {
5810 a -= hidden::fast_sqrtf(dist_sq);
5812 a -= std::sqrt(dist_sq);
5814 a = std::clamp(a + 0.5f, 0.0f, 1.0f);
5815 if (a >= hidden::epsilon_low) {
5816 if (a >= hidden::epsilon_high) {
5817 plot_unsafe(x + x_off, y + y_off, col);
5819 compose_unsafe(x + x_off, y + y_off, a, Rl, Gl, Bl);
5825 for_each_quadrant.template operator()<
float>(plot_arc);
5827 const auto rsF =
static_cast<float>(r - s);
5828 auto plot_arc = [&,
this]<
typename I>(int32_t xx0, int32_t yy0, int32_t xx1, int32_t yy1, int32_t x_off,
5830 limit_box(xx0, yy0, xx1, yy1, x_off, y_off);
5831 for (int32_t y = yy0; y <= yy1; y++) {
5832 for (int32_t x = xx0; x <= xx1; x++) {
5833 const float dx = (
static_cast<float>(x) + 0.5f) -
static_cast<float>(cx);
5834 const float dy = (
static_cast<float>(y) + 0.5f) -
static_cast<float>(cy);
5835 const float dist_sq = dx * dx + dy * dy;
5836 if (dist_sq > ((rF + 0.5f) * (rF + 0.5f))) {
5839 if (dist_sq < ((rsF - 0.5f) * (rsF - 0.5f))) {
5842 if (dist_sq < ((rsF + 0.5f) * (rsF + 0.5f))) {
5844 if (std::is_constant_evaluated()) {
5845 a -= hidden::fast_sqrtf(dist_sq);
5847 a -= std::sqrt(dist_sq);
5849 a = 1.0f - std::clamp(a + 0.5f, 0.0f, 1.0f);
5850 if (a >= hidden::epsilon_low) {
5851 if (a >= hidden::epsilon_high) {
5852 plot_unsafe(x + x_off, y + y_off, col);
5854 compose_unsafe(x + x_off, y + y_off, a, Rl, Gl, Bl);
5857 plot_unsafe(x + x_off, y + y_off, col);
5861 if (std::is_constant_evaluated()) {
5862 a -= hidden::fast_sqrtf(dist_sq);
5864 a -= std::sqrt(dist_sq);
5866 a = std::clamp(a + 0.5f, 0.0f, 1.0f);
5867 if (a >= hidden::epsilon_low) {
5868 if (a >= hidden::epsilon_high) {
5869 plot_unsafe(x + x_off, y + y_off, col);
5871 compose_unsafe(x + x_off, y + y_off, a, Rl, Gl, Bl);
5878 for_each_quadrant.template operator()<
float>(plot_arc);
5886 template <
typename shader_func>
5887 constexpr void circle_int_shader(int32_t cx, int32_t cy, int32_t r, int32_t ox, int32_t oy,
5888 const shader_func &shader, int32_t parent_x = -1, int32_t parent_y = -1,
5889 int32_t parent_w = -1, int32_t parent_h = -1) {
5890 auto bounds = calculate_circle_bounds(cx, cy, r);
5892 if (check_not_in_bounds(bounds.x0, bounds.y0, r * int32_t{2} + ox + int32_t{1},
5893 r * int32_t{2} + oy + int32_t{1})) {
5897 auto for_each_quadrant = [&]<
typename I>(
auto &&plot_arc) {
5898 plot_arc.template operator()<I>(bounds.x0, bounds.y0, bounds.x0r, bounds.y0r, 0, 0);
5899 plot_arc.template operator()<I>(bounds.x0r, bounds.y0, bounds.x0r2, bounds.y0r, ox, 0);
5900 plot_arc.template operator()<I>(bounds.x0, bounds.y0r, bounds.x0r, bounds.y0r2, 0, oy);
5901 plot_arc.template operator()<I>(bounds.x0r, bounds.y0r, bounds.x0r2, bounds.y0r2, ox, oy);
5904 auto limit_box = [](int32_t &xmin, int32_t &ymin, int32_t &xmax, int32_t &ymax, int32_t x_off, int32_t y_off) {
5905 xmin = std::max(xmin + x_off, int32_t{0}) - x_off;
5906 xmax = std::min(xmax + x_off,
static_cast<int32_t
>(W) - int32_t{1}) - x_off;
5907 ymin = std::max(ymin + y_off, int32_t{0}) - y_off;
5908 ymax = std::min(ymax + y_off,
static_cast<int32_t
>(H) - int32_t{1}) - y_off;
5911 const auto rF =
static_cast<float>(r);
5912 const int32_t actual_parent_x = (parent_x == -1) ? cx - r : parent_x;
5913 const int32_t actual_parent_y = (parent_y == -1) ? cy - r : parent_y;
5914 const int32_t actual_parent_w = (parent_w == -1) ? r * 2 : parent_w;
5915 const int32_t actual_parent_h = (parent_h == -1) ? r * 2 : parent_h;
5917 const auto parent_wF =
static_cast<float>(actual_parent_w);
5918 const auto parent_hF =
static_cast<float>(actual_parent_h);
5920 auto plot_arc = [&,
this]<
typename I>(int32_t xx0, int32_t yy0, int32_t xx1, int32_t yy1, int32_t x_off,
5922 limit_box(xx0, yy0, xx1, yy1, x_off, y_off);
5923 for (int32_t y = yy0; y <= yy1; y++) {
5924 for (int32_t x = xx0; x <= xx1; x++) {
5925 const float dx = (
static_cast<float>(x) + 0.5f) -
static_cast<float>(cx);
5926 const float dy = (
static_cast<float>(y) + 0.5f) -
static_cast<float>(cy);
5927 const float dist_sq = dx * dx + dy * dy;
5928 if (dist_sq > (rF + 0.5f) * (rF + 0.5f)) {
5932 float u = (
static_cast<float>(x + x_off) -
static_cast<float>(actual_parent_x)) / parent_wF;
5933 float v = (
static_cast<float>(y + y_off) -
static_cast<float>(actual_parent_y)) / parent_hF;
5935 u = std::clamp(u, 0.0f, 1.0f);
5936 v = std::clamp(v, 0.0f, 1.0f);
5937 auto rgba = shader(u, v, x + x_off, y + y_off);
5938 for (
auto &p : rgba) {
5939 p = std::clamp(p, 0.0f, 1.0f);
5942 if (dist_sq < (rF - 0.5f) * (rF - 0.5f)) {
5943 compose_unsafe(x + x_off, y + y_off, rgba[3], rgba[0], rgba[1], rgba[2]);
5948 if (std::is_constant_evaluated()) {
5949 a -= hidden::fast_sqrtf(dist_sq);
5951 a -= std::sqrt(dist_sq);
5953 a = std::clamp(a + 0.5f, 0.0f, 1.0f);
5954 if (a >= hidden::epsilon_low) {
5955 compose_unsafe(x + x_off, y + y_off, a * rgba[3], rgba[0], rgba[1], rgba[2]);
5960 for_each_quadrant.template operator()<
float>(plot_arc);
5966 template <
typename FONT, text_rotation ROTATION>
5967 constexpr void draw_char_mono(int32_t x, int32_t y,
const char_info<typename FONT::char_info_type> &ch,
5969 static_assert(FONT::mono ==
true,
"Can't use an antialiased font to draw mono/pixelized text.");
5970 int32_t ch_data_off =
static_cast<int32_t
>(ch.y) *
static_cast<int32_t
>(FONT::glyph_bitmap_stride) +
5971 static_cast<int32_t
>(ch.x) / 8;
5973 auto limit_box = [&](int32_t &xmin, int32_t &ymin, int32_t &xmax, int32_t &ymax) {
5974 ymin = std::max(ymin, int32_t{0});
5975 ymax = std::min(ymax,
static_cast<int32_t
>(H) - int32_t{1});
5976 xmin = std::max(xmin, int32_t{0});
5977 xmax = std::min(xmax,
static_cast<int32_t
>(W) - int32_t{1});
5980 if constexpr (ROTATION == DEGREE_0) {
5981 if (check_not_in_bounds(x + ch.xoffset, y + ch.yoffset, ch.width + 1, ch.height + 1)) {
5987 int32_t x2 = x +
static_cast<int32_t
>(ch.width);
5988 int32_t y2 = y +
static_cast<int32_t
>(ch.height);
5989 limit_box(x, y, x2, y2);
5990 for (int32_t yy = y; yy < y2; yy++) {
5991 for (int32_t xx = x; xx < x2; xx++) {
5992 const int32_t x_off = (xx - x) + ch.x % 8;
5993 const int32_t bit_index = 7 - (x_off % 8);
5994 const auto byte_index =
static_cast<size_t>(ch_data_off + x_off / 8);
5995 if (byte_index < (FONT::glyph_bitmap_stride * FONT::glyph_bitmap_height)) {
5996 const auto a =
static_cast<uint8_t
>((FONT::glyph_bitmap[byte_index] >> bit_index) & 1);
5998 plot_unsafe(xx, yy, col);
6002 ch_data_off += FONT::glyph_bitmap_stride;
6004 }
else if constexpr (ROTATION == DEGREE_180) {
6005 if (check_not_in_bounds(x - ch.xoffset - ch.width, y - FONT::ascent, ch.width + 1, ch.height + 1)) {
6011 int32_t x2 = x -
static_cast<int32_t
>(ch.width);
6012 int32_t y2 = y +
static_cast<int32_t
>(ch.height);
6013 limit_box(x2, y, x, y2);
6014 for (int32_t yy = y2 - 1; yy >= y; yy--) {
6015 for (int32_t xx = x2; xx < x; xx++) {
6016 const int32_t x_off = (ch.width - (xx - x2) - 1) + ch.x % 8;
6017 const int32_t bit_index = 7 - (x_off % 8);
6018 const auto byte_index =
static_cast<size_t>(ch_data_off + x_off / 8);
6019 if (byte_index < (FONT::glyph_bitmap_stride * FONT::glyph_bitmap_height)) {
6020 const auto a =
static_cast<uint8_t
>((FONT::glyph_bitmap[byte_index] >> bit_index) & 1);
6022 plot_unsafe(xx, yy, col);
6026 ch_data_off += FONT::glyph_bitmap_stride;
6028 }
else if constexpr (ROTATION == DEGREE_90) {
6029 if (check_not_in_bounds(x - FONT::ascent, y, ch.height + 1, ch.width + 1)) {
6035 int32_t x2 = x +
static_cast<int32_t
>(ch.height);
6036 int32_t y2 = y +
static_cast<int32_t
>(ch.width);
6037 limit_box(x, y, x2, y2);
6038 for (int32_t xx = x2 - 1; xx >= x; xx--) {
6039 for (int32_t yy = y; yy < y2; yy++) {
6040 const int32_t x_off = (yy - y) + ch.x % 8;
6041 const int32_t bit_index = 7 - (x_off % 8);
6042 const auto byte_index =
static_cast<size_t>(ch_data_off + x_off / 8);
6043 if (byte_index < (FONT::glyph_bitmap_stride * FONT::glyph_bitmap_height)) {
6044 const auto a =
static_cast<uint8_t
>((FONT::glyph_bitmap[byte_index] >> bit_index) & 1);
6046 plot_unsafe(xx, yy, col);
6050 ch_data_off += FONT::glyph_bitmap_stride;
6052 }
else if constexpr (ROTATION == DEGREE_270) {
6053 if (check_not_in_bounds(x + ch.yoffset, y - ch.xoffset - ch.width, ch.height + 1, ch.width + 1)) {
6059 int32_t x2 = x +
static_cast<int32_t
>(ch.height);
6060 int32_t y2 = y -
static_cast<int32_t
>(ch.width);
6061 limit_box(x, y2, x2, y);
6062 for (int32_t xx = x; xx < x2; xx++) {
6063 for (int32_t yy = y2; yy < y; yy++) {
6064 const int32_t x_off = (ch.width - (yy - y2) - 1) + ch.x % 8;
6065 const int32_t bit_index = 7 - (x_off % 8);
6066 const auto byte_index =
static_cast<size_t>(ch_data_off + x_off / 8);
6067 if (byte_index < (FONT::glyph_bitmap_stride * FONT::glyph_bitmap_height)) {
6068 const auto a =
static_cast<uint8_t
>((FONT::glyph_bitmap[byte_index] >> bit_index) & 1);
6070 plot_unsafe(xx, yy, col);
6074 ch_data_off += FONT::glyph_bitmap_stride;
6082 template <
typename FONT, text_rotation ROTATION>
6083 constexpr void draw_char_aa(int32_t x, int32_t y,
const char_info<typename FONT::char_info_type> &ch, uint8_t col) {
6084 static_assert(FONT::mono ==
false,
"Can't use a mono font to draw antialiased text.");
6086 int32_t ch_data_off =
static_cast<int32_t
>(ch.y) *
static_cast<int32_t
>(FONT::glyph_bitmap_stride) +
6087 static_cast<int32_t
>(ch.x) / 2;
6088 const float Rl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 0);
6089 const float Gl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 1);
6090 const float Bl = format.quant.linear_palette().at((col & format.color_mask) * 3 + 2);
6092 auto limit_box = [&](int32_t &xmin, int32_t &ymin, int32_t &xmax, int32_t &ymax) {
6093 ymin = std::max(ymin, int32_t{0});
6094 ymax = std::min(ymax,
static_cast<int32_t
>(H) - int32_t{1});
6095 xmin = std::max(xmin, int32_t{0});
6096 xmax = std::min(xmax,
static_cast<int32_t
>(W) - int32_t{1});
6099 if constexpr (ROTATION == DEGREE_0) {
6100 if (check_not_in_bounds(x + ch.xoffset, y + ch.yoffset, ch.width + 1, ch.height + 1)) {
6106 int32_t x2 = x +
static_cast<int32_t
>(ch.width);
6107 int32_t y2 = y +
static_cast<int32_t
>(ch.height);
6108 limit_box(x, y, x2, y2);
6109 for (int32_t yy = y; yy < y2; yy++) {
6110 for (int32_t xx = x; xx < x2; xx++) {
6111 const int32_t x_off = (xx - x) + ch.x % 2;
6112 const int32_t bit_index = (1 - (x_off % 2)) * 4;
6113 const auto byte_index =
static_cast<size_t>(ch_data_off + x_off / 2);
6114 if (byte_index < (FONT::glyph_bitmap_stride * FONT::glyph_bitmap_height)) {
6115 const auto a =
static_cast<uint8_t
>((FONT::glyph_bitmap[byte_index] >> bit_index) & 0xF);
6118 plot_unsafe(xx, yy, col);
6120 float Al = hidden::a2al_4bit[a];
6121 compose_unsafe(xx, yy, Al, Rl, Gl, Bl);
6126 ch_data_off += FONT::glyph_bitmap_stride;
6128 }
else if constexpr (ROTATION == DEGREE_180) {
6129 if (check_not_in_bounds(x - ch.xoffset - ch.width, y - FONT::ascent, ch.width + 1, ch.height + 1)) {
6135 int32_t x2 = x -
static_cast<int32_t
>(ch.width);
6136 int32_t y2 = y +
static_cast<int32_t
>(ch.height);
6137 limit_box(x2, y, x, y2);
6138 for (int32_t yy = y2 - 1; yy >= y; yy--) {
6139 for (int32_t xx = x2; xx < x; xx++) {
6140 const int32_t x_off = (ch.width - (xx - x2) - 1) + ch.x % 2;
6141 const int32_t bit_index = (1 - (x_off % 2)) * 4;
6142 const auto byte_index =
static_cast<size_t>(ch_data_off + x_off / 2);
6143 if (byte_index < (FONT::glyph_bitmap_stride * FONT::glyph_bitmap_height)) {
6144 const auto a =
static_cast<uint8_t
>((FONT::glyph_bitmap[byte_index] >> bit_index) & 0xF);
6147 plot_unsafe(xx, yy, col);
6149 float Al = hidden::a2al_4bit[a];
6150 compose_unsafe(xx, yy, Al, Rl, Gl, Bl);
6155 ch_data_off += FONT::glyph_bitmap_stride;
6157 }
else if constexpr (ROTATION == DEGREE_90) {
6158 if (check_not_in_bounds(x - FONT::ascent, y, ch.height + 1, ch.width + 1)) {
6164 int32_t x2 = x +
static_cast<int32_t
>(ch.height);
6165 int32_t y2 = y +
static_cast<int32_t
>(ch.width);
6166 limit_box(x, y, x2, y2);
6167 for (int32_t xx = x2 - 1; xx >= x; xx--) {
6168 for (int32_t yy = y; yy < y2; yy++) {
6169 const int32_t x_off = (yy - y) + ch.x % 2;
6170 const int32_t bit_index = (1 - (x_off % 2)) * 4;
6171 const auto byte_index =
static_cast<size_t>(ch_data_off + x_off / 2);
6172 if (byte_index < (FONT::glyph_bitmap_stride * FONT::glyph_bitmap_height)) {
6173 const auto a =
static_cast<uint8_t
>((FONT::glyph_bitmap[byte_index] >> bit_index) & 0xF);
6176 plot_unsafe(xx, yy, col);
6178 float Al = hidden::a2al_4bit[a];
6179 compose_unsafe(xx, yy, Al, Rl, Gl, Bl);
6184 ch_data_off += FONT::glyph_bitmap_stride;
6186 }
else if constexpr (ROTATION == DEGREE_270) {
6187 if (check_not_in_bounds(x + ch.yoffset, y - ch.xoffset - ch.width, ch.height + 1, ch.width + 1)) {
6193 int32_t x2 = x +
static_cast<int32_t
>(ch.height);
6194 int32_t y2 = y -
static_cast<int32_t
>(ch.width);
6195 limit_box(x, y2, x2, y);
6196 for (int32_t xx = x; xx < x2; xx++) {
6197 for (int32_t yy = y2; yy < y; yy++) {
6198 const int32_t x_off = (ch.width - (yy - y2) - 1) + ch.x % 2;
6199 const int32_t bit_index = (1 - (x_off % 2)) * 4;
6200 const auto byte_index =
static_cast<size_t>(ch_data_off + x_off / 2);
6201 if (byte_index < (FONT::glyph_bitmap_stride * FONT::glyph_bitmap_height)) {
6202 const auto a =
static_cast<uint8_t
>((FONT::glyph_bitmap[byte_index] >> bit_index) & 0xF);
6205 plot_unsafe(xx, yy, col);
6207 float Al = hidden::a2al_4bit[a];
6208 compose_unsafe(xx, yy, Al, Rl, Gl, Bl);
6213 ch_data_off += FONT::glyph_bitmap_stride;
6221 constexpr void extent(int32_t x, int32_t w, int32_t y, uint8_t col) {
6222 auto wS =
static_cast<int32_t
>(W);
6223 auto hS =
static_cast<int32_t
>(H);
6224 if (w <= 0 || y < 0 || y >= hS || x + w <= 0 || x >= wS) {
6227 const int32_t x0 = std::max(x, int32_t{0});
6228 const int32_t x1 = std::min(x + w,
static_cast<int32_t
>(W));
6229 T<W, H, GRAYSCALE, USE_SPAN>::extent(data,
static_cast<size_t>(x0),
static_cast<size_t>(x1),
6230 static_cast<size_t>(y), col);
6236 using dataType = std::conditional_t<USE_SPAN, std::span<uint8_t, T<W, H, GRAYSCALE, USE_SPAN>::image_size>,
6237 std::array<uint8_t, T<W, H, GRAYSCALE, USE_SPAN>::image_size>>;
6243 T<W, H, GRAYSCALE, USE_SPAN> format{};
Core class of constixel. Holds a buffer of an image width a certain size and format....
Definition constixel.hpp:3502
constexpr auto line_aa(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
Create an antialiased line shape for fluent method chaining. Only format_8bit targets are supported.
Definition constixel.hpp:5340
constexpr void sixel(F &&char_out, const rect< int32_t > &rect) const
Convert the current instance into a sixel stream. Typically an implementation looks like this:
Definition constixel.hpp:5121
constexpr void fill_round_rect(const struct draw_round_rect &d)
Fill a rounded rectangle with the specified color. Example:
Definition constixel.hpp:4722
constexpr int32_t string_width(const char *str, size_t character_count=std::numeric_limits< size_t >::max(), size_t *character_actual=nullptr)
Return the width of a string using the specified font in the template parameter. Typical use:
Definition constixel.hpp:4001
constexpr void fill_circle(const struct draw_circle &d)
Fill a circle with the specified radius and color. Example:
Definition constixel.hpp:4465
constexpr auto circle(int32_t cx, int32_t cy, int32_t r)
Create a circle shape for fluent method chaining.
Definition constixel.hpp:5276
constexpr void draw_line_aa(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t col, float stroke_width=1.0f)
Draw an antialiased line with variable stroke width. Only format_8bit targets are supported....
Definition constixel.hpp:3803
constexpr void flip_h()
Flip the contents of this image horizontally.
Definition constixel.hpp:4891
constexpr void blit_RGBA_diffused(const rect< int32_t > &dstrect, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride)
Blit an RGBA8 buffer into this instance using brute force color mapping. Simple integer based diffusi...
Definition constixel.hpp:5025
static constexpr int32_t width()
Width in pixels of the image.
Definition constixel.hpp:3572
constexpr void stroke_round_rect(const struct draw_round_rect &d)
Stroke a rounded rectangle with the specified color. Example:
Definition constixel.hpp:4678
constexpr void copy(const uint8_t *src)
Copy raw source data into this instance. No compositing occurs.
Definition constixel.hpp:3620
constexpr void fill_circle(int32_t cx, int32_t cy, int32_t radius, uint8_t col)
Definition constixel.hpp:4443
constexpr void stroke_circle(const struct draw_circle &d)
Stroke a circle with the specified radius and color. Example:
Definition constixel.hpp:4509
constexpr int32_t draw_string_aa(int32_t x, int32_t y, const char *str, uint8_t col, size_t character_count=std::numeric_limits< size_t >::max(), size_t *character_actual=nullptr)
Draw antialiased text at the specified coordinate. The template parameter selects which antialiased f...
Definition constixel.hpp:4183
constexpr void stroke_round_rect_aa(const struct draw_round_rect &d)
Stroke a rounded rectangle using antialiasing with the specified color. Example:
Definition constixel.hpp:4868
constexpr void fill_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col)
Fill a rounded rectangle using antialiasing with the specified color. Only format_8bit targets are su...
Definition constixel.hpp:4741
constexpr image< T, H, W, GRAYSCALE > transpose() const
Return a transposed version of this image.
Definition constixel.hpp:4944
constexpr auto round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius)
Create an antialiased rounded rectangle shape for fluent method chaining. Only format_8bit targets ar...
Definition constixel.hpp:5315
constexpr auto text_centered_mono(int32_t x, int32_t y, const char *str)
Create a text shape for fluent centered monospace string drawing with method chaining.
Definition constixel.hpp:5412
constexpr auto text_centered_aa(int32_t x, int32_t y, const char *str)
Create a text shape for fluent centered antialiased string drawing with method chaining....
Definition constixel.hpp:5433
constexpr void RGBA_uint8(std::array< uint8_t, W *H *4 > &dst) const
Convert this instance to an equivalent RGBA8 data array.
Definition constixel.hpp:4884
constexpr void stroke_circle(int32_t cx, int32_t cy, int32_t radius, uint8_t col, int32_t stroke_width=1)
Stroke a circle with the specified radius and color. Example:
Definition constixel.hpp:4482
constexpr auto fill_circle_aa(int32_t cx, int32_t cy, int32_t radius, const shader_func &shader) -> void
Fill a circle using antialiasing with a shader function that generates colors per pixel.
Definition constixel.hpp:4614
constexpr void convert(F &&uint8_out)
Convert the current instance into a byte stream formatted for embedded displays.
Definition constixel.hpp:5203
void sixel_to_cout() const
Convert the current instance into a sixel stream and output it to std::cout.
Definition constixel.hpp:5131
constexpr void blit_RGBA_diffused_linear(const rect< int32_t > &dstrect, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride)
Blit an RGBA8 buffer into this instance using brute force color mapping. Diffusion in linear color sp...
Definition constixel.hpp:5063
void vt100_clear_scrollback() const
Send a escape command to std::cout to clear the screen and scroll buffer of a vt100 compatible termin...
Definition constixel.hpp:5151
constexpr void fill_round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col)
Fill a rounded rectangle with the specified color. Example:
Definition constixel.hpp:4696
constexpr void sixel(F &&char_out) const
Convert the current instance into a sixel stream. Typically an implementation looks like this:
Definition constixel.hpp:5102
constexpr auto text_mono(int32_t x, int32_t y, const char *str)
Create a text shape for fluent monospace string drawing with method chaining.
Definition constixel.hpp:5370
constexpr void flip_v()
Flip the contents of this image vertically.
Definition constixel.hpp:4907
void vt100_clear() const
Send a escape command to std::cout to clear the screen and scroll buffer of a vt100 compatible termin...
Definition constixel.hpp:5144
constexpr void draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t col, int32_t stroke_width=1)
Draw a line with the specified color and thickness. Example:
Definition constixel.hpp:3676
constexpr void flip_hv()
Flip the contents of this image horizontally & vertically.
Definition constixel.hpp:4924
constexpr auto text_aa(int32_t x, int32_t y, const char *str)
Create a text shape for fluent antialiased string drawing with method chaining. Only format_8bit targ...
Definition constixel.hpp:5391
constexpr void draw_line(const struct draw_line &d)
Draw a line with the specified color and thickness. Example:
Definition constixel.hpp:3784
constexpr void draw_line_aa(const struct draw_line &d)
Draw a 1-pixel wide antialiased line with the specified color. Only format_8bit targets are supported...
Definition constixel.hpp:3979
constexpr void fill_circle_aa(const struct draw_circle &d)
Fill a circle using antialiasing with the specified radius and color. Only format_8bit targets are su...
Definition constixel.hpp:4591
constexpr void plot(const struct plot &p)
Plot a single pixel at the specified coordinates using the supplied color.
Definition constixel.hpp:3658
constexpr void png(F &&char_out) const
Convert the current instance into a png image. Typically an implementation looks like this:
Definition constixel.hpp:5084
void png_to_iterm() const
Convert the current instance into a png and display it in iTerm.
Definition constixel.hpp:5165
constexpr void clear()
Clear the image by setting everything to zero.
Definition constixel.hpp:3587
constexpr void copy(const image< T, W, H, GRAYSCALE > &src)
Copy source image into this instance. No compositing occurs, replaces current content.
Definition constixel.hpp:3610
constexpr void stroke_circle_aa(int32_t cx, int32_t cy, int32_t radius, uint8_t col, int32_t stroke_width=1)
Stroke a circle using antialiasing with the specified radius and color. Only format_8bit targets are ...
Definition constixel.hpp:4527
constexpr int32_t draw_string_aa(const struct draw_string &d, size_t character_count=std::numeric_limits< size_t >::max(), size_t *character_actual=nullptr)
Draw antialiased text at the specified coordinate. The template parameter selects which antialiased f...
Definition constixel.hpp:4257
constexpr auto fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const shader_func &shader) -> void
Fill a rectangle with a shader function that generates colors per pixel.
Definition constixel.hpp:4357
constexpr void fill_rect(const struct draw_rect &d)
Fill a rectangle with the specified color. Example:
Definition constixel.hpp:4335
static constexpr size_t size()
Size in bytes of the image data. This does not include the image instance size.
Definition constixel.hpp:3556
constexpr void stroke_circle_aa(const struct draw_circle &d)
Stroke a circle using antialiasing with the specified radius and color. Only format_8bit targets are ...
Definition constixel.hpp:4555
constexpr void RGBA_uint32(std::array< uint32_t, W *H > &dst) const
Convert this instance to an equivalent RGBA8 data array.
Definition constixel.hpp:4876
constexpr image< T, W, H, GRAYSCALE > clone() const
Returns a clone of this image. Data is copied.
Definition constixel.hpp:3603
constexpr std::array< uint8_t, T< W, H, GRAYSCALE, USE_SPAN >::image_size > & data_ref()
Get a reference to the underlying raw data of the image.
Definition constixel.hpp:3595
constexpr int32_t draw_string_mono(int32_t x, int32_t y, const char *str, uint8_t col, size_t character_count=std::numeric_limits< size_t >::max(), size_t *character_actual=nullptr)
Draw text at the specified coordinate. The template parameter selects which mono font to use....
Definition constixel.hpp:4053
constexpr auto round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius)
Create a rounded rectangle shape for fluent method chaining.
Definition constixel.hpp:5301
constexpr void blit_RGBA(int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride)
Blit an RGBA8 buffer into this instance using brute force color mapping.
Definition constixel.hpp:4971
constexpr void transpose(image< T, H, W, GRAYSCALE > &dst) const
Transpose this image into another.
Definition constixel.hpp:4956
constexpr void fill_circle_aa(int32_t cx, int32_t cy, int32_t radius, uint8_t col)
Fill a circle using antialiasing with the specified radius and color. Only format_8bit targets are su...
Definition constixel.hpp:4572
void png_to_kitty() const
Convert the current instance into a png and display it in a terminal with kitty graphics support.
Definition constixel.hpp:5176
constexpr void stroke_round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col, int32_t stroke_width=1)
Stroke a rounded rectangle with the specified color. Example:
Definition constixel.hpp:4640
constexpr uint8_t get_nearest_color(uint8_t r, uint8_t g, uint8_t b) const
Return closest match in the color palette based on the supplied red, green and blue values.
Definition constixel.hpp:3634
constexpr auto line(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
Create a line shape for fluent method chaining.
Definition constixel.hpp:5327
constexpr void blit_RGBA_diffused(int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride)
Blit an RGBA8 buffer into this instance using brute force color mapping. Simple integer based diffusi...
Definition constixel.hpp:5007
constexpr auto fill_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, const shader_func &shader) -> void
Fill a rounded rectangle using antialiasing with a shader function that generates colors per pixel.
Definition constixel.hpp:4792
image()=default
Creates a new image with internal storage.
static constexpr int32_t height()
Height in pixels of the image.
Definition constixel.hpp:3580
constexpr auto circle_aa(int32_t cx, int32_t cy, int32_t r)
Create an antialiased circle shape for fluent method chaining. Only format_8bit targets are supported...
Definition constixel.hpp:5288
constexpr void draw_string_centered_mono(int32_t x, int32_t y, const char *str, uint8_t col)
Draw text centered at the specified coordinate. The template parameter selects which mono font to use...
Definition constixel.hpp:4146
constexpr void plot(int32_t x, int32_t y, uint8_t col)
Plot a single pixel at the specified coordinates using the supplied color.
Definition constixel.hpp:3644
static constexpr bool grayscale()
Boolean indicating that the palette is grayscale instead of color.
Definition constixel.hpp:3540
constexpr void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col)
Fill a rectangle with the specified color. Example:
Definition constixel.hpp:4302
static constexpr size_t bit_depth()
Bit depth of the image.
Definition constixel.hpp:3548
constexpr auto point(int32_t x, int32_t y)
Create a point shape for fluent method chaining.
Definition constixel.hpp:5350
constexpr void blit_RGBA(const rect< int32_t > &dstrect, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride)
Blit an RGBA8 buffer into this instance using brute force color mapping.
Definition constixel.hpp:4988
constexpr void fill_round_rect_aa(const struct draw_round_rect &d)
Fill a rounded rectangle using antialiasing with the specified color. Only format_8bit targets are su...
Definition constixel.hpp:4769
static constexpr size_t bytes_per_line()
Bytes per line in the image data. Also called stride.
Definition constixel.hpp:3564
constexpr void draw_string_centered_aa(int32_t x, int32_t y, const char *str, uint8_t col)
Draw antialiased text centered at the specified coordinate. The template parameter selects which anti...
Definition constixel.hpp:4277
void vt100_home() const
Send a escape command to std::cout to home the cursor of a vt100 compatible terminal.
Definition constixel.hpp:5158
constexpr void stroke_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col, int32_t stroke_width=1)
Stroke a rounded rectangle using antialiasing with the specified color. Example:
Definition constixel.hpp:4830
constexpr auto rect(int32_t x, int32_t y, int32_t w, int32_t h)
Definition constixel.hpp:5265
image(const std::span< uint8_t, T< W, H, GRAYSCALE, USE_SPAN >::image_size > &other)
When USE_SPAN=true creates a new image with external storage based existing std::span.
Definition constixel.hpp:3531
constexpr void stroke_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col, int32_t stroke_width=1)
Draw a stroked rectangle with the specified color and stroke width. Example:
Definition constixel.hpp:4403
constexpr void blit_RGBA_diffused_linear(int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride)
Blit an RGBA8 buffer into this instance using brute force color mapping. Diffusion in linear color sp...
Definition constixel.hpp:5045
constexpr int32_t draw_string_mono(const struct draw_string &d, size_t character_count=std::numeric_limits< size_t >::max(), size_t *character_actual=nullptr)
Draw text at the specified coordinate. The template parameter selects which mono font to use....
Definition constixel.hpp:4127
constexpr void stroke_rect(const struct draw_rect &d)
Draw a stroked rectangle with the specified color and stroke width. Example:
Definition constixel.hpp:4427
Fluent API for drawing anti-aliased circles.
Definition constixel.hpp:3051
constexpr circle_aa & stroke(uint8_t col, int32_t stroke_width=1)
Draw the circle outline.
Definition constixel.hpp:3096
constexpr circle_aa & fill(uint8_t col)
Fill the circle with a solid color.
Definition constixel.hpp:3072
constexpr auto fill_shader(const shader_func &shader) -> circle_aa &
Fill the circle using a shader function.
Definition constixel.hpp:3083
constexpr circle_aa(image_type &image, int32_t cx_, int32_t cy_, int32_t r_)
Construct an anti-aliased circle shape.
Definition constixel.hpp:3064
Fluent API for drawing circles.
Definition constixel.hpp:3007
constexpr circle & fill(uint8_t col)
Fill the circle with a solid color.
Definition constixel.hpp:3028
constexpr circle(image_type &image, int32_t cx_, int32_t cy_, int32_t r_)
Construct a circle shape.
Definition constixel.hpp:3020
constexpr circle & stroke(uint8_t col, int32_t stroke_width=1)
Draw the circle outline.
Definition constixel.hpp:3039
Fluent API for drawing anti-aliased lines.
Definition constixel.hpp:3251
constexpr line_aa & stroke(uint8_t col, float stroke_width=1.0f)
Draw the anti-aliased line.
Definition constixel.hpp:3275
constexpr line_aa(image_type &image, int32_t x0_, int32_t y0_, int32_t x1_, int32_t y1_)
Construct an anti-aliased line shape.
Definition constixel.hpp:3265
Fluent API for drawing lines.
Definition constixel.hpp:3215
constexpr line & stroke(uint8_t col, int32_t stroke_width=1)
Draw the line.
Definition constixel.hpp:3239
constexpr line(image_type &image, int32_t x0_, int32_t y0_, int32_t x1_, int32_t y1_)
Construct a line shape.
Definition constixel.hpp:3229
Fluent API for drawing points.
Definition constixel.hpp:3287
constexpr point(image_type &image, int32_t x_, int32_t y_)
Construct a point shape.
Definition constixel.hpp:3299
constexpr point & plot(uint8_t col)
Plot the point with the specified color.
Definition constixel.hpp:3307
Fluent API for drawing rectangles.
Definition constixel.hpp:2948
constexpr auto fill_shader(const shader_func &shader) -> rect &
Fill the rectangle using a shader function.
Definition constixel.hpp:2982
constexpr rect & fill(uint8_t col)
Fill the rectangle with a solid color.
Definition constixel.hpp:2971
constexpr rect & stroke(uint8_t col, int32_t stroke_width=1)
Draw the rectangle outline.
Definition constixel.hpp:2995
constexpr rect(image_type &image, int32_t x_, int32_t y_, int32_t w_, int32_t h_)
Construct a rectangle shape.
Definition constixel.hpp:2962
Fluent API for drawing anti-aliased rounded rectangles.
Definition constixel.hpp:3155
constexpr auto fill_shader(const shader_func &shader) -> round_rect_aa &
Fill the rounded rectangle using a shader function.
Definition constixel.hpp:3190
constexpr round_rect_aa & stroke(uint8_t col, int32_t stroke_width=1)
Draw the rounded rectangle outline.
Definition constixel.hpp:3203
constexpr round_rect_aa(image_type &image, int32_t x_, int32_t y_, int32_t w_, int32_t h_, int32_t radius_)
Construct an anti-aliased rounded rectangle shape.
Definition constixel.hpp:3170
constexpr round_rect_aa & fill(uint8_t col)
Fill the rounded rectangle with a solid color.
Definition constixel.hpp:3179
Fluent API for drawing rounded rectangles.
Definition constixel.hpp:3108
constexpr round_rect & fill(uint8_t col)
Fill the rounded rectangle with a solid color.
Definition constixel.hpp:3132
constexpr round_rect & stroke(uint8_t col, int32_t stroke_width=1)
Draw the rounded rectangle outline.
Definition constixel.hpp:3143
constexpr round_rect(image_type &image, int32_t x_, int32_t y_, int32_t w_, int32_t h_, int32_t radius_)
Construct a rounded rectangle shape.
Definition constixel.hpp:3123
Fluent API for drawing anti-aliased text.
Definition constixel.hpp:3369
constexpr text_aa & color(uint8_t col)
Draw the text with the specified color.
Definition constixel.hpp:3404
constexpr int32_t draw(uint8_t col, size_t character_count=std::numeric_limits< size_t >::max(), size_t *character_actual=nullptr)
Draw the text and return the width.
Definition constixel.hpp:3394
constexpr text_aa(image_type &image, int32_t x_, int32_t y_, const char *str_)
Construct an anti-aliased text shape.
Definition constixel.hpp:3383
Fluent API for drawing centered anti-aliased text.
Definition constixel.hpp:3453
constexpr text_centered_aa(image_type &image, int32_t x_, int32_t y_, const char *str_)
Construct a centered anti-aliased text shape.
Definition constixel.hpp:3467
constexpr text_centered_aa & color(uint8_t col)
Draw the centered text with the specified color.
Definition constixel.hpp:3476
Fluent API for drawing centered monospace text.
Definition constixel.hpp:3417
constexpr text_centered_mono & color(uint8_t col)
Draw the centered text with the specified color.
Definition constixel.hpp:3440
constexpr text_centered_mono(image_type &image, int32_t x_, int32_t y_, const char *str_)
Construct a centered monospace text shape.
Definition constixel.hpp:3431
Fluent API for drawing monospace text.
Definition constixel.hpp:3320
constexpr int32_t draw(uint8_t col, size_t character_count=std::numeric_limits< size_t >::max(), size_t *character_actual=nullptr)
Draw the text and return the width.
Definition constixel.hpp:3345
constexpr text_mono & color(uint8_t col)
Draw the text with the specified color.
Definition constixel.hpp:3356
constexpr text_mono(image_type &image, int32_t x_, int32_t y_, const char *str_)
Construct a monospace text shape.
Definition constixel.hpp:3334
Fluent shape API classes for method chaining.
Definition constixel.hpp:2940
Definition constixel.hpp:2902
int32_t cx
Definition constixel.hpp:2903
int32_t cy
Definition constixel.hpp:2904
uint8_t col
Definition constixel.hpp:2906
int32_t r
Definition constixel.hpp:2905
int32_t sw
Definition constixel.hpp:2907
Definition constixel.hpp:2878
int32_t x0
Definition constixel.hpp:2879
uint8_t col
Definition constixel.hpp:2883
int32_t y0
Definition constixel.hpp:2880
float sw
Definition constixel.hpp:2884
int32_t y1
Definition constixel.hpp:2882
int32_t x1
Definition constixel.hpp:2881
Definition constixel.hpp:2890
int32_t sw
Definition constixel.hpp:2896
int32_t x
Definition constixel.hpp:2891
int32_t w
Definition constixel.hpp:2893
int32_t h
Definition constixel.hpp:2894
uint8_t col
Definition constixel.hpp:2895
int32_t y
Definition constixel.hpp:2892
Definition constixel.hpp:2914
int32_t r
Definition constixel.hpp:2919
int32_t w
Definition constixel.hpp:2917
int32_t x
Definition constixel.hpp:2915
uint8_t col
Definition constixel.hpp:2920
int32_t h
Definition constixel.hpp:2918
int32_t sw
Definition constixel.hpp:2921
int32_t y
Definition constixel.hpp:2916
Definition constixel.hpp:2927
uint8_t col
Definition constixel.hpp:2931
int32_t x
Definition constixel.hpp:2928
int32_t y
Definition constixel.hpp:2929
const char * str
Definition constixel.hpp:2930
Definition constixel.hpp:2869
uint8_t col
Definition constixel.hpp:2872
int32_t x
Definition constixel.hpp:2870
int32_t y
Definition constixel.hpp:2871
basic rectangle structure
Definition constixel.hpp:616
T x
x coordinate
Definition constixel.hpp:617
T h
height
Definition constixel.hpp:620
constexpr rect & operator&=(const rect &other)
intersects one rect with another
Definition constixel.hpp:642
constexpr rect operator&(const rect &other) const
intersects one rect with another
Definition constixel.hpp:624
T w
width
Definition constixel.hpp:619
T y
y coordinate
Definition constixel.hpp:618