constixel
Loading...
Searching...
No Matches
constixel.hpp
1/*
2MIT License
3
4Copyright (c) 2025 Tinic Uro
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24#ifndef CONSTIXEL_HPP_
25#define CONSTIXEL_HPP_
26
27#include <algorithm>
28#include <array>
29#include <bit>
30#include <cmath>
31#include <cstddef>
32#include <cstdint>
33#include <cstring>
34#include <iostream>
35#include <limits>
36#include <span>
37#include <string>
38#include <type_traits>
39#include <utility>
40#include <vector>
41
42#if defined(__ARM_NEON)
43#include <arm_neon.h>
44#endif // #if defined(__ARM_NEON)
45
46#if defined(__AVX2__)
47#include <immintrin.h>
48#endif // #if defined(__AVX2__)
49
50namespace constixel {
51
53namespace hidden {
54
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); // wrap x to [0, 2π)
57 double res = 1.0;
58 double term = 1.0;
59 const double x2 = x * x;
60 for (int32_t i = 1; i < terms; ++i) {
61 term *= -x2 / ((2 * i - 1) * (2 * i));
62 res += term;
63 }
64 return res;
65}
66
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); // wrap x to [0, 2π)
69 double res = x;
70 double term = x;
71 const double x2 = x * x;
72 for (int32_t i = 1; i < terms; ++i) {
73 term *= -x2 / ((2 * i) * (2 * i + 1));
74 res += term;
75 }
76 return res;
77}
78
79static constexpr float fast_sqrtf(const float x) {
80 auto i = std::bit_cast<int32_t>(x);
81 const int k = i & 0x00800000;
82 float y = 0.0f;
83 if (k != 0) {
84 i = 0x5ed9d098 - (i >> 1);
85 y = std::bit_cast<float>(i);
86 y = 2.33139729f * y * ((-x * y * y) + 1.07492042f);
87 } else {
88 i = 0x5f19d352 - (i >> 1);
89 y = std::bit_cast<float>(i);
90 y = 0.82420468f * y * ((-x * y * y) + 2.14996147f);
91 }
92 const float c = x * y;
93 const float r = ((y * -c) + 1.0f);
94 y = ((0.5f * c * r) + c);
95 return y;
96}
97
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)));
104}
105
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);
111}
112
113[[nodiscard]] static constexpr float fast_pow(const float x, const float p) {
114 return fast_exp2(p * fast_log2(x));
115}
116
117[[nodiscard]] static constexpr float linear_to_srgb(float c) {
118 if (c <= 0.0031308f) {
119 return 12.92f * c;
120 }
121 if (std::is_constant_evaluated()) {
122 return 1.055f * fast_pow(c, 1.0f / 2.4f) - 0.055f;
123 }
124 return 1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f;
125}
126
127[[nodiscard]] static constexpr float srgb_to_linear(float s) {
128 if (s <= 0.040449936f) {
129 return s / 12.92f;
130 }
131 if (std::is_constant_evaluated()) {
132 return fast_pow((s + 0.055f) / 1.055f, 2.4f);
133 }
134 return std::pow((s + 0.055f) / 1.055f, 2.4f);
135}
136
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)));
148}
149
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);
167}
168
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));
171}
172
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);
182}
183#endif // #if defined(__ARM_NEON)
184
185#if defined(__AVX2__)
186
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);
205}
206
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)); // 1/(1<<23)
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)));
219}
220
221inline __m128 fast_pow_ps(__m128 x, __m128 p) {
222 return fast_exp2_ps(_mm_mul_ps(p, fast_log2_ps(x)));
223}
224
225inline __m128 pow_1_over_2_4_ps(__m128 x) {
226 return fast_pow_ps(x, _mm_set1_ps(1.0f / 2.4f));
227}
228
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));
239}
240#endif // #if defined(__AVX2__)
241
242struct oklch {
243 double l = 0.0;
244 double c = 0.0;
245 double h = 0.0;
246};
247
248struct oklab {
249 double l = 0.0;
250 double a = 0.0;
251 double b = 0.0;
252};
253
254struct srgb {
255 double r = 0.0;
256 double g = 0.0;
257 double b = 0.0;
258};
259
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;
264
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;
268
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_;
272
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)))))};
276}
277
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)};
282}
283
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);
286
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));
291 }
292 return a2al;
293}
294
295static constexpr const std::array<float, 16> a2al_4bit = gen_a2al_4bit_consteval();
296
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));
301 }
302 return a2al;
303}
304
305static constexpr const std::array<float, 256> a2al_8bit = gen_a2al_8bit_consteval();
306
307template <size_t S>
308class quantize {
309 static constexpr size_t palette_size = S;
310
311 std::array<float, palette_size * 3> linearpal{};
312#if defined(__ARM_NEON)
313 // Note: ordering after linearpal critical for overreads
314 std::array<float, palette_size * 3> linearpal_neon{};
315#endif // #if defined(__ARM_NEON)
316#if defined(__AVX2__)
317 // Note: ordering after linearpal critical for overreads
318 alignas(32) std::array<float, palette_size * 3> linearpal_avx2{};
319#endif // #if defined(__ARM_NEON)
320
321 std::array<uint32_t, palette_size> pal{};
322
323 public:
324 ~quantize() = default;
325 quantize(const quantize &&) = delete;
326 quantize(const quantize &) = delete;
327 quantize &operator=(const quantize &) = delete;
328 quantize &operator=(quantize &&) = delete;
329
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);
334 }
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));
342 }
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);
350 }
351 }
352 }
353#endif // #if defined(__ARM_NEON)
354#if defined(__AVX2__)
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);
361 }
362 }
363 }
364#endif // #if defined(__AVX2__)
365 }
366
367 [[nodiscard]] constexpr uint8_t nearest(int32_t r, int32_t g, int32_t b) const {
368 int32_t best = 0;
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;
375 if (d < bestd) {
376 bestd = d;
377 best = static_cast<int32_t>(i);
378 }
379 }
380 return static_cast<uint8_t>(best);
381 }
382
383 [[nodiscard]] constexpr auto &palette() const {
384 return pal;
385 }
386
387 [[nodiscard]] constexpr auto &linear_palette() const {
388 return linearpal;
389 }
390
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);
398
399 float best = std::numeric_limits<float>::infinity();
400 std::size_t bestIdx = 0;
401
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);
406
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);
409#else
410 const float32x4_t dist = vaddq_f32(vaddq_f32(vmulq_f32(dr, dr), vmulq_f32(dg, dg)), vmulq_f32(db, db));
411#endif
412
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);
416 if (d0 < best) {
417 best = d0;
418 bestIdx = i;
419 if (d0 <= epsilon) {
420 break;
421 }
422 }
423 const float d1 = vget_lane_f32(lo, 1);
424 if (d1 < best) {
425 best = d1;
426 bestIdx = i + 1;
427 if (d1 <= epsilon) {
428 break;
429 }
430 }
431 const float d2 = vget_lane_f32(hi, 0);
432 if (d2 < best) {
433 best = d2;
434 bestIdx = i + 2;
435 if (d2 <= epsilon) {
436 break;
437 }
438 }
439 const float d3 = vget_lane_f32(hi, 1);
440 if (d3 < best) {
441 best = d3;
442 bestIdx = i + 3;
443 if (d3 <= epsilon) {
444 break;
445 }
446 }
447 }
448 return static_cast<uint8_t>(bestIdx);
449 }
450#endif // #if defined(__ARM_NEON)
451#if defined(__AVX2__)
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);
456
457 float best = std::numeric_limits<float>::infinity();
458 std::uint8_t bestIdx = 0;
459
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]);
464
465 __m256 dist =
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))));
469
470 alignas(32) float d[8];
471 _mm256_store_ps(d, dist);
472
473 for (size_t lane = 0; lane < 8; lane++) {
474 if (d[lane] < best) {
475 best = d[lane];
476 bestIdx = static_cast<uint8_t>(i + lane);
477 if (d[lane] <= epsilon) {
478 return static_cast<uint8_t>(bestIdx);
479 }
480 }
481 }
482 }
483
484 return static_cast<uint8_t>(bestIdx);
485 }
486#endif // #if defined(__AVX2__)
487 size_t best = 0;
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;
494 if (d < bestd) {
495 bestd = d;
496 best = i;
497 if (d <= epsilon) {
498 break;
499 }
500 }
501 }
502 return static_cast<uint8_t>(best);
503 }
504};
505
506} // namespace hidden
508
510template <size_t N, typename T>
511class hextree {
512 static constexpr T bitslices = ((sizeof(T) * 8) / 4) - 1;
513
514 static constexpr size_t child_nodes_n = 1UL << 4;
515 static constexpr size_t child_nodes_n_mask = child_nodes_n - 1;
516
517 struct node {
518 std::array<T, child_nodes_n> child{};
519 constexpr node() {
520 for (auto &c : child) {
521 c = invalid;
522 }
523 }
524 };
525
526 public:
527 static constexpr T invalid = std::numeric_limits<T>::max();
528
529 std::array<node, N> nodes{};
530
531 [[nodiscard]] consteval size_t byte_size() const {
532 return sizeof(node) * nodes.size();
533 }
534
535 ~hextree() = default;
536 hextree(const hextree &&) = delete;
537 hextree(const hextree &) = delete;
538 hextree &operator=(const hextree &) = delete;
539 hextree &operator=(hextree &&) = delete;
540
541 explicit consteval hextree() = default;
542 template <std::size_t NS>
543 explicit consteval hextree(const std::array<std::pair<T, T>, NS> &in) {
544 nodes[0] = node{};
545 T node_cnt = 1;
546 for (auto [key, val] : in) {
547 T idx = 0;
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) {
552 next = node_cnt;
553 nodes.at(idx).child[nib] = next;
554 node_cnt++;
555 }
556 idx = next;
557 }
558 nodes.at(idx).child[key & child_nodes_n_mask] = val;
559 }
560 }
561
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) {
567 T idx = 0;
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();
575 }
576 idx = next;
577 }
578 vnodes[idx].child[key & child_nodes_n_mask] = val;
579 }
580 return static_cast<T>(vnodes.size());
581 }
582
583 [[nodiscard]] constexpr T lookup(T key) const {
584 T idx = 0;
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) {
589 return invalid;
590 }
591 idx = next;
592 }
593 return nodes.at(idx).child[key & child_nodes_n_mask];
594 }
595};
597
599template <typename T>
600struct char_info {
601 T x = 0;
602 T y = 0;
603 T width = 0;
604 T height = 0;
605 T xadvance = 0;
606 T xoffset = 0;
607 T yoffset = 0;
608};
610
615template <typename T>
616struct rect {
617 T x = 0;
618 T y = 0;
619 T w = 0;
620 T h = 0;
621
624 constexpr rect operator&(const rect &other) const {
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);
629
630 T nw = x2 - x1;
631 T nh = y2 - y1;
632
633 if (nw <= 0 || nh <= 0) {
634 return {T{0}, T{0}, T{0}, T{0}};
635 }
636
637 return {x1, y1, nw, nh};
638 }
639
642 constexpr rect &operator&=(const rect &other) {
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);
647
648 T nw = x2 - x1;
649 T nh = y2 - y1;
650
651 if (nw <= 0 || nh <= 0) {
652 *this = {T{0}, T{0}, T{0}, T{0}};
653 return *this;
654 }
655
656 x = x1;
657 y = y1;
658 w = nw;
659 h = nh;
660
661 return *this;
662 }
663};
664
666class format {
667 public:
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;
674 }
675 return ((adler32_s2 << 16) | adler32_s1);
676 }
677
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));
684 }
685
686 template <typename F, typename A>
687 static constexpr void png_write_crc32(F &&char_out, const A &array, size_t bytes) {
688 size_t idx = 0;
689 uint32_t crc = 0xFFFFFFFF;
690 for (size_t c = 0; c < bytes; c++) {
691 auto d = static_cast<uint8_t>(array.at(idx++));
692 crc ^= d;
693 for (int i = 0; i < 8; ++i) {
694 crc = (crc & 1) ? (crc >> 1) ^ 0xEDB88320u : crc >> 1;
695 }
696 }
697 png_write_be(std::forward<F>(char_out), crc ^ 0xFFFFFFFF);
698 }
699
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)));
704 }
705 }
706
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);
717 }
718
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{};
723 size_t i = 0;
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);
736 if (depth <= 8) {
737 header.at(i++) = static_cast<char>(depth);
738 header.at(i++) = 3;
739 } else if (depth == 24) {
740 header.at(i++) = 8;
741 header.at(i++) = 2;
742 } else {
743 header.at(i++) = 8;
744 header.at(i++) = 6;
745 }
746 header.at(i++) = 0;
747 header.at(i++) = 0;
748 header.at(i++) = 0;
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);
752 }
753
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{};
757 size_t i = 0;
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);
766 }
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);
770 }
771
772 template <typename F>
773 static constexpr void png_end(F &&char_out) {
774 std::array<char, 4> header{};
775 size_t i = 0;
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);
783 }
784
785 template <typename F>
786 static constexpr void png_idat_zlib_header(F &&char_out) {
787 std::array<char, 6> header{};
788 size_t i = 0;
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);
798 }
799
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;
808 while (bytes > 0) {
809 size_t i = 0;
810 header.at(i++) = 'I';
811 header.at(i++) = 'D';
812 header.at(i++) = 'A';
813 header.at(i++) = 'T';
814
815 const size_t bytes_to_copy = std::min(max_data_use, bytes);
816
817 header.at(i++) = ((bytes_to_copy == bytes) && last_line) ? 0x01 : 0x00;
818
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);
823
824 const size_t adlersum32_start_pos = i;
825 if (filter_first != 0) {
826 filter_first = 0;
827 header.at(i++) = 0;
828 }
829 for (size_t d = 0; d < bytes_to_copy; d++) {
830 header.at(i++) = *line++;
831 }
832 adler32_sum = adler32(&header[adlersum32_start_pos], i - adlersum32_start_pos, adler32_sum);
833
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);
837
838 bytes -= bytes_to_copy;
839 }
840
841 return adler32_sum;
842 }
843
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{};
847 size_t i = 0;
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);
859 }
860
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');
866 }
867
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);
878 }
879
880 template <typename F>
881 static constexpr void sixel_number(F &&char_out, uint16_t u) {
882 if (u < 10) {
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)));
896 } else {
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)));
902 }
903 }
904
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));
914 if (c < 2) {
915 std::forward<F>(char_out)(';');
916 }
917 }
918 }
919
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)('\\');
924 }
925
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);
931 }
932
933 constexpr void clear() {
934 set.fill(0);
935 }
936
937 [[nodiscard]] constexpr size_t genstack(std::array<PBT, PBS> &stack) const {
938 size_t count = 0;
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);
943 }
944 }
945 }
946 return count;
947 }
948
949 std::array<uint32_t, PBS / 32> set{};
950 static constexpr size_t set_idx_mask = (1UL << sizeof(set) / 4) - 1;
951 };
952
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);
959 }
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++) {
963 size_t bpl = 0;
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);
966 }
967 png_idat_zlib_trailer(std::forward<F>(char_out), adler32_sum);
968 png_end(std::forward<F>(char_out));
969 }
970
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]);
978 }
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) {
984 pset.clear();
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];
989 if (col != 0) {
990 std::forward<F>(char_out)('$');
991 }
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))) {
999 repeat_count++;
1000 continue;
1001 }
1002 break;
1003 }
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));
1007 x += repeat_count;
1008 }
1009 std::forward<F>(char_out)(static_cast<char>('?' + bits6));
1010 }
1011 }
1012 std::forward<F>(char_out)('-');
1013 }
1014 sixel_end(std::forward<F>(char_out));
1015 }
1016};
1018
1029template <size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
1030class format_1bit : public format {
1031 public:
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;
1037
1038 static consteval auto gen_palette_consteval() {
1039 return std::array<uint32_t, (1UL << bits_per_pixel)>({{0x00000000, 0x00ffffff}});
1040 }
1041 static constexpr const auto quant = hidden::quantize<1UL << bits_per_pixel>(gen_palette_consteval());
1042
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);
1047 return b;
1048 }
1049
1050 static constexpr void transpose8x8(std::array<uint8_t, 8> &a) {
1051 // clang-format off
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]));
1060
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);
1065
1066 t = ((y >> 4) & 0x0F0F0F0F) | (x & 0xF0F0F0F0);
1067 y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
1068
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);
1077 // clang-format on
1078 }
1079
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];
1091 } else {
1092 tmp[c] = src[(y * 8 + c) * src_stride + x];
1093 }
1094 }
1095 for (size_t c = xl; c < 8; c++) {
1096 tmp[c] = 0;
1097 }
1098 transpose8x8(tmp);
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];
1103 } else {
1104 dst[(x * 8 + c) * dst_stride + y] = tmp[c];
1105 }
1106 }
1107 }
1108 }
1109 }
1110
1111 static constexpr void compose(std::span<uint8_t, image_size> & /*data*/, size_t /*x*/, size_t /*y*/, float /*cola*/,
1112 float /*colr*/, float /*colg*/, float /*colb*/) {
1113#ifndef _MSC_VER
1114 static_assert(false, "composing not supported on 1-bit format, use a mono font.");
1115#endif // #ifndef _MSC_VER
1116 }
1117
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;
1121 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));
1125 }
1126
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;
1130 xl0 %= 8;
1131 const size_t xr8 = xr0 / 8;
1132 xr0 %= 8;
1133 const size_t xs8 = xr8 - xl8;
1134 const auto c8 =
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];
1141 if (xs8 > 0) {
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++) {
1145 yptr[x] = c8;
1146 }
1147 if (xr0 != 0) {
1148 yptr[xr8] &= static_cast<uint8_t>(~mr[xr0]);
1149 yptr[xr8] |= static_cast<uint8_t>(mr[xr0] & c8);
1150 }
1151 } else {
1152 yptr[xl8] &= static_cast<uint8_t>(~(ml[xl0] & mr[xr0]));
1153 yptr[xl8] |= static_cast<uint8_t>((ml[xl0] & mr[xr0] & c8));
1154 }
1155 }
1156
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);
1161 }
1162
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);
1170 }
1171 ptr += bytes_per_line;
1172 }
1173 }
1174
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);
1185 }
1186 ptr += bytes_per_line;
1187 }
1188 }
1189
1190 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data, const rect<int32_t> &r, const uint8_t *ptr,
1191 int32_t stride) {
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);
1201 }
1202 }
1203 }
1204
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++) {
1210 int32_t err = 0;
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},
1219 int32_t{0xFF * 6});
1220 }
1221 }
1222 }
1223
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++) {
1229 float err_r = 0;
1230 float err_g = 0;
1231 float err_b = 0;
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);
1239 Rl = Rl + err_r;
1240 Gl = Gl + err_g;
1241 Bl = Bl + err_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);
1248 }
1249 }
1250 }
1251
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;
1258 });
1259 }
1260
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;
1268 uint8_t out = 0;
1269 size_t inc = y % S;
1270 for (size_t y6 = 0; y6 < 6; y6++) {
1271 out >>= 1;
1272 if ((y + y6) < H * S) {
1273 out |= static_cast<uint8_t>((static_cast<uint8_t>(((*ptr) >> (7 - x8)) & 1) == col) ? (1UL << 5)
1274 : 0);
1275 if ((y + y6) != ((H * S) - 1)) {
1276 if (++inc >= S) {
1277 inc = 0;
1278 ptr += bytes_per_line;
1279 }
1280 }
1281 }
1282 }
1283 return out;
1284 },
1285 [](const uint8_t *data_raw, size_t x, size_t w, size_t y, palette_bitset<uint8_t, 32> &set) {
1286 size_t inc = y % S;
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));
1293 }
1294 if (++inc >= S) {
1295 inc = 0;
1296 }
1297 }
1298 }
1299 });
1300 }
1302};
1303
1315template <size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
1316class format_2bit : public format {
1317 public:
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() {
1324 if (GRAYSCALE) {
1325 return std::array<uint32_t, (1UL << bits_per_pixel)>({{0x000000, 0x444444, 0x888888, 0xffffff}});
1326 }
1327 return std::array<uint32_t, (1UL << bits_per_pixel)>({{0x000000, 0xffffff, 0xff0000, 0x0077ff}});
1328 }
1329 static constexpr const auto quant = hidden::quantize<1UL << bits_per_pixel>(gen_palette_consteval());
1330
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);
1334 return b;
1335 }
1336
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);
1343 data[j] =
1344 static_cast<uint8_t>((data[j] & ~(0x03U << ishift)) | (static_cast<uint32_t>(pixel) << ishift));
1345 }
1346 }
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);
1352 data[j + 4] =
1353 static_cast<uint8_t>((data[j + 4] & ~(0x03U << ishift)) | (static_cast<uint32_t>(pixel) << ishift));
1354 }
1355 }
1356 }
1357
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;
1365
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;
1370
1371 dst[dst_byte] &= ~(0x03 << dst_shift);
1372 dst[dst_byte] |= pixel << dst_shift;
1373 }
1374 }
1375 }
1376
1377 static constexpr void compose(std::span<uint8_t, image_size> & /*data*/, size_t /*x*/, size_t /*y*/, float /*cola*/,
1378 float /*colr*/, float /*colg*/, float /*colb*/) {
1379#ifndef _MSC_VER
1380 static_assert(false, "composing not supported on 2-bit format, use a mono font.");
1381#endif // #ifndef _MSC_VER
1382 }
1383
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;
1387 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));
1391 }
1392
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;
1396 xl0 %= 4;
1397 const size_t xr4 = xr0 / 4;
1398 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];
1404 if (xs4 > 0) {
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++) {
1408 yptr[x] = c4;
1409 }
1410 if (xr0 != 0) {
1411 yptr[xr4] &= static_cast<uint8_t>(~mr[xr0]);
1412 yptr[xr4] |= static_cast<uint8_t>(mr[xr0] & c4);
1413 }
1414 } else {
1415 yptr[xl4] &= static_cast<uint8_t>(~(ml[xl0] & mr[xr0]));
1416 yptr[xl4] |= static_cast<uint8_t>(ml[xl0] & mr[xr0] & c4);
1417 }
1418 }
1419
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);
1424 }
1425
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);
1433 }
1434 ptr += bytes_per_line;
1435 }
1436 }
1437
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);
1448 }
1449 ptr += bytes_per_line;
1450 }
1451 }
1452
1453 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data, const rect<int32_t> &r, const uint8_t *ptr,
1454 int32_t stride) {
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]));
1463 }
1464 }
1465 }
1466
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++) {
1472 int32_t err_r = 0;
1473 int32_t err_g = 0;
1474 int32_t err_b = 0;
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];
1479 R = R + err_r;
1480 G = G + err_g;
1481 B = B + err_b;
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},
1485 int32_t{255});
1486 err_g = std::clamp(G - static_cast<int32_t>((quant.palette().at(n) >> 8) & 0xFF), int32_t{-255},
1487 int32_t{255});
1488 err_b = std::clamp(B - static_cast<int32_t>((quant.palette().at(n) >> 16) & 0xFF), int32_t{-255},
1489 int32_t{255});
1490 }
1491 }
1492 }
1493
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++) {
1499 float err_r = 0;
1500 float err_g = 0;
1501 float err_b = 0;
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);
1509 Rl = Rl + err_r;
1510 Gl = Gl + err_g;
1511 Bl = Bl + err_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);
1517 }
1518 }
1519 }
1520
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;
1527 });
1528 }
1529
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;
1537 uint8_t out = 0;
1538 size_t inc = y % S;
1539 for (size_t y6 = 0; y6 < 6; y6++) {
1540 out >>= 1;
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)) {
1545 if (++inc >= S) {
1546 inc = 0;
1547 ptr += bytes_per_line;
1548 }
1549 }
1550 }
1551 }
1552 return out;
1553 },
1554 [](const uint8_t *data_raw, size_t x, size_t w, size_t y, palette_bitset<uint8_t, 32> &set) {
1555 size_t inc = y % S;
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));
1562 }
1563 if (++inc >= S) {
1564 inc = 0;
1565 }
1566 }
1567 }
1568 });
1569 }
1571};
1572
1584template <size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
1585class format_4bit : public format {
1586 public:
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;
1592
1593 static consteval auto gen_palette_consteval() {
1594 if (GRAYSCALE) {
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}});
1598 }
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}});
1602 }
1603 static constexpr const auto quant = hidden::quantize<1UL << bits_per_pixel>(gen_palette_consteval());
1604
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);
1607 return b;
1608 }
1609
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);
1616 data[j] =
1617 static_cast<uint8_t>((data[j] & ~(0x0FU << ishift)) | (static_cast<uint32_t>(pixel) << ishift));
1618 }
1619 }
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);
1625 data[j + 2] =
1626 static_cast<uint8_t>((data[j + 2] & ~(0x0FU << ishift)) | (static_cast<uint32_t>(pixel) << ishift));
1627 }
1628 }
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);
1634 data[j + 4] =
1635 static_cast<uint8_t>((data[j + 4] & ~(0x0FU << ishift)) | (static_cast<uint32_t>(pixel) << ishift));
1636 }
1637 }
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);
1643 data[j + 6] =
1644 static_cast<uint8_t>((data[j + 6] & ~(0x0FU << ishift)) | (static_cast<uint32_t>(pixel) << ishift));
1645 }
1646 }
1647 }
1648
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;
1656
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;
1661
1662 dst[dst_byte] &= ~(0x0F << dst_shift);
1663 dst[dst_byte] |= pixel << dst_shift;
1664 }
1665 }
1666 }
1667
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));
1675 }
1676
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;
1680 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));
1684 }
1685
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;
1689 xl0 %= 2;
1690 const size_t xr2 = xr0 / 2;
1691 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];
1697 if (xs2 > 0) {
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++) {
1701 yptr[x] = c2;
1702 }
1703 if (xr0 != 0) {
1704 yptr[xr2] &= static_cast<uint8_t>(~mr[xr0]);
1705 yptr[xr2] |= static_cast<uint8_t>(mr[xr0] & c2);
1706 }
1707 } else {
1708 yptr[xl2] &= static_cast<uint8_t>(~(ml[xl0] & mr[xr0]));
1709 yptr[xl2] |= static_cast<uint8_t>(ml[xl0] & mr[xr0] & c2);
1710 }
1711 }
1712
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);
1717 }
1718
1719 [[nodiscard]] static constexpr uint8_t get_col(const std::span<const uint8_t, image_size> data, size_t x,
1720 size_t y) {
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);
1725 }
1726
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);
1734 }
1735 ptr += bytes_per_line;
1736 }
1737 }
1738
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);
1749 }
1750 ptr += bytes_per_line;
1751 }
1752 }
1753
1754 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data, const rect<int32_t> &r, const uint8_t *ptr,
1755 int32_t stride) {
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]));
1764 }
1765 }
1766 }
1767
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++) {
1773 int32_t err_r = 0;
1774 int32_t err_g = 0;
1775 int32_t err_b = 0;
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];
1780 R = R + err_r;
1781 G = G + err_g;
1782 B = B + err_b;
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},
1786 int32_t{255});
1787 err_g = std::clamp(G - static_cast<int32_t>((quant.palette().at(n) >> 8) & 0xFF), int32_t{-255},
1788 int32_t{255});
1789 err_b = std::clamp(B - static_cast<int32_t>((quant.palette().at(n) >> 16) & 0xFF), int32_t{-255},
1790 int32_t{255});
1791 }
1792 }
1793 }
1794
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++) {
1800 float err_r = 0;
1801 float err_g = 0;
1802 float err_b = 0;
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);
1810 Rl = Rl + err_r;
1811 Gl = Gl + err_g;
1812 Bl = Bl + err_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);
1818 }
1819 }
1820 }
1821
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;
1828 });
1829 }
1830
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;
1838 uint8_t out = 0;
1839 size_t inc = y % S;
1840 for (size_t y6 = 0; y6 < 6; y6++) {
1841 out >>= 1;
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)) {
1846 if (++inc >= S) {
1847 inc = 0;
1848 ptr += bytes_per_line;
1849 }
1850 }
1851 }
1852 }
1853 return out;
1854 },
1855 [](const uint8_t *data_raw, size_t x, size_t w, size_t y, palette_bitset<uint8_t, 32> &set) {
1856 size_t inc = y % S;
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));
1863 }
1864 if (++inc >= S) {
1865 inc = 0;
1866 }
1867 }
1868 }
1869 });
1870 }
1872};
1873
1885template <size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
1886class format_8bit : public format {
1887 public:
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;
1893
1894 static consteval auto gen_palette_consteval() {
1895 std::array<uint32_t, (1UL << bits_per_pixel)> pal{};
1896 if (GRAYSCALE) {
1897 for (size_t c = 0; c < 256; c++) {
1898 pal[c] = static_cast<uint32_t>((c << 16) | (c << 8) | c);
1899 }
1900 } else {
1901 pal[0] = 0x000000;
1902 pal[1] = 0xffffff;
1903 pal[2] = 0xff0000;
1904 pal[3] = 0x00ff00;
1905 pal[4] = 0x0000ff;
1906 pal[5] = 0xffff00;
1907 pal[6] = 0x00ffff;
1908 pal[7] = 0xff00ff;
1909 pal[8] = 0x333333;
1910 pal[9] = 0x666666;
1911 pal[10] = 0x999999;
1912 pal[11] = 0xcccccc;
1913 pal[12] = 0x7f0000;
1914 pal[13] = 0x007f00;
1915 pal[14] = 0x00007f;
1916 pal[15] = 0x7f7f00;
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);
1920 }
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);
1936 }
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),
1945 }));
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);
1950 }
1951 }
1952 }
1953 return pal;
1954 }
1955 static constexpr const auto quant = hidden::quantize<1UL << bits_per_pixel>(gen_palette_consteval());
1956
1957 static constexpr uint8_t reverse(uint8_t b) {
1958 return b;
1959 }
1960
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++;
1968 } else {
1969 dst[(W - x - 1) * H + y] = *src++;
1970 }
1971 } else {
1972 if constexpr (FLIP_V) {
1973 dst[x * H + (H - y - 1)] = *src++;
1974 } else {
1975 dst[x * H + y] = *src++;
1976 }
1977 }
1978 }
1979 }
1980 }
1981
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;
1984 }
1985
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++) {
1989 yptr[x] = col;
1990 }
1991 }
1992
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) {
1995 // clang-format off
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};
2002 // Note: this reads 1 float into linearpal_neon
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]));
2008 } else
2009#endif // #if defined(__ARM_NEON)
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]);
2016 // Note: this reads 1 float into linearpal_avx2
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]));
2022 } else
2023#endif // #if defined(__AVX2__)
2024 {
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));
2030 }
2031 // clang-format on
2032 }
2033
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);
2040 }
2041 ptr += bytes_per_line;
2042 }
2043 }
2044
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);
2055 }
2056 ptr += bytes_per_line;
2057 }
2058 }
2059
2060 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data, const rect<int32_t> &r, const uint8_t *ptr,
2061 int32_t stride) {
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]);
2070 }
2071 }
2072 }
2073
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++) {
2079 int32_t err_r = 0;
2080 int32_t err_g = 0;
2081 int32_t err_b = 0;
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];
2086 R = R + err_r;
2087 G = G + err_g;
2088 B = B + err_b;
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},
2092 int32_t{255});
2093 err_g = std::clamp(G - static_cast<int32_t>((quant.palette().at(n) >> 8) & 0xFF), int32_t{-255},
2094 int32_t{255});
2095 err_b = std::clamp(B - static_cast<int32_t>((quant.palette().at(n) >> 16) & 0xFF), int32_t{-255},
2096 int32_t{255});
2097 }
2098 }
2099 }
2100
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++) {
2106 float err_r = 0;
2107 float err_g = 0;
2108 float err_b = 0;
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);
2116 Rl = Rl + err_r;
2117 Gl = Gl + err_g;
2118 Bl = Bl + err_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);
2124 }
2125 }
2126 }
2127
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;
2134 });
2135 }
2136
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];
2143 uint8_t out = 0;
2144 size_t inc = y % S;
2145 for (size_t y6 = 0; y6 < 6; y6++) {
2146 out >>= 1;
2147 if ((y + y6) < H * S) {
2148 out |= static_cast<uint8_t>((*ptr == col) ? (1UL << 5) : 0);
2149 if ((y + y6) != ((H * S) - 1)) {
2150 if (++inc >= S) {
2151 inc = 0;
2152 ptr += bytes_per_line;
2153 }
2154 }
2155 }
2156 }
2157 return out;
2158 },
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];
2162 size_t inc = y % 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]);
2167 }
2168 if ((y + y6) != ((H * S) - 1)) {
2169 if (++inc >= S) {
2170 inc = 0;
2171 ptr += bytes_per_line;
2172 }
2173 }
2174 }
2175 }
2176 });
2177 }
2179};
2180
2192template <size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
2193class format_24bit : public format {
2194 public:
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;
2200
2201 static consteval auto gen_palette_consteval() {
2203 }
2204 static constexpr const auto quant = hidden::quantize<1UL << 8>(gen_palette_consteval());
2205
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++;
2215 } else {
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++;
2219 }
2220 } else {
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++;
2225 } else {
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++;
2229 }
2230 }
2231 }
2232 }
2233 }
2234
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;
2240 }
2241
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;
2249 }
2250 }
2251
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);
2263
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);
2267
2268 uint8x8_t out;
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);
2273 return;
2274 }
2275#endif // #if defined(__ARM_NEON)
2276#if defined(__AVX2__)
2277 if (!std::is_constant_evaluated()) {
2278 const size_t off = y * bytes_per_line + x * 3;
2279
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;
2284
2285 const float as = cola + la * (1.0f - cola);
2286 const float inv_cola = 1.0f - cola;
2287
2288 __m128 dst = _mm_set_ps(0.0f, lb, lg, lr);
2289 __m128 src = _mm_set_ps(0.0f, colb, colg, colr);
2290
2291 __m128 blended = _mm_add_ps(_mm_mul_ps(src, _mm_set1_ps(cola)), _mm_mul_ps(dst, _mm_set1_ps(inv_cola)));
2292
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));
2296
2297 __m128 scaled = _mm_mul_ps(blended, inv_as);
2298 __m128 srgb = hidden::linear_to_srgb_approx_sse(scaled);
2299
2300 alignas(16) float out[4];
2301 _mm_store_ps(out, srgb);
2302
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);
2306 return;
2307 }
2308#endif // #if defined(__AVX2__)
2309 const size_t off = y * bytes_per_line + x * 3;
2310
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;
2315
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);
2320
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);
2324 }
2325
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);
2333 }
2334 }
2335 }
2336
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;
2347 }
2348 src_ptr += bytes_per_line;
2349 dst_ptr += W * 4;
2350 }
2351 }
2352
2353 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data, const rect<int32_t> &r, const uint8_t *ptr,
2354 int32_t stride) {
2355 rect<int32_t> intersect_rect{.x = 0, .y = 0, .w = W, .h = H};
2356 intersect_rect &= rect<int32_t>{.x = r.x, .y = r.y, .w = r.w, .h = r.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];
2368 }
2369 dst += bytes_per_line;
2370 src += stride;
2371 }
2372 }
2373
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);
2377 }
2378
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);
2382 }
2383
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;
2390 });
2391 }
2392
2393 template <size_t S, typename F>
2394 static constexpr void sixel(const std::span<const uint8_t, image_size> & /*data*/, F && /*char_out*/,
2395 const rect<int32_t> & /*r*/) {
2396#ifndef _MSC_VER
2397 static_assert(false, "Sixel not available for format_24bit.");
2398#endif // #ifndef _MSC_VER
2399 }
2401};
2402
2414template <size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
2415class format_32bit : public format {
2416 public:
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;
2422
2423 static consteval auto gen_palette_consteval() {
2425 }
2426 static constexpr const auto quant = hidden::quantize<1UL << 8>(gen_palette_consteval());
2427
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++;
2438 } else {
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++;
2443 }
2444 } else {
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++;
2450 } else {
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++;
2455 }
2456 }
2457 }
2458 }
2459 }
2460
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;
2467 ptr[3] = 0xFF;
2468 } else {
2469 uint32_t *yptr = reinterpret_cast<uint32_t *>(&data.data()[y * bytes_per_line + x * 4]);
2470 *yptr = quant.palette().at(col) | 0xFF000000;
2471 }
2472 }
2473
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;
2482 *yptr++ = 0xFF;
2483 }
2484 } else {
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++) {
2488 *yptr++ = rgba;
2489 }
2490 }
2491 }
2492
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);
2505
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);
2509
2510 uint8x8_t out;
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);
2515
2516 vst1_lane_u32(reinterpret_cast<uint32_t *>(&data[off]), vreinterpret_u32_u8(out), 0);
2517 return;
2518 }
2519#endif // #if defined(__ARM_NEON)
2520#if defined(__AVX2__)
2521 if (!std::is_constant_evaluated()) {
2522 const size_t off = y * bytes_per_line + x * 4;
2523
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);
2528
2529 const float as = cola + la * (1.0f - cola);
2530 const float inv_cola = 1.0f - cola;
2531
2532 __m128 dst = _mm_set_ps(0.0f, lb, lg, lr);
2533 __m128 src = _mm_set_ps(0.0f, colb, colg, colr);
2534
2535 __m128 blended = _mm_add_ps(_mm_mul_ps(src, _mm_set1_ps(cola)), _mm_mul_ps(dst, _mm_set1_ps(inv_cola)));
2536
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));
2540
2541 __m128 scaled = _mm_mul_ps(blended, inv_as);
2542 __m128 srgb = hidden::linear_to_srgb_approx_sse(scaled);
2543
2544 alignas(16) float out[4];
2545 _mm_store_ps(out, srgb);
2546
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);
2551 return;
2552 }
2553#endif // #if defined(__AVX2__)
2554 const size_t off = y * bytes_per_line + x * 4;
2555
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);
2560
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);
2565
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);
2570 }
2571
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));
2581 }
2582 }
2583 } else {
2584 std::memcpy(dst.data(), src.data(), src.size());
2585 }
2586 }
2587
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];
2593 }
2594 } else {
2595 std::memcpy(dst.data(), src.data(), src.size());
2596 }
2597 }
2598
2599 static constexpr void blit_RGBA(std::span<uint8_t, image_size> data, const rect<int32_t> &r, const uint8_t *ptr,
2600 int32_t stride) {
2601 rect<int32_t> intersect_rect{.x = 0, .y = 0, .w = W, .h = H};
2602 intersect_rect &= rect<int32_t>{.x = r.x, .y = r.y, .w = r.w, .h = r.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;
2612 src += stride;
2613 }
2614 }
2615
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);
2619 }
2620
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);
2624 }
2625
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;
2632 });
2633 }
2634
2635 template <size_t S, typename F>
2636 static constexpr void sixel(const std::span<const uint8_t, image_size> & /*data*/, F && /*char_out*/,
2637 const rect<int32_t> & /*r*/) {
2638#ifndef _MSC_VER
2639 static_assert(false, "Sixel not available for format_32bit.");
2640#endif // #ifndef _MSC_VER
2641 }
2643};
2644
2650enum color : uint8_t {
2651 // Basic colors (0-15)
2652 BLACK = 0,
2653 TRANSPARENT = 0,
2654 WHITE = 1,
2655 RED = 2,
2656 LIME = 3,
2657 BLUE = 4,
2658 YELLOW = 5,
2659 CYAN = 6,
2660 MAGENTA = 7,
2661 GRAY_20 = 8,
2662 GRAY_40 = 9,
2663 GRAY_60 = 10,
2664 GRAY_80 = 11,
2665 MAROON = 12,
2666 GREEN = 13,
2667 NAVY = 14,
2668 OLIVE = 15,
2669
2670 // Gray ramp (16-31)
2671 BLACK_OPAQUE = 16,
2672 GRAY_10 = 17,
2673 GRAY_11 = 18,
2674 GRAY_20_ALT = 19,
2675 GRAY_30 = 20,
2676 GRAY_33 = 21,
2677 GRAY_40_ALT = 22,
2678 GRAY_47 = 23,
2679 GRAY_53 = 24,
2680 GRAY_60_ALT = 25,
2681 DARK_GRAY = 26,
2682 SILVER = 27,
2683 GRAY_80_ALT = 28,
2684 GAINSBORO = 29,
2685 LIGHT_GRAY = 30,
2686 WHITE_ALT = 31,
2687
2688 // Red luminance ramp (32-47)
2689 RED_DARK_1 = 32,
2690 RED_DARK_2 = 33,
2691 RED_DARK_3 = 34,
2692 RED_MEDIUM_1 = 35,
2693 RED_MEDIUM_2 = 36,
2694 RED_BRIGHT_1 = 37,
2695 RED_BRIGHT_2 = 38,
2696 RED_FULL = 39,
2697 RED_PINK_1 = 40,
2698 RED_PINK_2 = 41,
2699 RED_PINK_3 = 42,
2700 RED_PINK_4 = 43,
2701 RED_PINK_5 = 44,
2702 RED_PINK_6 = 45,
2703 RED_PALE_1 = 46,
2704 RED_WHITE = 47,
2705
2706 // Green luminance ramp (48-63)
2707 GREEN_DARK_1 = 48,
2708 GREEN_DARK_2 = 49,
2709 GREEN_DARK_3 = 50,
2710 GREEN_MEDIUM_1 = 51,
2711 GREEN_MEDIUM_2 = 52,
2712 GREEN_BRIGHT_1 = 53,
2713 GREEN_BRIGHT_2 = 54,
2714 GREEN_FULL = 55,
2715 GREEN_LIGHT_1 = 56,
2716 GREEN_LIGHT_2 = 57,
2717 GREEN_LIGHT_3 = 58,
2718 GREEN_LIGHT_4 = 59,
2719 GREEN_PALE_1 = 60,
2720 GREEN_PALE_2 = 61,
2721 GREEN_PALE_3 = 62,
2722 GREEN_WHITE = 63,
2723
2724 // Blue luminance ramp (64-79)
2725 BLUE_DARK_1 = 64,
2726 BLUE_DARK_2 = 65,
2727 BLUE_DARK_3 = 66,
2728 BLUE_MEDIUM_1 = 67,
2729 BLUE_MEDIUM_2 = 68,
2730 BLUE_BRIGHT_1 = 69,
2731 BLUE_BRIGHT_2 = 70,
2732 BLUE_FULL = 71,
2733 BLUE_LIGHT_1 = 72,
2734 BLUE_LIGHT_2 = 73,
2735 BLUE_LIGHT_3 = 74,
2736 BLUE_LIGHT_4 = 75,
2737 BLUE_PALE_1 = 76,
2738 BLUE_PALE_2 = 77,
2739 BLUE_PALE_3 = 78,
2740 BLUE_WHITE = 79,
2741
2742 // Yellow luminance ramp (80-95)
2743 YELLOW_DARK_1 = 80,
2744 YELLOW_DARK_2 = 81,
2745 YELLOW_DARK_3 = 82,
2746 YELLOW_MEDIUM_1 = 83,
2747 YELLOW_MEDIUM_2 = 84,
2748 YELLOW_BRIGHT_1 = 85,
2749 YELLOW_BRIGHT_2 = 86,
2750 YELLOW_FULL = 87,
2751 YELLOW_LIGHT_1 = 88,
2752 YELLOW_LIGHT_2 = 89,
2753 YELLOW_LIGHT_3 = 90,
2754 YELLOW_LIGHT_4 = 91,
2755 YELLOW_PALE_1 = 92,
2756 YELLOW_PALE_2 = 93,
2757 YELLOW_PALE_3 = 94,
2758 YELLOW_WHITE = 95,
2759
2760 // Cyan luminance ramp (96-111)
2761 CYAN_DARK_1 = 96,
2762 CYAN_DARK_2 = 97,
2763 CYAN_DARK_3 = 98,
2764 CYAN_MEDIUM_1 = 99,
2765 CYAN_MEDIUM_2 = 100,
2766 CYAN_BRIGHT_1 = 101,
2767 CYAN_BRIGHT_2 = 102,
2768 CYAN_FULL = 103,
2769 CYAN_LIGHT_1 = 104,
2770 CYAN_LIGHT_2 = 105,
2771 CYAN_LIGHT_3 = 106,
2772 CYAN_LIGHT_4 = 107,
2773 CYAN_PALE_1 = 108,
2774 CYAN_PALE_2 = 109,
2775 CYAN_PALE_3 = 110,
2776 CYAN_WHITE = 111,
2777
2778 // Magenta luminance ramp (112-127)
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,
2786 MAGENTA_FULL = 119,
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,
2795
2796 DARK_RED = 12,
2797 DARK_GREEN = 13,
2798 DARK_BLUE = 14,
2799 ORANGE = 38,
2800 PURPLE = 115,
2801 PINK = 125,
2802 BROWN = 83,
2803 TEAL = 99,
2804
2805 GRAY_RAMP_START = 16,
2806 GRAY_RAMP_COUNT = 16,
2807 GRAY_RAMP_STOP = GRAY_RAMP_START + 15,
2808
2809 RED_LUMA_RAMP_START = 32,
2810 RED_LUMA_RAMP_COUNT = 16,
2811 RED_LUMA_RAMP_STOP = RED_LUMA_RAMP_START + 15,
2812
2813 GREEN_LUMA_RAMP_START = 48,
2814 GREEN_LUMA_RAMP_COUNT = 16,
2815 GREEN_LUMA_RAMP_STOP = GREEN_LUMA_RAMP_START + 15,
2816
2817 BLUE_LUMA_RAMP_START = 64,
2818 BLUE_LUMA_RAMP_COUNT = 16,
2819 BLUE_LUMA_RAMP_STOP = BLUE_LUMA_RAMP_START + 15,
2820
2821 YELLOW_LUMA_RAMP_START = 80,
2822 YELLOW_LUMA_RAMP_COUNT = 16,
2823 YELLOW_LUMA_RAMP_STOP = YELLOW_LUMA_RAMP_START + 15,
2824
2825 CYAN_LUMA_RAMP_START = 96,
2826 CYAN_LUMA_RAMP_COUNT = 16,
2827 CYAN_LUMA_RAMP_STOP = CYAN_LUMA_RAMP_START + 15,
2828
2829 MAGENTA_LUMA_RAMP_START = 112,
2830 MAGENTA_LUMA_RAMP_COUNT = 16,
2831 MAGENTA_LUMA_RAMP_STOP = MAGENTA_LUMA_RAMP_START + 15,
2832
2833 OKLCH_RAMP_START = 128,
2834 OKLCH_RAMP_COUNT = 128,
2835 OKLCH_RAMP_STOP = 255
2836};
2837
2842enum text_rotation {
2843 DEGREE_0,
2844 DEGREE_90,
2845 DEGREE_180,
2846 DEGREE_270
2847};
2848
2853enum device_format {
2854 STRAIGHT_THROUGH,
2855 RGB565_8BIT_SERIAL,
2857 // Byte encoding: 0xRRRRRGGG 0xGGGBBBBB
2858 RGB666_8BIT_SERIAL_1,
2860 // Byte encoding: 0x00RRRRRR 0x00GGGGGG 0x00BBBBBB
2861 RGB666_8BIT_SERIAL_2
2863 // Byte encoding: 0xRRRRRR00 0xGGGGGG00 0xBBBBBB00
2864};
2865
2869struct plot {
2870 int32_t x = 0;
2871 int32_t y = 0;
2872 uint8_t col = color::WHITE;
2873};
2874
2879 int32_t x0 = 0;
2880 int32_t y0 = 0;
2881 int32_t x1 = 0;
2882 int32_t y1 = 0;
2883 uint8_t col = color::WHITE;
2884 float sw = 1;
2885};
2886
2891 int32_t x = 0;
2892 int32_t y = 0;
2893 int32_t w = 0;
2894 int32_t h = 0;
2895 uint8_t col = color::WHITE;
2896 int32_t sw = 1;
2897};
2898
2903 int32_t cx = 0;
2904 int32_t cy = 0;
2905 int32_t r = 0;
2906 uint8_t col = color::WHITE;
2907 int32_t sw = 1;
2908};
2909
2915 int32_t x = 0;
2916 int32_t y = 0;
2917 int32_t w = 0;
2918 int32_t h = 0;
2919 int32_t r = 0;
2920 uint8_t col = color::WHITE;
2921 int32_t sw = 1;
2922};
2923
2928 int32_t x = 0;
2929 int32_t y = 0;
2930 const char *str = nullptr;
2931 uint8_t col = color::WHITE;
2932};
2933
2934template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
2935class image;
2936
2940namespace shapes {
2941
2947template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
2948class rect {
2949 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
2950 image_type &img;
2951 int32_t x, y, w, h;
2952
2953 public:
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_) {
2964 }
2965
2971 constexpr rect &fill(uint8_t col) {
2972 img.fill_rect(x, y, w, h, col);
2973 return *this;
2974 }
2975
2981 template <typename shader_func>
2982 constexpr auto fill_shader(const shader_func &shader) -> rect &
2983 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
2984 {
2985 img.fill_rect(x, y, w, h, shader);
2986 return *this;
2987 }
2988
2995 constexpr rect &stroke(uint8_t col, int32_t stroke_width = 1) {
2996 img.stroke_rect(x, y, w, h, col, stroke_width);
2997 return *this;
2998 }
2999};
3000
3006template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
3007class circle {
3008 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3009 image_type &img;
3010 int32_t cx, cy, r;
3011
3012 public:
3020 constexpr circle(image_type &image, int32_t cx_, int32_t cy_, int32_t r_) : img(image), cx(cx_), cy(cy_), r(r_) {
3021 }
3022
3028 constexpr circle &fill(uint8_t col) {
3029 img.fill_circle(cx, cy, r, col);
3030 return *this;
3031 }
3032
3039 constexpr circle &stroke(uint8_t col, int32_t stroke_width = 1) {
3040 img.stroke_circle(cx, cy, r, col, stroke_width);
3041 return *this;
3042 }
3043};
3044
3050template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
3052 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3053 image_type &img;
3054 int32_t cx, cy, r;
3055
3056 public:
3064 constexpr circle_aa(image_type &image, int32_t cx_, int32_t cy_, int32_t r_) : img(image), cx(cx_), cy(cy_), r(r_) {
3065 }
3066
3072 constexpr circle_aa &fill(uint8_t col) {
3073 img.fill_circle_aa(cx, cy, r, col);
3074 return *this;
3075 }
3076
3082 template <typename shader_func>
3083 constexpr auto fill_shader(const shader_func &shader) -> circle_aa &
3084 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
3085 {
3086 img.fill_circle_aa(cx, cy, r, shader);
3087 return *this;
3088 }
3089
3096 constexpr circle_aa &stroke(uint8_t col, int32_t stroke_width = 1) {
3097 img.stroke_circle_aa(cx, cy, r, col, stroke_width);
3098 return *this;
3099 }
3100};
3101
3107template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
3109 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3110 image_type &img;
3111 int32_t x, y, w, h, radius;
3112
3113 public:
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_) {
3125 }
3126
3132 constexpr round_rect &fill(uint8_t col) {
3133 img.fill_round_rect(x, y, w, h, radius, col);
3134 return *this;
3135 }
3136
3143 constexpr round_rect &stroke(uint8_t col, int32_t stroke_width = 1) {
3144 img.stroke_round_rect(x, y, w, h, radius, col, stroke_width);
3145 return *this;
3146 }
3147};
3148
3154template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
3156 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3157 image_type &img;
3158 int32_t x, y, w, h, radius;
3159
3160 public:
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_) {
3172 }
3173
3179 constexpr round_rect_aa &fill(uint8_t col) {
3180 img.fill_round_rect_aa(x, y, w, h, radius, col);
3181 return *this;
3182 }
3183
3189 template <typename shader_func>
3190 constexpr auto fill_shader(const shader_func &shader) -> round_rect_aa &
3191 requires std::is_invocable_r_v<std::array<float, 4>, shader_func, float, float, int32_t, int32_t>
3192 {
3193 img.fill_round_rect_aa(x, y, w, h, radius, shader);
3194 return *this;
3195 }
3196
3203 constexpr round_rect_aa &stroke(uint8_t col, int32_t stroke_width = 1) {
3204 img.stroke_round_rect_aa(x, y, w, h, radius, col, stroke_width);
3205 return *this;
3206 }
3207};
3208
3214template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
3215class line {
3216 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3217 image_type &img;
3218 int32_t x0, y0, x1, y1;
3219
3220 public:
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_) {
3231 }
3232
3239 constexpr line &stroke(uint8_t col, int32_t stroke_width = 1) {
3240 img.draw_line(x0, y0, x1, y1, col, stroke_width);
3241 return *this;
3242 }
3243};
3244
3250template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
3251class line_aa {
3252 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3253 image_type &img;
3254 int32_t x0, y0, x1, y1;
3255
3256 public:
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_) {
3267 }
3268
3275 constexpr line_aa &stroke(uint8_t col, float stroke_width = 1.0f) {
3276 img.draw_line_aa(x0, y0, x1, y1, col, stroke_width);
3277 return *this;
3278 }
3279};
3280
3286template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
3287class point {
3288 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3289 image_type &img;
3290 int32_t x, y;
3291
3292 public:
3299 constexpr point(image_type &image, int32_t x_, int32_t y_) : img(image), x(x_), y(y_) {
3300 }
3301
3307 constexpr point &plot(uint8_t col) {
3308 img.plot(x, y, col);
3309 return *this;
3310 }
3311};
3312
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>
3321 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3322 image_type &img;
3323 int32_t x, y;
3324 const char *str;
3325
3326 public:
3334 constexpr text_mono(image_type &image, int32_t x_, int32_t y_, const char *str_)
3335 : img(image), x(x_), y(y_), str(str_) {
3336 }
3337
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,
3348 character_actual);
3349 }
3350
3356 constexpr text_mono &color(uint8_t col) {
3357 img.template draw_string_mono<FONT, KERNING, ROTATION>(x, y, str, col);
3358 return *this;
3359 }
3360};
3361
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>
3369class text_aa {
3370 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3371 image_type &img;
3372 int32_t x, y;
3373 const char *str;
3374
3375 public:
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_) {
3385 }
3386
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);
3397 }
3398
3404 constexpr text_aa &color(uint8_t col) {
3405 img.template draw_string_aa<FONT, KERNING, ROTATION>(x, y, str, col);
3406 return *this;
3407 }
3408};
3409
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>
3418 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3419 image_type &img;
3420 int32_t x, y;
3421 const char *str;
3422
3423 public:
3431 constexpr text_centered_mono(image_type &image, int32_t x_, int32_t y_, const char *str_)
3432 : img(image), x(x_), y(y_), str(str_) {
3433 }
3434
3440 constexpr text_centered_mono &color(uint8_t col) {
3441 img.template draw_string_centered_mono<FONT, KERNING, ROTATION>(x, y, str, col);
3442 return *this;
3443 }
3444};
3445
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>
3454 using image_type = image<T, W, H, GRAYSCALE, USE_SPAN>;
3455 image_type &img;
3456 int32_t x, y;
3457 const char *str;
3458
3459 public:
3467 constexpr text_centered_aa(image_type &image, int32_t x_, int32_t y_, const char *str_)
3468 : img(image), x(x_), y(y_), str(str_) {
3469 }
3470
3476 constexpr text_centered_aa &color(uint8_t col) {
3477 img.template draw_string_centered_aa<FONT, KERNING, ROTATION>(x, y, str, col);
3478 return *this;
3479 }
3480};
3481
3482} // namespace shapes
3483
3500template <template <size_t, size_t, bool, bool> class T, size_t W, size_t H, bool GRAYSCALE = false,
3501 bool USE_SPAN = false>
3502class image {
3503 static_assert(sizeof(W) >= sizeof(uint32_t));
3504 static_assert(sizeof(H) >= sizeof(uint32_t));
3505
3506 static_assert(W > 0 && H > 0);
3507 static_assert(W <= 65535 && H <= 65535);
3508
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;
3513#else // #if defined(__x6_64__) || defined(_M_X64) || defined(__aarch64__)
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;
3517#endif // #if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__)
3518
3519 public:
3524 requires(!USE_SPAN)
3525 = default;
3526
3531 image(const std::span<uint8_t, T<W, H, GRAYSCALE, USE_SPAN>::image_size> &other)
3532 requires(USE_SPAN)
3533 : data(other) {
3534 }
3535
3540 [[nodiscard]] static constexpr bool grayscale() {
3541 return GRAYSCALE;
3542 }
3543
3548 [[nodiscard]] static constexpr size_t bit_depth() {
3549 return T<W, H, GRAYSCALE, USE_SPAN>::bits_per_pixel;
3550 }
3551
3556 [[nodiscard]] static constexpr size_t size() {
3557 return T<W, H, GRAYSCALE, USE_SPAN>::image_size;
3558 }
3559
3564 [[nodiscard]] static constexpr size_t bytes_per_line() {
3565 return T<W, H, GRAYSCALE, USE_SPAN>::bytes_per_line;
3566 }
3567
3572 [[nodiscard]] static constexpr int32_t width() {
3573 return static_cast<int32_t>(W);
3574 }
3575
3580 [[nodiscard]] static constexpr int32_t height() {
3581 return static_cast<int32_t>(H);
3582 }
3583
3587 constexpr void clear() {
3588 data.fill(0);
3589 }
3590
3595 [[nodiscard]] constexpr std::array<uint8_t, T<W, H, GRAYSCALE, USE_SPAN>::image_size> &data_ref() {
3596 return data;
3597 }
3598
3603 [[nodiscard]] constexpr image<T, W, H, GRAYSCALE> clone() const {
3604 return *this;
3605 }
3606
3610 constexpr void copy(const image<T, W, H, GRAYSCALE> &src) {
3611 data = src.data;
3612 }
3613
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];
3624 }
3625 }
3626
3634 [[nodiscard]] constexpr uint8_t get_nearest_color(uint8_t r, uint8_t g, uint8_t b) const {
3635 return format.quant.nearest(r, g, b);
3636 }
3637
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) {
3648 return;
3649 }
3650 T<W, H, GRAYSCALE, USE_SPAN>::plot(data, static_cast<uint32_t>(x), static_cast<uint32_t>(y), col);
3651 }
3652
3658 constexpr void plot(const struct plot &p) {
3659 plot(p.x, p.y, p.col);
3660 }
3661
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) {
3679 return;
3680 }
3681
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)) {
3684 return;
3685 }
3686
3687 if (stroke_width <= 0) {
3688 return;
3689 }
3690
3691 if (stroke_width == 1) {
3692 bool steep = abs(y1 - y0) > abs(x1 - x0);
3693 if (steep) {
3694 std::swap(x0, y0);
3695 std::swap(x1, y1);
3696 }
3697 if (x0 > x1) {
3698 std::swap(x0, x1);
3699 std::swap(y0, y1);
3700 }
3701
3702 const int32_t dx = x1 - x0;
3703 const int32_t dy = abs(y1 - y0);
3704 int32_t err = dx / 2;
3705 int32_t ystep = 0;
3706 if (y0 < y1) {
3707 ystep = 1;
3708 } else {
3709 ystep = -1;
3710 }
3711
3712 for (; x0 <= x1; x0++) {
3713 if (steep) {
3714 plot(y0, x0, col);
3715 } else {
3716 plot(x0, y0, col);
3717 }
3718 err -= dy;
3719 if (err < 0) {
3720 y0 += ystep;
3721 err += dx;
3722 }
3723 }
3724 return;
3725 }
3726
3727 auto line_thick = [&]<typename I>() {
3728 const int32_t half_width = stroke_width / 2;
3729
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);
3735
3736 if (min_x > max_x || min_y > max_y) {
3737 return;
3738 }
3739
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;
3743
3744 if (line_length_sq <= 1) {
3745 if (x0 >= 0 && x0 < static_cast<int32_t>(W) && y0 >= 0 && y0 < static_cast<int32_t>(H)) {
3746 fill_circle(x0, y0, half_width, col);
3747 }
3748 return;
3749 }
3750
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;
3755
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));
3758
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;
3761
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;
3765
3766 if (distance_sq <= static_cast<I>(half_width) * static_cast<I>(half_width)) {
3767 plot(px, py, col);
3768 }
3769 }
3770 }
3771 };
3772 line_thick.template operator()<calc_square_type>();
3773 }
3774
3784 constexpr void draw_line(const struct draw_line &d) {
3785 draw_line(d.x0, d.y0, d.x1, d.y1, d.col, static_cast<int32_t>(d.sw));
3786 }
3787
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) {
3807 return;
3808 }
3809
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);
3813
3814 if (stroke_width <= 0.0f) {
3815 return;
3816 }
3817
3818 if (stroke_width <= 1.0f) {
3819 if (!clip_line(x0, y0, x1, y1, 0, 0, W, H)) {
3820 return;
3821 }
3822
3823 bool steep = abs(y1 - y0) > abs(x1 - x0);
3824 if (steep) {
3825 std::swap(x0, y0);
3826 std::swap(x1, y1);
3827 }
3828 if (x0 > x1) {
3829 std::swap(x0, x1);
3830 std::swap(y0, y1);
3831 }
3832
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;
3836
3837 auto color_compose = [&](int32_t x, int32_t y, float a) {
3838 if (a < hidden::epsilon_low) {
3839 return;
3840 }
3841 if (a >= hidden::epsilon_high) {
3842 plot(x, y, col);
3843 } else {
3844 compose(x, y, a, Rl, Gl, Bl);
3845 }
3846 };
3847
3848 auto ipart = [](float x) {
3849 return std::floor(x);
3850 };
3851 auto fpart = [](float x) {
3852 return x - std::floor(x);
3853 };
3854 auto rfpart = [&](float x) {
3855 return 1.0f - fpart(x);
3856 };
3857
3858 // first endpoint
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));
3864 if (steep) {
3865 color_compose(ypxl1, xpxl1, rfpart(yend) * xgap);
3866 color_compose(ypxl1 + 1, xpxl1, fpart(yend) * xgap);
3867 } else {
3868 color_compose(xpxl1, ypxl1, rfpart(yend) * xgap);
3869 color_compose(xpxl1, ypxl1 + 1, fpart(yend) * xgap);
3870 }
3871 float intery = yend + gradient;
3872
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));
3878 if (steep) {
3879 color_compose(ypxl2, xpxl2, rfpart(yend) * xgap);
3880 color_compose(ypxl2 + 1, xpxl2, fpart(yend) * xgap);
3881 } else {
3882 color_compose(xpxl2, ypxl2, rfpart(yend) * xgap);
3883 color_compose(xpxl2, ypxl2 + 1, fpart(yend) * xgap);
3884 }
3885
3886 for (int32_t x = xpxl1 + 1; x < xpxl2; ++x) {
3887 auto y = static_cast<int32_t>(ipart(intery));
3888 if (steep) {
3889 color_compose(y, x, rfpart(intery));
3890 color_compose(y + 1, x, fpart(intery));
3891 } else {
3892 color_compose(x, y, rfpart(intery));
3893 color_compose(x, y + 1, fpart(intery));
3894 }
3895 intery += gradient;
3896 }
3897 return;
3898 }
3899
3900 const float half_width = stroke_width * 0.5f;
3901
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);
3907
3908 if (min_x > max_x || min_y > max_y) {
3909 return;
3910 }
3911
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;
3915
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);
3925 } else {
3926 dist = std::sqrt(dist);
3927 }
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) {
3931 plot(px, py, col);
3932 } else {
3933 compose(px, py, coverage, Rl, Gl, Bl);
3934 }
3935 }
3936 }
3937 }
3938 }
3939 return;
3940 }
3941
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);
3954 } else {
3955 dist = std::sqrt(dist);
3956 }
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) {
3960 plot(px, py, col);
3961 } else {
3962 compose(px, py, coverage, Rl, Gl, Bl);
3963 }
3964 }
3965 }
3966 }
3967 }
3968
3979 constexpr void draw_line_aa(const struct draw_line &d) {
3980 draw_line_aa(d.x0, d.y0, d.x1, d.y1, d.col, d.sw);
3981 }
3982
4000 template <typename FONT, bool KERNING = false>
4001 [[nodiscard]] constexpr int32_t string_width(const char *str,
4002 size_t character_count = std::numeric_limits<size_t>::max(),
4003 size_t *character_actual = nullptr) {
4004 int32_t x = 0;
4005 size_t count = 0;
4006 while (*str != 0 && count++ < character_count) {
4007 uint32_t utf32 = 0;
4008 str = get_next_utf32(str, &utf32);
4009 size_t index = 0;
4010 if (lookup_glyph<FONT>(utf32, &index)) {
4011 const char_info<typename FONT::char_info_type> &ch_info = FONT::char_table.at(index);
4012 if (*str == 0) {
4013 x += static_cast<int32_t>(ch_info.width);
4014 } else {
4015 x += static_cast<int32_t>(ch_info.xadvance);
4016 if constexpr (KERNING) {
4017 x += get_kerning<FONT>(utf32, str);
4018 }
4019 }
4020 }
4021 }
4022 if (character_actual) {
4023 *character_actual = count;
4024 }
4025 return x;
4026 }
4027
4052 template <typename FONT, bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4053 constexpr int32_t draw_string_mono(int32_t x, int32_t y, const char *str, uint8_t col,
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) {
4059 return 0;
4060 }
4061 if (str == nullptr) {
4062 return 0;
4063 }
4064 size_t count = 0;
4065 while (*str != 0 && count++ < character_count) {
4066 uint32_t utf32 = 0;
4067 str = get_next_utf32(str, &utf32);
4068 size_t index = 0;
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);
4076 }
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);
4081 }
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);
4086 }
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);
4091 }
4092 }
4093 }
4094 }
4095 if (character_actual) {
4096 *character_actual = count;
4097 }
4098 if constexpr (ROTATION == DEGREE_90 || ROTATION == DEGREE_270) {
4099 return y;
4100 } else {
4101 return x;
4102 }
4103 }
4104
4126 template <typename FONT, bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4127 constexpr int32_t draw_string_mono(const struct draw_string &d,
4128 size_t character_count = std::numeric_limits<size_t>::max(),
4129 size_t *character_actual = nullptr) {
4130 return draw_string_mono<FONT, KERNING, ROTATION>(d.x, d.y, d.str, d.col, character_count, character_actual);
4131 }
4132
4145 template <typename FONT, bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4146 constexpr void draw_string_centered_mono(int32_t x, int32_t y, const char *str, uint8_t col) {
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) {
4155 }
4156 }
4157
4182 template <typename FONT, bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4183 constexpr int32_t draw_string_aa(int32_t x, int32_t y, const char *str, uint8_t col,
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) {
4189 return 0;
4190 }
4191 if (str == nullptr) {
4192 return 0;
4193 }
4194 size_t count = 0;
4195 while (*str != 0 && count++ < character_count) {
4196 uint32_t utf32 = 0;
4197 str = get_next_utf32(str, &utf32);
4198 size_t index = 0;
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);
4206 }
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);
4211 }
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);
4216 }
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);
4221 }
4222 }
4223 }
4224 }
4225 if (character_actual) {
4226 *character_actual = count;
4227 }
4228 if constexpr (ROTATION == DEGREE_90 || ROTATION == DEGREE_270) {
4229 return y;
4230 } else {
4231 return x;
4232 }
4233 }
4234
4256 template <typename FONT, bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4257 constexpr int32_t draw_string_aa(const struct draw_string &d,
4258 size_t character_count = std::numeric_limits<size_t>::max(),
4259 size_t *character_actual = nullptr) {
4260 return draw_string_aa<FONT, KERNING, ROTATION>(d.x, d.y, d.str, d.col, character_count, character_actual);
4261 }
4262
4276 template <typename FONT, bool KERNING = false, text_rotation ROTATION = DEGREE_0>
4277 constexpr void draw_string_centered_aa(int32_t x, int32_t y, const char *str, uint8_t col) {
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) {
4286 }
4287 }
4288
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) {
4305 return;
4306 }
4307 if (w <= 0 || h <= 0) {
4308 return;
4309 }
4310 if (check_not_in_bounds(x, y, w, h)) {
4311 return;
4312 }
4313 if (y < 0) {
4314 h += y;
4315 y = 0;
4316 }
4317 if (y + h >= static_cast<int32_t>(H)) {
4318 h = static_cast<int32_t>(H) - y;
4319 }
4320 h += y;
4321 for (; y < h; y++) {
4322 extent(x, w, y, col);
4323 }
4324 }
4325
4335 constexpr void fill_rect(const struct draw_rect &d) {
4336 fill_rect(d.x, d.y, d.w, d.h, d.col);
4337 }
4338
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>
4359 {
4360 auto minmax_check = std::minmax({x, y, w, h});
4361 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4362 return;
4363 }
4364 if (w <= 0 || h <= 0) {
4365 return;
4366 }
4367 if (check_not_in_bounds(x, y, w, h)) {
4368 return;
4369 }
4370
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));
4375
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);
4383 }
4384 compose(px, py, rgba[3], rgba[0], rgba[1], rgba[2]);
4385 }
4386 }
4387 }
4388
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) {
4406 return;
4407 }
4408 if (w <= 0 || h <= 0) {
4409 return;
4410 }
4411 stroke_width = std::min(abs(stroke_width), std::max(w, h) / 2);
4412 fill_rect(x, y, stroke_width, h, col);
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);
4416 }
4417
4427 constexpr void stroke_rect(const struct draw_rect &d) {
4428 stroke_rect(d.x, d.y, d.w, d.h, d.col, d.sw);
4429 }
4430
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) {
4446 return;
4447 }
4448 radius = abs(radius);
4449 if (radius == 1) {
4450 fill_rect(cx - 1, cy - 1, 2, 2, col);
4451 return;
4452 }
4453 circle_int<false, false>(cx, cy, radius, 0, 0, col, 0);
4454 }
4455
4465 constexpr void fill_circle(const struct draw_circle &d) {
4466 fill_circle(d.cx, d.cy, d.r, d.col);
4467 }
4468
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) {
4485 return;
4486 }
4487 radius = abs(radius);
4488 stroke_width = abs(stroke_width);
4489 if (radius == 1) {
4490 fill_rect(cx - 1, cy - 1, 2, 2, col);
4491 return;
4492 }
4493 if (stroke_width >= radius) {
4494 circle_int<false, false>(cx, cy, radius, 0, 0, col, 0);
4495 return;
4496 }
4497 circle_int<false, true>(cx, cy, radius, 0, 0, col, stroke_width);
4498 }
4499
4509 constexpr void stroke_circle(const struct draw_circle &d) {
4510 stroke_circle(d.cx, d.cy, d.r, d.col, d.sw);
4511 }
4512
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) {
4530 return;
4531 }
4532 radius = abs(radius);
4533 stroke_width = abs(stroke_width);
4534 if (radius == 1) {
4535 fill_rect(cx - 1, cy - 1, 2, 2, col);
4536 return;
4537 }
4538 if (stroke_width >= radius) {
4539 circle_int<true, false>(cx, cy, radius, 0, 0, col, 0);
4540 return;
4541 }
4542 circle_int<true, true>(cx, cy, radius, 0, 0, col, stroke_width);
4543 }
4544
4555 constexpr void stroke_circle_aa(const struct draw_circle &d) {
4556 stroke_circle_aa(d.cx, d.cy, d.r, d.col, d.sw);
4557 }
4558
4572 constexpr void fill_circle_aa(int32_t cx, int32_t cy, int32_t radius, uint8_t col) {
4573 auto minmax_check = std::minmax({cx, cy, radius});
4574 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4575 return;
4576 }
4577 radius = abs(radius);
4578 circle_int<true, false>(cx, cy, radius, 0, 0, col, 0);
4579 }
4580
4591 constexpr void fill_circle_aa(const struct draw_circle &d) {
4592 fill_circle_aa(d.cx, d.cy, d.r, d.col);
4593 }
4594
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>
4616 {
4617 auto minmax_check = std::minmax({cx, cy, radius});
4618 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4619 return;
4620 }
4621 radius = abs(radius);
4622 circle_int_shader(cx, cy, radius, 0, 0, shader);
4623 }
4624
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) {
4644 return;
4645 }
4646 if (w <= 0 || h <= 0) {
4647 return;
4648 }
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;
4654 if (radius == 0) {
4655 stroke_rect(x, y, w, h, col, stroke_width);
4656 } else {
4657 if (radius > stroke_width) {
4658 circle_int<false, true>(x + cr, y + cr, cr, dx, dy, col, stroke_width);
4659 } else {
4660 circle_int<false, false>(x + cr, y + cr, cr, dx, dy, col, 0);
4661 }
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);
4666 }
4667 }
4668
4678 constexpr void stroke_round_rect(const struct draw_round_rect &d) {
4679 stroke_round_rect(d.x, d.y, d.w, d.h, d.r, d.col, d.sw);
4680 }
4681
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) {
4699 return;
4700 }
4701 if (w <= 0 || h <= 0) {
4702 return;
4703 }
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);
4708 fill_rect(x, y + cr, cr, dy, col);
4709 fill_rect(x + w - cr, y + cr, cr, dy, col);
4710 fill_rect(x + cr, y, dx, h, col);
4711 }
4712
4722 constexpr void fill_round_rect(const struct draw_round_rect &d) {
4723 fill_round_rect(d.x, d.y, d.w, d.h, d.r, d.col);
4724 }
4725
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) {
4744 return;
4745 }
4746 if (w <= 0 || h <= 0) {
4747 return;
4748 }
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);
4754 fill_rect(x, y + cr, cr, dy, col);
4755 fill_rect(x + w - cr, y + cr, cr, dy, col);
4756 fill_rect(x + cr, y, dx, h, col);
4757 }
4758
4769 constexpr void fill_round_rect_aa(const struct draw_round_rect &d) {
4770 fill_round_rect_aa(d.x, d.y, d.w, d.h, d.r, d.col);
4771 }
4772
4791 template <typename shader_func>
4792 constexpr auto fill_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius,
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>
4795 {
4796 auto minmax_check = std::minmax({x, y, w, h, radius});
4797 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
4798 return;
4799 }
4800 if (w <= 0 || h <= 0) {
4801 return;
4802 }
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;
4807
4808 circle_int_shader(x + cr, y + cr, cr, dx, dy, shader, x, y, w, h);
4809
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);
4813 }
4814
4830 constexpr void stroke_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col,
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) {
4834 return;
4835 }
4836 if (w <= 0 || h <= 0) {
4837 return;
4838 }
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;
4844 if (radius == 0) {
4845 stroke_rect(x, y, w, h, col, stroke_width);
4846 } else {
4847 if (radius > stroke_width) {
4848 circle_int<true, true>(x + cr, y + cr, cr, dx, dy, col, stroke_width);
4849 } else {
4850 circle_int<true, false>(x + cr, y + cr, cr, dx, dy, col, 0);
4851 }
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);
4856 }
4857 }
4858
4868 constexpr void stroke_round_rect_aa(const struct draw_round_rect &d) {
4869 stroke_round_rect_aa(d.x, d.y, d.w, d.h, d.r, d.col, d.sw);
4870 }
4871
4876 constexpr void RGBA_uint32(std::array<uint32_t, W * H> &dst) const {
4877 T<W, H, GRAYSCALE, USE_SPAN>::RGBA_uint32(dst, data);
4878 }
4879
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);
4886 }
4887
4891 constexpr void flip_h() {
4892 uint8_t *ptr = data.data();
4893 for (size_t y = 0; y < H; y++) {
4894 for (size_t x = 0; x < bytes_per_line() / 2; x++) {
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]);
4897 ptr[x] = b;
4898 ptr[bytes_per_line() - x - 1] = a;
4899 }
4900 ptr += bytes_per_line();
4901 }
4902 }
4903
4907 constexpr void flip_v() {
4908 uint8_t *ptr = data.data();
4909 for (size_t x = 0; x < bytes_per_line(); x++) {
4910 for (size_t y = 0; y < H / 2; y++) {
4911 uint8_t *ptr_a = ptr + y * bytes_per_line();
4912 uint8_t *ptr_b = ptr + (H - y - 1) * bytes_per_line();
4913 const uint8_t a = ptr_a[x];
4914 const uint8_t b = ptr_b[x];
4915 ptr_a[x] = b;
4916 ptr_b[x] = a;
4917 }
4918 }
4919 }
4920
4924 constexpr void flip_hv() {
4925 uint8_t *ptr = data.data();
4926 for (size_t x = 0; x < bytes_per_line(); x++) {
4927 for (size_t y = 0; y < H / 2; y++) {
4928 uint8_t *ptr_a = ptr + y * bytes_per_line();
4929 uint8_t *ptr_b = ptr + (H - y - 1) * bytes_per_line();
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]);
4932 ptr_a[x] = b;
4933 ptr_b[bytes_per_line() - x - 1] = a;
4934 }
4935 }
4936 }
4937
4943 template <bool FLIP_H = false, bool FLIP_V = false>
4945 image<T, H, W, GRAYSCALE> transposed;
4946 T<W, H, GRAYSCALE, USE_SPAN>::template transpose<FLIP_H, FLIP_V>(data.data(), transposed.data_ref().data());
4947 return transposed;
4948 }
4949
4955 template <bool FLIP_H = false, bool FLIP_V = false>
4956 constexpr void transpose(image<T, H, W, GRAYSCALE> &dst) const {
4957 T<W, H, GRAYSCALE, USE_SPAN>::template transpose<FLIP_H, FLIP_V>(data.data(), dst.data_ref().data());
4958 }
4959
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,
4972 int32_t stride) {
4973 constixel::rect<int32_t> blitrect{.x = x, .y = y, .w = w, .h = h};
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);
4977 }
4978
4988 constexpr void blit_RGBA(const rect<int32_t> &dstrect, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride) {
4989 constixel::rect<int32_t> blitrect{dstrect};
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);
4993 }
4994
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) {
5009 constixel::rect<int32_t> blitrect{.x = x, .y = y, .w = w, .h = h};
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);
5013 }
5014
5025 constexpr void blit_RGBA_diffused(const rect<int32_t> &dstrect, const uint8_t *ptr, int32_t iw, int32_t ih,
5026 int32_t stride) {
5027 constixel::rect<int32_t> blitrect{dstrect};
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);
5031 }
5032
5045 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,
5046 int32_t ih, int32_t stride) {
5047 constixel::rect<int32_t> blitrect{.x = x, .y = y, .w = w, .h = h};
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);
5051 }
5052
5063 constexpr void blit_RGBA_diffused_linear(const rect<int32_t> &dstrect, const uint8_t *ptr, int32_t iw, int32_t ih,
5064 int32_t stride) {
5065 constixel::rect<int32_t> blitrect{dstrect};
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);
5069 }
5070
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));
5086 }
5087
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});
5104 }
5105
5120 template <size_t S = 1, typename F>
5121 constexpr void sixel(F &&char_out, const rect<int32_t> &rect) const {
5122 T<W, H, GRAYSCALE, USE_SPAN>::template sixel<S>(data, std::forward<F>(char_out), rect);
5123 }
5124
5130 template <size_t S = 1>
5131 void sixel_to_cout() const {
5132 std::string out;
5133 T<W, H, GRAYSCALE, USE_SPAN>::template sixel<S>(data,
5134 [&out](char ch) mutable {
5135 out.push_back(ch);
5136 },
5137 {0, 0, W, H});
5138 std::cout << out << '\n';
5139 }
5140
5144 void vt100_clear() const {
5145 std::cout << "\033[2J\033[3J\033[H";
5146 }
5147
5152 std::cout << "\033[3J";
5153 }
5154
5158 void vt100_home() const {
5159 std::cout << "\033[H";
5160 }
5161
5165 void png_to_iterm() const {
5166 std::string output;
5167 output.append("\033]1337;File=inline=1:");
5168 append_png_as_base64(output);
5169 output.append("\07");
5170 std::cout << output << '\n';
5171 }
5172
5176 void png_to_kitty() const {
5177 std::string base64{};
5178 std::string output{};
5179 append_png_as_base64(base64);
5180 bool first = true;
5181 for (; !base64.empty();) {
5182 if (first) {
5183 first = false;
5184 output.append("\033_Ga=T,f=100,");
5185 } else {
5186 output.append("\033_G");
5187 }
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\\");
5193 }
5194 std::cout << output << '\n';
5195 }
5196
5202 template <device_format dst_format, typename F>
5203 constexpr void convert(F &&uint8_out) {
5204 if constexpr (dst_format == STRAIGHT_THROUGH) {
5205 for (auto c : data) {
5206 std::forward<F>(uint8_out)(static_cast<uint8_t>(c));
5207 }
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));
5217 }
5218 ptr += format.bytes_per_line;
5219 }
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));
5228 }
5229 ptr += format.bytes_per_line;
5230 }
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);
5239 }
5240 ptr += format.bytes_per_line;
5241 }
5242 }
5243 }
5244
5256
5265 constexpr auto rect(int32_t x, int32_t y, int32_t w, int32_t h) {
5266 return shapes::rect<T, W, H, GRAYSCALE, USE_SPAN>(*this, x, y, w, h);
5267 }
5268
5276 constexpr auto circle(int32_t cx, int32_t cy, int32_t r) {
5277 return shapes::circle<T, W, H, GRAYSCALE, USE_SPAN>(*this, cx, cy, r);
5278 }
5279
5288 constexpr auto circle_aa(int32_t cx, int32_t cy, int32_t r) {
5289 return shapes::circle_aa<T, W, H, GRAYSCALE, USE_SPAN>(*this, cx, cy, r);
5290 }
5291
5301 constexpr auto round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius) {
5302 return shapes::round_rect<T, W, H, GRAYSCALE, USE_SPAN>(*this, x, y, w, h, radius);
5303 }
5304
5315 constexpr auto round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius) {
5316 return shapes::round_rect_aa<T, W, H, GRAYSCALE, USE_SPAN>(*this, x, y, w, h, radius);
5317 }
5318
5327 constexpr auto line(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
5328 return shapes::line<T, W, H, GRAYSCALE, USE_SPAN>(*this, x0, y0, x1, y1);
5329 }
5330
5340 constexpr auto line_aa(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
5341 return shapes::line_aa<T, W, H, GRAYSCALE, USE_SPAN>(*this, x0, y0, x1, y1);
5342 }
5343
5350 constexpr auto point(int32_t x, int32_t y) {
5352 }
5353
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) {
5372 }
5373
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) {
5393 }
5394
5411 template <typename FONT, bool KERNING = false, text_rotation ROTATION = DEGREE_0>
5412 constexpr auto text_centered_mono(int32_t x, int32_t y, const char *str) {
5414 }
5415
5432 template <typename FONT, bool KERNING = false, text_rotation ROTATION = DEGREE_0>
5433 constexpr auto text_centered_aa(int32_t x, int32_t y, const char *str) {
5435 }
5436
5437 private:
5438#ifndef __INTELLISENSE__
5440 constexpr bool check_not_in_bounds(int32_t x, int32_t y, int32_t w, int32_t h) {
5441 constixel::rect<int32_t> intersect_rect{.x = 0, .y = 0, .w = W, .h = H};
5442 intersect_rect &= constixel::rect<int32_t>{.x = x, .y = y, .w = w, .h = h};
5443 if (intersect_rect.w <= 0 || intersect_rect.h <= 0) {
5444 return true;
5445 }
5446 return false;
5447 }
5448
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) {
5451 enum : uint32_t {
5452 INSIDE = 0,
5453 XMIN = 1,
5454 XMAX = 2,
5455 YMIN = 4,
5456 YMAX = 8
5457 };
5458
5459 auto calc_code = [&](int32_t x, int32_t y) {
5460 uint32_t code = INSIDE;
5461 if (x < xmin) {
5462 code |= XMIN;
5463 } else if (x > xmax) {
5464 code |= XMAX;
5465 }
5466 if (y < ymin) {
5467 code |= YMIN;
5468 } else if (y > ymax) {
5469 code |= YMAX;
5470 }
5471 return code;
5472 };
5473
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) {
5479 return true;
5480 }
5481 if ((outcode0 & outcode1) != 0) {
5482 return false;
5483 }
5484 uint32_t outcode_out = outcode1 > outcode0 ? outcode1 : outcode0;
5485 int32_t x = 0;
5486 int32_t y = 0;
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);
5492 y = ymax;
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);
5498 y = ymin;
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);
5504 x = xmax;
5505 } else {
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);
5510 x = xmin;
5511 }
5512 if (outcode_out == outcode0) {
5513 x0 = x;
5514 y0 = y;
5515 outcode0 = calc_code(x0, y0);
5516 } else {
5517 x1 = x;
5518 y1 = y;
5519 outcode1 = calc_code(x1, y1);
5520 }
5521 }
5522 return false;
5523 };
5524 return clip_loop.template operator()<calc_square_type>();
5525 }
5526
5530 template <typename abs_T>
5531 [[nodiscard]] static constexpr abs_T abs(abs_T v) {
5532 return v < 0 ? -v : v;
5533 }
5534
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) {
5541 return false;
5542 }
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) {
5549 return false;
5550 }
5551 }
5552 }
5553 *glyph_index = index;
5554 return true;
5555 }
5556
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;
5564 uint32_t utf_r = 0;
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));
5571 }
5572 }
5573 return 0;
5574 }
5575
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;
5581 if (lead < 0x80) {
5582 *utf32 = lead;
5583 return str + 1;
5584 }
5585 if ((lead >> 5) == 0x06 && str[1] != char{0}) {
5586 *utf32 = ((lead & 0x1F) << 6) | (static_cast<uint32_t>(str[1]) & 0x3F);
5587 return str + 2;
5588 }
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);
5592 return str + 3;
5593 }
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);
5597 return str + 4;
5598 }
5599 *utf32 = 0;
5600 return str + 1;
5601 }
5602
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) {
5610 return;
5611 }
5612 T<W, H, GRAYSCALE, USE_SPAN>::compose(data, static_cast<uint32_t>(x), static_cast<uint32_t>(y), cola, colr,
5613 colg, colb);
5614 }
5615
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,
5621 colg, colb);
5622 }
5623
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);
5629 }
5630
5634 void append_png_as_base64(std::string &output) const {
5635 size_t buffer = 0;
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]);
5645 }
5646 return [&output]() mutable {
5647 if (output.capacity() == 0) {
5648 return;
5649 }
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('=');
5654 }
5655 }
5656 };
5657 });
5658 }
5659
5663 constexpr auto calculate_circle_bounds(int32_t cx, int32_t cy, int32_t r) const {
5664 struct bounds {
5665 int32_t x0, y0, x0r, x0r2, y0r, y0r2;
5666 };
5667
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});
5674
5675 return bounds{x0, y0, x0r, x0r2, y0r, y0r2};
5676 }
5677
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>
5685 {
5686 auto minmax_check = std::minmax({x, y, w, h});
5687 if (minmax_check.first < min_coord || minmax_check.second > max_coord) {
5688 return;
5689 }
5690 if (w <= 0 || h <= 0) {
5691 return;
5692 }
5693 if (check_not_in_bounds(x, y, w, h)) {
5694 return;
5695 }
5696
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));
5701
5702 const auto parent_wF = static_cast<float>(parent_w);
5703 const auto parent_hF = static_cast<float>(parent_h);
5704
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;
5709
5710 u = std::clamp(u, 0.0f, 1.0f);
5711 v = std::clamp(v, 0.0f, 1.0f);
5712
5713 auto rgba = shader(u, v, px, py);
5714 for (auto &p : rgba) {
5715 p = std::clamp(p, 0.0f, 1.0f);
5716 }
5717 compose(px, py, rgba[3], rgba[0], rgba[1], rgba[2]);
5718 }
5719 }
5720 }
5721
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);
5728
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})) {
5731 return;
5732 }
5733
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);
5739 };
5740
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;
5746 };
5747
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,
5751 int32_t y_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})) {
5759 continue;
5760 }
5761 plot_unsafe(x + x_off, y + y_off, col);
5762 }
5763 }
5764 };
5765 for_each_quadrant.template operator()<calc_square_type>(plot_arc);
5766 } else {
5767 auto plot_arc = [&, this]<typename I>(int32_t xx0, int32_t yy0, int32_t xx1, int32_t yy1, int32_t x_off,
5768 int32_t y_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})) {
5776 continue;
5777 }
5778 if (dist_sq < (static_cast<I>(r - s) * static_cast<I>(r - s) * I{4} - I{3})) {
5779 continue;
5780 }
5781 plot_unsafe(x + x_off, y + y_off, col);
5782 }
5783 }
5784 };
5785 for_each_quadrant.template operator()<calc_square_type>(plot_arc);
5786 }
5787 } else {
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,
5794 int32_t y_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)) {
5802 continue;
5803 }
5804 if (dist_sq < (rF - 0.5f) * (rF - 0.5f)) {
5805 plot_unsafe(x + x_off, y + y_off, col);
5806 continue;
5807 }
5808 float a = rF;
5809 if (std::is_constant_evaluated()) {
5810 a -= hidden::fast_sqrtf(dist_sq);
5811 } else {
5812 a -= std::sqrt(dist_sq);
5813 }
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);
5818 } else {
5819 compose_unsafe(x + x_off, y + y_off, a, Rl, Gl, Bl);
5820 }
5821 }
5822 }
5823 }
5824 };
5825 for_each_quadrant.template operator()<float>(plot_arc);
5826 } else {
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,
5829 int32_t y_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))) {
5837 continue;
5838 }
5839 if (dist_sq < ((rsF - 0.5f) * (rsF - 0.5f))) {
5840 continue;
5841 }
5842 if (dist_sq < ((rsF + 0.5f) * (rsF + 0.5f))) {
5843 float a = rsF;
5844 if (std::is_constant_evaluated()) {
5845 a -= hidden::fast_sqrtf(dist_sq);
5846 } else {
5847 a -= std::sqrt(dist_sq);
5848 }
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);
5853 } else {
5854 compose_unsafe(x + x_off, y + y_off, a, Rl, Gl, Bl);
5855 }
5856 } else {
5857 plot_unsafe(x + x_off, y + y_off, col);
5858 }
5859 } else {
5860 float a = rF;
5861 if (std::is_constant_evaluated()) {
5862 a -= hidden::fast_sqrtf(dist_sq);
5863 } else {
5864 a -= std::sqrt(dist_sq);
5865 }
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);
5870 } else {
5871 compose_unsafe(x + x_off, y + y_off, a, Rl, Gl, Bl);
5872 }
5873 }
5874 }
5875 }
5876 }
5877 };
5878 for_each_quadrant.template operator()<float>(plot_arc);
5879 }
5880 }
5881 }
5882
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);
5891
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})) {
5894 return;
5895 }
5896
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);
5902 };
5903
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;
5909 };
5910
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;
5916
5917 const auto parent_wF = static_cast<float>(actual_parent_w);
5918 const auto parent_hF = static_cast<float>(actual_parent_h);
5919
5920 auto plot_arc = [&, this]<typename I>(int32_t xx0, int32_t yy0, int32_t xx1, int32_t yy1, int32_t x_off,
5921 int32_t y_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)) {
5929 continue;
5930 }
5931
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;
5934
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);
5940 }
5941
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]);
5944 continue;
5945 }
5946
5947 float a = rF;
5948 if (std::is_constant_evaluated()) {
5949 a -= hidden::fast_sqrtf(dist_sq);
5950 } else {
5951 a -= std::sqrt(dist_sq);
5952 }
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]);
5956 }
5957 }
5958 }
5959 };
5960 for_each_quadrant.template operator()<float>(plot_arc);
5961 }
5962
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,
5968 uint8_t col) {
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;
5972
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});
5978 };
5979
5980 if constexpr (ROTATION == DEGREE_0) {
5981 if (check_not_in_bounds(x + ch.xoffset, y + ch.yoffset, ch.width + 1, ch.height + 1)) {
5982 return;
5983 }
5984
5985 x += ch.xoffset;
5986 y += ch.yoffset;
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);
5997 if (a) {
5998 plot_unsafe(xx, yy, col);
5999 }
6000 }
6001 }
6002 ch_data_off += FONT::glyph_bitmap_stride;
6003 }
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)) {
6006 return;
6007 }
6008
6009 x -= ch.xoffset;
6010 y -= FONT::ascent;
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);
6021 if (a) {
6022 plot_unsafe(xx, yy, col);
6023 }
6024 }
6025 }
6026 ch_data_off += FONT::glyph_bitmap_stride;
6027 }
6028 } else if constexpr (ROTATION == DEGREE_90) {
6029 if (check_not_in_bounds(x - FONT::ascent, y, ch.height + 1, ch.width + 1)) {
6030 return;
6031 }
6032
6033 x -= FONT::ascent;
6034 y += ch.xoffset;
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);
6045 if (a) {
6046 plot_unsafe(xx, yy, col);
6047 }
6048 }
6049 }
6050 ch_data_off += FONT::glyph_bitmap_stride;
6051 }
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)) {
6054 return;
6055 }
6056
6057 x += ch.yoffset;
6058 y -= ch.xoffset;
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);
6069 if (a) {
6070 plot_unsafe(xx, yy, col);
6071 }
6072 }
6073 }
6074 ch_data_off += FONT::glyph_bitmap_stride;
6075 }
6076 }
6077 }
6078
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.");
6085
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);
6091
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});
6097 };
6098
6099 if constexpr (ROTATION == DEGREE_0) {
6100 if (check_not_in_bounds(x + ch.xoffset, y + ch.yoffset, ch.width + 1, ch.height + 1)) {
6101 return;
6102 }
6103
6104 x += ch.xoffset;
6105 y += ch.yoffset;
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);
6116 if (a != 0) {
6117 if (a == 0xF) {
6118 plot_unsafe(xx, yy, col);
6119 } else {
6120 float Al = hidden::a2al_4bit[a];
6121 compose_unsafe(xx, yy, Al, Rl, Gl, Bl);
6122 }
6123 }
6124 }
6125 }
6126 ch_data_off += FONT::glyph_bitmap_stride;
6127 }
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)) {
6130 return;
6131 }
6132
6133 x -= ch.xoffset;
6134 y -= FONT::ascent;
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);
6145 if (a != 0) {
6146 if (a == 0xF) {
6147 plot_unsafe(xx, yy, col);
6148 } else {
6149 float Al = hidden::a2al_4bit[a];
6150 compose_unsafe(xx, yy, Al, Rl, Gl, Bl);
6151 }
6152 }
6153 }
6154 }
6155 ch_data_off += FONT::glyph_bitmap_stride;
6156 }
6157 } else if constexpr (ROTATION == DEGREE_90) {
6158 if (check_not_in_bounds(x - FONT::ascent, y, ch.height + 1, ch.width + 1)) {
6159 return;
6160 }
6161
6162 x -= FONT::ascent;
6163 y += ch.xoffset;
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);
6174 if (a != 0) {
6175 if (a == 0xF) {
6176 plot_unsafe(xx, yy, col);
6177 } else {
6178 float Al = hidden::a2al_4bit[a];
6179 compose_unsafe(xx, yy, Al, Rl, Gl, Bl);
6180 }
6181 }
6182 }
6183 }
6184 ch_data_off += FONT::glyph_bitmap_stride;
6185 }
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)) {
6188 return;
6189 }
6190
6191 x += ch.yoffset;
6192 y -= ch.xoffset;
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);
6203 if (a != 0) {
6204 if (a == 0xF) {
6205 plot_unsafe(xx, yy, col);
6206 } else {
6207 float Al = hidden::a2al_4bit[a];
6208 compose_unsafe(xx, yy, Al, Rl, Gl, Bl);
6209 }
6210 }
6211 }
6212 }
6213 ch_data_off += FONT::glyph_bitmap_stride;
6214 }
6215 }
6216 }
6217
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) {
6225 return;
6226 }
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);
6231 }
6232
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>>;
6238 dataType data{};
6239
6243 T<W, H, GRAYSCALE, USE_SPAN> format{};
6244
6246#endif // #ifndef __INTELLISENSE__
6247};
6248
6249} // namespace constixel
6250
6251#endif // CONSTIXEL_HPP_
1-bit format, just b/w. Use as template parameter for image. Example:
Definition constixel.hpp:1030
24-bit format. Use as template parameter for image. Example:
Definition constixel.hpp:2193
2-bit color format, 4 colors total. Use as template parameter for image.
Definition constixel.hpp:1316
32-bit format. Use as template parameter for image. Example:
Definition constixel.hpp:2415
4-bit color format, 16 colors total. Use as template parameter for image.
Definition constixel.hpp:1585
8-bit format, 256 colors total. Use as template parameter for image. Example:
Definition constixel.hpp:1886
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