ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
scalar/halfblock.c
Go to the documentation of this file.
1
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <stdint.h>
18#include <stdbool.h>
19
21#include <ascii-chat/common.h>
23#include <ascii-chat/log/log.h>
25
26/* ============================================================================
27 * Scalar Halfblock Rendering
28 * ============================================================================
29 */
30
48char *rgb_to_truecolor_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes) {
49
50 if (width <= 0 || height <= 0)
51 return platform_strdup("");
52 if (stride_bytes <= 0)
53 stride_bytes = width * 3;
54
55 outbuf_t ob = {0};
56 // Estimate: per cell ~ 10-14 bytes (ANSI sequences); half the rows + newlines
57 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
58 ob.cap = est_cells * 14u + (size_t)((height + 1) / 2) * 8u + 64u;
59 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
60 if (!ob.buf)
61 return NULL;
62
63 // Track SGR state (use -1 to indicate "not set")
64 int cur_fr = -1, cur_fg = -1, cur_fb = -1;
65 int cur_br = -1, cur_bg = -1, cur_bb = -1;
66
67 // Upper half-block character: U+2580 = UTF-8 0xE2 0x96 0x80
68 static const char HB[3] = {(char)0xE2, (char)0x96, (char)0x80};
69
70 // Process 2 source rows per output line
71 for (int y = 0; y < height; y += 2) {
72 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
73 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
74
75 int x = 0;
76 while (x < width) {
77 // Read top pixel
78 const uint8_t *pT = rowT + (size_t)x * 3u;
79 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
80
81 // Read bottom pixel (or duplicate top if no bottom row)
82 uint8_t rB = rT, gB = gT, bB = bT;
83 if (rowB) {
84 const uint8_t *pB = rowB + (size_t)x * 3u;
85 rB = pB[0];
86 gB = pB[1];
87 bB = pB[2];
88 }
89
90 // Extend run while top and bottom colors match exactly
91 int j = x + 1;
92 for (; j < width; ++j) {
93 const uint8_t *qT = rowT + (size_t)j * 3u;
94 uint8_t rT2 = qT[0], gT2 = qT[1], bT2 = qT[2];
95
96 uint8_t rB2 = rT2, gB2 = gT2, bB2 = bT2;
97 if (rowB) {
98 const uint8_t *qB = rowB + (size_t)j * 3u;
99 rB2 = qB[0];
100 gB2 = qB[1];
101 bB2 = qB[2];
102 }
103
104 // Check if colors match
105 if (!((rT2 == rT && gT2 == gT && bT2 == bT) && (rB2 == rB && gB2 == gB && bB2 == bB)))
106 break;
107 }
108 uint32_t run = (uint32_t)(j - x);
109
110 // Check if transparent (all black = padding)
111 bool is_transparent = (rT == 0 && gT == 0 && bT == 0 && rB == 0 && gB == 0 && bB == 0);
112
113 if (is_transparent) {
114 // Reset colors before transparent areas to prevent color bleeding
115 if (cur_fr != -1 || cur_fg != -1 || cur_fb != -1 || cur_br != -1 || cur_bg != -1 || cur_bb != -1) {
116 emit_reset(&ob);
117 cur_fr = cur_fg = cur_fb = -1;
118 cur_br = cur_bg = cur_bb = -1;
119 }
120 // Emit spaces for transparent area (no RLE for padding)
121 for (uint32_t k = 0; k < run; ++k) {
122 ob_write(&ob, " ", 1);
123 }
124 } else {
125 // Normal colored halfblocks - set fg to TOP, bg to BOTTOM if changed
126 if (cur_fr != rT || cur_fg != gT || cur_fb != bT) {
127 emit_set_fg(&ob, rT, gT, bT);
128 cur_fr = rT;
129 cur_fg = gT;
130 cur_fb = bT;
131 }
132 if (cur_br != rB || cur_bg != gB || cur_bb != bB) {
133 emit_set_bg(&ob, rB, gB, bB);
134 cur_br = rB;
135 cur_bg = gB;
136 cur_bb = bB;
137 }
138
139 // Emit halfblock once, then RLE or literals
140 ob_write(&ob, HB, 3);
141 if (rep_is_profitable(run)) {
142 emit_rep(&ob, run - 1);
143 } else {
144 for (uint32_t k = 1; k < run; ++k) {
145 ob_write(&ob, HB, 3);
146 }
147 }
148 }
149
150 x = j;
151 }
152
153 // End of line: reset and newline (except for last output line)
154 emit_reset(&ob);
155 if (y + 2 < height) {
156 ob_putc(&ob, '\n');
157 }
158 cur_fr = cur_fg = cur_fb = -1;
159 cur_br = cur_bg = cur_bb = -1;
160 }
161
162 // Ensure null terminator
163 ob_term(&ob);
164 return ob.buf;
165}
166
167/* ============================================================================
168 * Monochrome Halfblock Rendering
169 * ============================================================================
170 */
171
184char *rgb_to_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette) {
185 (void)palette; // Unused for monochrome mode
186 if (width <= 0 || height <= 0)
187 return platform_strdup("");
188 if (stride_bytes <= 0)
189 stride_bytes = width * 3;
190
191 outbuf_t ob = {0};
192 // Estimate: per cell ~ 3 bytes (UTF-8 block shade); half the rows + newlines
193 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
194 ob.cap = est_cells * 3u + (size_t)((height + 1) / 2) * 2u + 64u;
195 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
196 if (!ob.buf)
197 return NULL;
198
199 // Process 2 source rows per output line
200 for (int y = 0; y < height; y += 2) {
201 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
202 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
203
204 int x = 0;
205 while (x < width) {
206 // Read top pixel
207 const uint8_t *pT = rowT + (size_t)x * 3u;
208 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
209
210 // Read bottom pixel (or duplicate top if no bottom row)
211 uint8_t rB = rT, gB = gT, bB = bT;
212 if (rowB) {
213 const uint8_t *pB = rowB + (size_t)x * 3u;
214 rB = pB[0];
215 gB = pB[1];
216 bB = pB[2];
217 }
218
219 // Extend run while colors match exactly (treating as monochrome)
220 int j = x + 1;
221 for (; j < width; ++j) {
222 const uint8_t *qT = rowT + (size_t)j * 3u;
223 uint8_t rT2 = qT[0], gT2 = qT[1], bT2 = qT[2];
224
225 uint8_t rB2 = rT2, gB2 = gT2, bB2 = bT2;
226 if (rowB) {
227 const uint8_t *qB = rowB + (size_t)j * 3u;
228 rB2 = qB[0];
229 gB2 = qB[1];
230 bB2 = qB[2];
231 }
232
233 if (!((rT2 == rT && gT2 == gT && bT2 == bT) && (rB2 == rB && gB2 == gB && bB2 == bB)))
234 break;
235 }
236 uint32_t run = (uint32_t)(j - x);
237
238 // Convert to luminance for monochrome shading
239 uint8_t lum_top = (rT * 76 + gT * 150 + bT * 29) >> 8; // ITU-R BT.601
240 uint8_t lum_bot = (rB * 76 + gB * 150 + bB * 29) >> 8;
241
242 // Check if transparent (both pixels very dark)
243 bool is_transparent = (lum_top < 16 && lum_bot < 16);
244
245 if (is_transparent) {
246 // Emit spaces for transparent area (no RLE for padding)
247 for (uint32_t k = 0; k < run; ++k) {
248 ob_write(&ob, " ", 1);
249 }
250 } else {
251 // Use block characters based on luminance levels
252 // Map luminance (0-255) to block shade characters
253 uint8_t shade_top = (lum_top >> 6); // Map 0-255 to 0-3
254
255 static const char shades[4][3] = {
256 {(char)0xE2, (char)0x96, (char)0x91}, // ░ light shade
257 {(char)0xE2, (char)0x96, (char)0x92}, // ▒ medium shade
258 {(char)0xE2, (char)0x96, (char)0x93}, // ▓ dark shade
259 {(char)0xE2, (char)0x96, (char)0x88} // █ full block
260 };
261
262 // Emit the appropriate shade character
263 const char *shade_char = shades[shade_top];
264 ob_write(&ob, shade_char, 3);
265 if (rep_is_profitable(run)) {
266 emit_rep(&ob, run - 1);
267 } else {
268 for (uint32_t k = 1; k < run; ++k) {
269 ob_write(&ob, shade_char, 3);
270 }
271 }
272 }
273
274 x = j;
275 }
276
277 // End of line: reset and newline (except for last output line)
278 // Note: even monochrome calls reset for structural consistency
279 if (y + 2 < height) {
280 ob_putc(&ob, '\n');
281 }
282 }
283
284 ob_term(&ob);
285 return ob.buf;
286}
287
288/* ============================================================================
289 * 16-Color Halfblock Rendering
290 * ============================================================================
291 */
292
297char *rgb_to_16color_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes,
298 const char *palette) {
299 (void)palette; // Unused for 16-color mode
300 if (width <= 0 || height <= 0)
301 return platform_strdup("");
302 if (stride_bytes <= 0)
303 stride_bytes = width * 3;
304
305 // Initialize 16-color lookup table
307
308 outbuf_t ob = {0};
309 // Estimate: per cell ~ 12 bytes (16-color codes); half the rows + newlines + resets
310 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
311 ob.cap = est_cells * 12u + (size_t)((height + 1) / 2) * 8u + 64u;
312 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
313 if (!ob.buf)
314 return NULL;
315
316 // Track 16-color state
317 int cur_fg = -1, cur_bg = -1;
318 static const char HB[3] = {(char)0xE2, (char)0x96, (char)0x80};
319
320 for (int y = 0; y < height; y += 2) {
321 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
322 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
323
324 int x = 0;
325 while (x < width) {
326 const uint8_t *pT = rowT + (size_t)x * 3u;
327 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
328 uint8_t color_fg = rgb_to_16color(rT, gT, bT);
329
330 uint8_t rB = rT, gB = gT, bB = bT;
331 if (rowB) {
332 const uint8_t *pB = rowB + (size_t)x * 3u;
333 rB = pB[0];
334 gB = pB[1];
335 bB = pB[2];
336 }
337 uint8_t color_bg = rgb_to_16color(rB, gB, bB);
338
339 // Extend run while colors match
340 int j = x + 1;
341 for (; j < width; ++j) {
342 const uint8_t *qT = rowT + (size_t)j * 3u;
343 uint8_t color_fg2 = rgb_to_16color(qT[0], qT[1], qT[2]);
344
345 uint8_t color_bg2 = color_fg2;
346 if (rowB) {
347 const uint8_t *qB = rowB + (size_t)j * 3u;
348 color_bg2 = rgb_to_16color(qB[0], qB[1], qB[2]);
349 }
350
351 if (color_fg2 != color_fg || color_bg2 != color_bg)
352 break;
353 }
354 uint32_t run = (uint32_t)(j - x);
355
356 // Check if transparent
357 bool is_transparent = (rT == 0 && gT == 0 && bT == 0 && rB == 0 && gB == 0 && bB == 0);
358
359 if (is_transparent) {
360 if (cur_fg != -1 || cur_bg != -1) {
361 emit_reset(&ob);
362 cur_fg = cur_bg = -1;
363 }
364 // Emit spaces for transparent area (no RLE for padding)
365 for (uint32_t k = 0; k < run; ++k) {
366 ob_write(&ob, " ", 1);
367 }
368 } else {
369 // Emit 16-color codes if changed
370 if (cur_fg != color_fg) {
371 char buf[16];
372 char *ptr = append_16color_fg(buf, color_fg);
373 ob_write(&ob, buf, (size_t)(ptr - buf));
374 cur_fg = color_fg;
375 }
376 if (cur_bg != color_bg) {
377 char buf[16];
378 char *ptr = append_16color_bg(buf, color_bg);
379 ob_write(&ob, buf, (size_t)(ptr - buf));
380 cur_bg = color_bg;
381 }
382
383 ob_write(&ob, HB, 3);
384 if (rep_is_profitable(run)) {
385 emit_rep(&ob, run - 1);
386 } else {
387 for (uint32_t k = 1; k < run; ++k) {
388 ob_write(&ob, HB, 3);
389 }
390 }
391 }
392
393 x = j;
394 }
395
396 emit_reset(&ob);
397 if (y + 2 < height) {
398 ob_putc(&ob, '\n');
399 }
400 cur_fg = cur_bg = -1;
401 }
402
403 ob_term(&ob);
404 return ob.buf;
405}
406
407/* ============================================================================
408 * 256-Color Halfblock Rendering
409 * ============================================================================
410 */
411
416char *rgb_to_256color_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes,
417 const char *palette) {
418 (void)palette; // Unused for 256-color mode
419 if (width <= 0 || height <= 0)
420 return platform_strdup("");
421 if (stride_bytes <= 0)
422 stride_bytes = width * 3;
423
424 // Initialize 256-color lookup table
426
427 outbuf_t ob = {0};
428 // Estimate: per cell ~ 14 bytes (256-color codes); half the rows + newlines + resets
429 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
430 ob.cap = est_cells * 14u + (size_t)((height + 1) / 2) * 8u + 64u;
431 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
432 if (!ob.buf)
433 return NULL;
434
435 // Track 256-color state
436 int cur_fg = -1, cur_bg = -1;
437 static const char HB[3] = {(char)0xE2, (char)0x96, (char)0x80};
438
439 for (int y = 0; y < height; y += 2) {
440 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
441 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
442
443 int x = 0;
444 while (x < width) {
445 const uint8_t *pT = rowT + (size_t)x * 3u;
446 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
447 uint8_t color_fg = rgb_to_256color(rT, gT, bT);
448
449 uint8_t rB = rT, gB = gT, bB = bT;
450 if (rowB) {
451 const uint8_t *pB = rowB + (size_t)x * 3u;
452 rB = pB[0];
453 gB = pB[1];
454 bB = pB[2];
455 }
456 uint8_t color_bg = rgb_to_256color(rB, gB, bB);
457
458 // Extend run while colors match
459 int j = x + 1;
460 for (; j < width; ++j) {
461 const uint8_t *qT = rowT + (size_t)j * 3u;
462 uint8_t color_fg2 = rgb_to_256color(qT[0], qT[1], qT[2]);
463
464 uint8_t color_bg2 = color_fg2;
465 if (rowB) {
466 const uint8_t *qB = rowB + (size_t)j * 3u;
467 color_bg2 = rgb_to_256color(qB[0], qB[1], qB[2]);
468 }
469
470 if (color_fg2 != color_fg || color_bg2 != color_bg)
471 break;
472 }
473 uint32_t run = (uint32_t)(j - x);
474
475 // Check if transparent
476 bool is_transparent = (rT == 0 && gT == 0 && bT == 0 && rB == 0 && gB == 0 && bB == 0);
477
478 if (is_transparent) {
479 if (cur_fg != -1 || cur_bg != -1) {
480 emit_reset(&ob);
481 cur_fg = cur_bg = -1;
482 }
483 // Emit spaces for transparent area (no RLE for padding)
484 for (uint32_t k = 0; k < run; ++k) {
485 ob_write(&ob, " ", 1);
486 }
487 } else {
488 // Emit 256-color codes if changed
489 if (cur_fg != color_fg) {
490 char buf[16];
491 char *ptr = append_256color_fg(buf, color_fg);
492 ob_write(&ob, buf, (size_t)(ptr - buf));
493 cur_fg = color_fg;
494 }
495 if (cur_bg != color_bg) {
496 char buf[16];
497 char *ptr = append_256color_bg(buf, color_bg);
498 ob_write(&ob, buf, (size_t)(ptr - buf));
499 cur_bg = color_bg;
500 }
501
502 ob_write(&ob, HB, 3);
503 if (rep_is_profitable(run)) {
504 emit_rep(&ob, run - 1);
505 } else {
506 for (uint32_t k = 1; k < run; ++k) {
507 ob_write(&ob, HB, 3);
508 }
509 }
510 }
511
512 x = j;
513 }
514
515 emit_reset(&ob);
516 if (y + 2 < height) {
517 ob_putc(&ob, '\n');
518 }
519 cur_fg = cur_bg = -1;
520 }
521
522 ob_term(&ob);
523 return ob.buf;
524}
Platform abstraction layer umbrella header providing unified cross-platform API.
ANSI escape sequence utilities and fast color code generation.
⚙️ Common definitions, error codes, macros, and types shared throughout the application
char * append_16color_fg(char *dst, uint8_t color_index)
Definition ansi.c:405
char * append_16color_bg(char *dst, uint8_t color_index)
Definition ansi.c:421
char * append_256color_bg(char *dst, uint8_t color_index)
Definition ansi.c:352
void ansi_fast_init_256color(void)
Definition ansi.c:334
uint8_t rgb_to_16color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:437
char * append_256color_fg(char *dst, uint8_t color_index)
Definition ansi.c:344
uint8_t rgb_to_256color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:360
void ansi_fast_init_16color(void)
Definition ansi.c:398
unsigned int uint32_t
Definition common.h:58
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned char uint8_t
Definition common.h:56
char * platform_strdup(const char *s)
Duplicate string (strdup replacement)
char * rgb_to_truecolor_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes)
Scalar truecolor halfblock renderer.
char * rgb_to_16color_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
Scalar 16-color halfblock renderer Uses 16-color ANSI codes for foreground (top) and implicit backgro...
void emit_set_bg(outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
Emit background color sequence (auto-select mode)
void ob_term(outbuf_t *ob)
Append null terminator to buffer.
void ob_putc(outbuf_t *ob, char c)
Append a character to buffer.
bool rep_is_profitable(uint32_t runlen)
Check if run-length encoding is profitable.
char * rgb_to_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
Scalar monochrome halfblock renderer (no color codes)
void emit_rep(outbuf_t *ob, uint32_t extra)
Emit run-length encoded sequence.
void ob_write(outbuf_t *ob, const char *s, size_t n)
Append a string to buffer.
void emit_reset(outbuf_t *ob)
Emit ANSI reset sequence.
char * rgb_to_256color_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
Scalar 256-color halfblock renderer Uses 256-color ANSI codes for foreground (top) and implicit backg...
void emit_set_fg(outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
Emit foreground color sequence (auto-select mode)
📝 Logging API with multiple log levels and terminal output control
ClangTool/LibTooling compatibility shim for stdbool.h.
Dynamic output buffer (auto-expanding)
size_t cap
Buffer capacity in bytes (maximum length before reallocation)
char * buf
Buffer pointer (allocated, owned by caller, must be freed)