ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
foreground.c
Go to the documentation of this file.
1
7#include <stdbool.h>
8#include <stddef.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <math.h>
14
15#include <ascii-chat/common.h>
26
27char *image_print(const image_t *p, const char *palette) {
28 if (!p || !palette) {
29 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: p=%p or palette=%p is NULL", p, palette);
30 return NULL;
31 }
32 if (!p->pixels) {
33 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: p->pixels is NULL");
34 return NULL;
35 }
36
37 const int h = p->h;
38 const int w = p->w;
39
40 if (h <= 0 || w <= 0) {
41 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: invalid dimensions h=%d, w=%d", h, w);
42 return NULL;
43 }
44
45 // Get UTF-8 character cache for proper multi-byte character support
46 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
47 if (!utf8_cache) {
48 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 palette cache for scalar rendering");
49 return NULL;
50 }
51
52 // Character index ramp is now part of UTF-8 cache - no separate cache needed
53
54 // Need space for h rows with UTF-8 characters, plus h-1 newlines, plus null terminator
55 const size_t max_char_bytes = 4; // Max UTF-8 character size
56
57 const rgb_pixel_t *pix = p->pixels;
58
59 // Use outbuf_t for efficient UTF-8 RLE emission (same as SIMD renderers)
60 outbuf_t ob = {0};
61
62 // Calculate buffer size with overflow checking
63 size_t w_times_bytes;
64 if (checked_size_mul((size_t)w, max_char_bytes, &w_times_bytes) != ASCIICHAT_OK) {
65 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: width too large for UTF-8 encoding");
66 return NULL;
67 }
68
69 size_t w_times_bytes_plus_one;
70 if (checked_size_add(w_times_bytes, 1, &w_times_bytes_plus_one) != ASCIICHAT_OK) {
71 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: width * bytes + 1 overflow");
72 return NULL;
73 }
74
75 if (checked_size_mul((size_t)h, w_times_bytes_plus_one, &ob.cap) != ASCIICHAT_OK) {
76 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: height * (width * bytes + 1) overflow");
77 return NULL;
78 }
79
80 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
81 if (!ob.buf) {
82 SET_ERRNO(ERROR_MEMORY, "Failed to allocate output buffer for scalar rendering");
83 return NULL;
84 }
85
86 // Process pixels with UTF-8 RLE emission (same approach as SIMD)
87 for (int y = 0; y < h; y++) {
88 const int row_offset = y * w;
89
90 for (int x = 0; x < w;) {
91 const rgb_pixel_t pixel = pix[row_offset + x];
92 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
93 const int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
94
95 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
96 uint8_t safe_luminance = clamp_rgb(luminance);
97 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
98 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
99
100 // cache64 is indexed by luminance bucket; char_idx is only for run matching.
101 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
102
103 // Find run length for same character (RLE optimization)
104 int j = x + 1;
105 while (j < w) {
106 const rgb_pixel_t next_pixel = pix[row_offset + j];
107 const int next_luminance = (77 * next_pixel.r + 150 * next_pixel.g + 29 * next_pixel.b + 128) >> 8;
108 uint8_t next_safe_luminance = clamp_rgb(next_luminance);
109 uint8_t next_luma_idx = (uint8_t)(next_safe_luminance >> 2); // 0-63 index (same as SIMD)
110 uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx]; // Map to character index (same as SIMD)
111 if (next_char_idx != char_idx)
112 break;
113 j++;
114 }
115 uint32_t run = (uint32_t)(j - x);
116
117 // Emit UTF-8 character with RLE (same as SIMD)
118 ob_write(&ob, (const char *)char_info->utf8_bytes, char_info->byte_len);
119 if (rep_is_profitable(run)) {
120 emit_rep(&ob, run - 1);
121 } else {
122 for (uint32_t k = 1; k < run; k++) {
123 ob_write(&ob, (const char *)char_info->utf8_bytes, char_info->byte_len);
124 }
125 }
126 x = j;
127 }
128
129 // Add newline between rows (except last row)
130 if (y != h - 1) {
131 ob_putc(&ob, '\n');
132 }
133 }
134
135 ob_term(&ob);
136 return ob.buf;
137}
138
139// Color quantization to reduce frame size and improve performance
140void quantize_color(int *r, int *g, int *b, int levels) {
141 if (!r || !g || !b) {
142 SET_ERRNO(ERROR_INVALID_PARAM, "quantize_color: r, g, or b is NULL");
143 return;
144 }
145
146 if (levels <= 0) {
147 SET_ERRNO(ERROR_INVALID_PARAM, "quantize_color: levels must be positive, got %d", levels);
148 return;
149 }
150
151 int step = 256 / levels;
152 *r = (*r / step) * step;
153 *g = (*g / step) * step;
154 *b = (*b / step) * step;
155}
156
191// Legacy truecolor foreground renderer using RLE optimization.
192// Only supports single-byte ASCII palette characters. For multi-byte UTF-8
193// palettes (blocks, digital, cool), use image_print_color_utf8 instead.
194char *image_print_color(const image_t *p, const char *palette) {
195 if (!p || !palette) {
196 SET_ERRNO(ERROR_INVALID_PARAM, "p=%p or palette=%p is NULL", p, palette);
197 return NULL;
198 }
199 if (!p->pixels) {
200 SET_ERRNO(ERROR_INVALID_PARAM, "p->pixels is NULL");
201 return NULL;
202 }
203
204 // Get UTF-8 character cache for proper multi-byte character support
205 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
206 if (!utf8_cache) {
207 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 palette cache for scalar color rendering");
208 return NULL;
209 }
210
211 const int h = p->h;
212 const int w = p->w;
213
214 // Constants for ANSI escape codes (using exact sizes from ansi_fast.c)
215 const size_t max_fg_ansi = 19; // \033[38;2;255;255;255m
216 const size_t max_bg_ansi = 19; // \033[48;2;255;255;255m
217 const size_t reset_len = 4; // \033[0m
218
219 const size_t h_sz = (size_t)h;
220 const size_t w_sz = (size_t)w;
221
222 // Ensure h * w won't overflow
223 if (h_sz > 0 && w_sz > SIZE_MAX / h_sz) {
224 SET_ERRNO(ERROR_INVALID_STATE, "Image dimensions too large: %d x %d", h, w);
225 return NULL;
226 }
227
228 const size_t total_pixels = h_sz * w_sz;
229 const size_t bytes_per_pixel = 1 + max_fg_ansi + max_bg_ansi; // Conservative estimate for max possible
230
231 // Ensure total_pixels * bytes_per_pixel won't overflow
232 if (total_pixels > SIZE_MAX / bytes_per_pixel) {
233 SET_ERRNO(ERROR_INVALID_STATE, "Pixel data too large for buffer: %d x %d", h, w);
234 return NULL;
235 }
236
237 const size_t pixel_bytes = total_pixels * bytes_per_pixel;
238
239 // Per row: newline (except last row)
240 // Final reset added once by ansi_rle_finish()
241 const size_t total_newlines = (h_sz > 0) ? (h_sz - 1) : 0;
242 const size_t final_reset = reset_len; // One reset at end (from ansi_rle_finish)
243
244 // Final buffer size: pixel bytes + per-row newlines + final reset + null terminator
245 const size_t extra_bytes = total_newlines + final_reset + 1;
246
247 if (pixel_bytes > SIZE_MAX - extra_bytes) {
248 SET_ERRNO(ERROR_INVALID_STATE, "Final buffer size would overflow: %d x %d", h, w);
249 return NULL;
250 }
251
252 const size_t lines_size = pixel_bytes + extra_bytes;
253 char *lines;
254 lines = SAFE_MALLOC(lines_size, char *);
255
256 const rgb_pixel_t *pix = p->pixels;
257 // char *current_pos = lines;
258 // const char *buffer_end = lines + lines_size - 1; // reserve space for '\0'
259
260 // Initialize the optimized RLE context for color sequence caching
261 // Note: This function should be called via image_print_with_capabilities() for proper per-client rendering
262 ansi_rle_context_t rle_ctx;
263 ansi_color_mode_t color_mode = ANSI_MODE_FOREGROUND; // Default to foreground-only for legacy usage
264 ansi_rle_init(&rle_ctx, lines, lines_size, color_mode);
265
266 // Process each pixel using the optimized RLE context
267 for (int y = 0; y < h; y++) {
268 const int row_offset = y * w;
269
270 for (int x = 0; x < w; x++) {
271 const rgb_pixel_t pixel = pix[row_offset + x];
272 int r = pixel.r, g = pixel.g, b = pixel.b;
273 // Standard ITU-R BT.601 luminance calculation (uses original values for accuracy)
274 const int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
275
276 // Use UTF-8 character cache for proper character selection
277 uint8_t safe_luminance = clamp_rgb(luminance);
278 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
279
280 // For single-byte ASCII, use RLE optimization; for multi-byte UTF-8, write directly
281 if (char_info->byte_len == 1 && (unsigned char)char_info->utf8_bytes[0] < 128) {
282 // ASCII character - use RLE optimization
283 const char ascii_char = char_info->utf8_bytes[0];
284 ansi_rle_add_pixel(&rle_ctx, (uint8_t)r, (uint8_t)g, (uint8_t)b, ascii_char);
285 } else {
286 // Multi-byte UTF-8 character - write color code and full UTF-8 bytes directly
287 int written = SAFE_SNPRINTF((char *)&rle_ctx.buffer[rle_ctx.length], rle_ctx.capacity - rle_ctx.length,
288 "\033[38;2;%d;%d;%dm", r, g, b);
289 rle_ctx.length += written;
290 // Write UTF-8 bytes
291 for (int i = 0; i < char_info->byte_len && rle_ctx.length < rle_ctx.capacity; i++) {
292 rle_ctx.buffer[rle_ctx.length++] = char_info->utf8_bytes[i];
293 }
294 }
295 }
296
297 // Add newline after each row (except the last row)
298 if (y != h - 1 && rle_ctx.length < rle_ctx.capacity - 1) {
299 rle_ctx.buffer[rle_ctx.length++] = '\n';
300 }
301 }
302
303 ansi_rle_finish(&rle_ctx);
304
305 return lines;
306}
307
308// Truecolor foreground renderer with full UTF-8 support (no RLE)
309// Used by non-SIMD builds (WASM) where the RLE system truncates multi-byte characters.
310char *image_print_color_utf8(const image_t *p, const char *palette) {
311 if (!p || !palette || !p->pixels) {
312 return NULL;
313 }
314
315 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
316 if (!utf8_cache) {
317 return NULL;
318 }
319
320 const int h = p->h;
321 const int w = p->w;
322 const size_t h_sz = (size_t)h;
323 const size_t w_sz = (size_t)w;
324
325 if (h_sz > 0 && w_sz > SIZE_MAX / h_sz) {
326 return NULL;
327 }
328
329 const size_t bytes_per_pixel = 30; // fg sequence (~19) + char (up to 4) + margin
330 const size_t total_pixels = h_sz * w_sz;
331 const size_t lines_size = total_pixels * bytes_per_pixel + h_sz + 16;
332
333 char *lines;
334 lines = SAFE_MALLOC(lines_size, char *);
335
336 char *ptr = lines;
337 const rgb_pixel_t *pix = p->pixels;
338
339 for (int y = 0; y < h; y++) {
340 const int row_offset = y * w;
341
342 for (int x = 0; x < w; x++) {
343 const rgb_pixel_t pixel = pix[row_offset + x];
344
345 ptr += SAFE_SNPRINTF(ptr, 20, "\033[38;2;%d;%d;%dm", pixel.r, pixel.g, pixel.b);
346
347 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
348 uint8_t safe_luminance = clamp_rgb(luminance);
349 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
350 for (int i = 0; i < char_info->byte_len; i++) {
351 *ptr++ = char_info->utf8_bytes[i];
352 }
353 }
354
355 *ptr++ = '\033';
356 *ptr++ = '[';
357 *ptr++ = '0';
358 *ptr++ = 'm';
359 if (y < h - 1) {
360 *ptr++ = '\n';
361 }
362 }
363
364 *ptr = '\0';
365 return lines;
366}
367
368// RGB to ANSI color conversion functions
369char *rgb_to_ansi_fg(int r, int g, int b) {
370 _Thread_local static char color_code[20]; // \033[38;2;255;255;255m + \0 = 20 bytes max
371 SAFE_SNPRINTF(color_code, sizeof(color_code), "\033[38;2;%d;%d;%dm", r, g, b);
372 return color_code;
373}
374
375char *rgb_to_ansi_bg(int r, int g, int b) {
376 _Thread_local static char color_code[20]; // \033[48;2;255;255;255m + \0 = 20 bytes max
377 SAFE_SNPRINTF(color_code, sizeof(color_code), "\033[48;2;%d;%d;%dm", r, g, b);
378 return color_code;
379}
380
381void rgb_to_ansi_8bit(int r, int g, int b, int *fg_code, int *bg_code) {
382 if (!fg_code || !bg_code) {
383 SET_ERRNO(ERROR_INVALID_PARAM, "rgb_to_ansi_8bit: fg_code or bg_code is NULL");
384 return;
385 }
386
387 // Convert RGB to 8-bit color code (216 color cube + 24 grayscale)
388 if (r == g && g == b) {
389 // Grayscale
390 if (r < 8) {
391 *fg_code = 16;
392 } else if (r > 248) {
393 *fg_code = 231;
394 } else {
395 *fg_code = 232 + (r - 8) / 10;
396 }
397 } else {
398 // Color cube: 16 + 36*r + 6*g + b where r,g,b are 0-5
399 int r_level = (r * 5) / 255;
400 int g_level = (g * 5) / 255;
401 int b_level = (b * 5) / 255;
402 *fg_code = 16 + 36 * r_level + 6 * g_level + b_level;
403 }
404 *bg_code = *fg_code; // Same logic for background
405}
406
407// Capability-aware image printing function
414#include <stdbool.h>
415#include <stddef.h>
416#include <stdint.h>
417#include <stdio.h>
418#include <stdlib.h>
419#include <string.h>
420#include <math.h>
421
422#include <ascii-chat/common.h>
429#include <ascii-chat/util/math.h>
430
431char *image_print_256color(const image_t *image, const char *palette) {
432 if (!image || !image->pixels || !palette) {
433 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
434 return NULL;
435 }
436
437 int h = image->h;
438 int w = image->w;
439
440 if (h <= 0 || w <= 0) {
441 SET_ERRNO(ERROR_INVALID_STATE, "image_print_256color: invalid dimensions h=%d, w=%d", h, w);
442 return NULL;
443 }
444
446
447 // Buffer size: 256-color ANSI codes are "\033[38;5;NNNm" (max 13 bytes) + char + reset + newline
448 size_t h_times_w;
449 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w) != ASCIICHAT_OK) {
450 return NULL;
451 }
452
453 size_t h_times_w_times_18;
454 if (checked_size_mul(h_times_w, 18u, &h_times_w_times_18) != ASCIICHAT_OK) {
455 return NULL;
456 }
457
458 size_t buffer_size;
459 if (checked_size_add(h_times_w_times_18, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
460 return NULL;
461 }
462
463 char *buffer = SAFE_MALLOC(buffer_size, char *);
464 char *ptr = buffer;
465 const char *reset_code = "\033[0m";
466
467 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
468 if (!utf8_cache) {
469 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache for 256-color rendering");
470 SAFE_FREE(buffer);
471 return NULL;
472 }
473
474 for (int y = 0; y < h; y++) {
475 for (int x = 0; x < w; x++) {
476 rgb_pixel_t pixel = image->pixels[y * w + x];
477
478 uint8_t color_index = rgb_to_256color(pixel.r, pixel.g, pixel.b);
479 ptr = append_256color_fg(ptr, color_index);
480
481 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
482
483 uint8_t safe_luminance = clamp_rgb(luminance);
484
485 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
486
487 if (char_info && char_info->byte_len > 0) {
488 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
489 *ptr++ = char_info->utf8_bytes[byte_idx];
490 }
491 } else {
492 size_t palette_len = strlen(palette);
493 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
494 *ptr++ = palette[palette_index];
495 }
496 }
497
498 SAFE_STRNCPY(ptr, reset_code, 5);
499 ptr += strlen(reset_code);
500 if (y < h - 1) {
501 *ptr++ = '\n';
502 }
503 }
504
505 *ptr = '\0';
506 return buffer;
507}
508
509// 16-color image printing function using ansi_fast color conversion
516#include <stdbool.h>
517#include <stddef.h>
518#include <stdint.h>
519#include <stdio.h>
520#include <stdlib.h>
521#include <string.h>
522#include <math.h>
523
524#include <ascii-chat/common.h>
531#include <ascii-chat/util/math.h>
532
533char *image_print_16color(const image_t *image, const char *palette) {
534 if (!image || !image->pixels || !palette) {
535 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
536 return NULL;
537 }
538
539 int h = image->h;
540 int w = image->w;
541
542 if (h <= 0 || w <= 0) {
543 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color: invalid dimensions h=%d, w=%d", h, w);
544 return NULL;
545 }
546
547 // Initialize 16-color lookup table
549
550 // Calculate buffer size (smaller than 256-color due to shorter ANSI sequences)
551 // Space for ANSI codes + newlines
552 // Calculate with overflow checking
553 size_t h_times_w;
554 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w) != ASCIICHAT_OK) {
555 return NULL;
556 }
557
558 size_t h_times_w_times_12;
559 if (checked_size_mul(h_times_w, 12u, &h_times_w_times_12) != ASCIICHAT_OK) {
560 return NULL;
561 }
562
563 size_t buffer_size;
564 if (checked_size_add(h_times_w_times_12, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
565 return NULL;
566 }
567
568 char *buffer;
569 buffer = SAFE_MALLOC(buffer_size, char *);
570
571 char *ptr = buffer;
572 const char *reset_code = "\033[0m";
573
574 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
575 if (!utf8_cache) {
576 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache for 16-color rendering");
577 SAFE_FREE(buffer);
578 return NULL;
579 }
580
581 for (int y = 0; y < h; y++) {
582 for (int x = 0; x < w; x++) {
583 rgb_pixel_t pixel = image->pixels[y * w + x];
584
585 // Convert RGB to 16-color index and generate ANSI sequence
586 uint8_t color_index = rgb_to_16color(pixel.r, pixel.g, pixel.b);
587 ptr = append_16color_fg(ptr, color_index);
588
589 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
590 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
591
592 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
593 uint8_t safe_luminance = clamp_rgb(luminance);
594 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
595 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
596
597 if (char_info) {
598 // Copy UTF-8 character bytes
599 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
600 *ptr++ = char_info->utf8_bytes[byte_idx];
601 }
602 } else {
603 // Fallback to simple ASCII if UTF-8 cache fails
604 size_t palette_len = strlen(palette);
605 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
606 *ptr++ = palette[palette_index];
607 }
608 }
609
610 // Add reset and newline at end of each row
611 SAFE_STRNCPY(ptr, reset_code, 5);
612 ptr += strlen(reset_code);
613 if (y < h - 1) {
614 *ptr++ = '\n';
615 }
616 }
617
618 *ptr = '\0';
619 return buffer;
620}
621
622// 16-color image printing with Floyd-Steinberg dithering
629#include <stdbool.h>
630#include <stddef.h>
631#include <stdint.h>
632#include <stdio.h>
633#include <stdlib.h>
634#include <string.h>
635#include <math.h>
636
637#include <ascii-chat/common.h>
644#include <ascii-chat/util/math.h>
645
646char *image_print_16color_dithered(const image_t *image, const char *palette) {
647 if (!image || !image->pixels || !palette) {
648 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
649 return NULL;
650 }
651
652 int h = image->h;
653 int w = image->w;
654
655 if (h <= 0 || w <= 0) {
656 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color_dithered: invalid dimensions h=%d, w=%d", h, w);
657 return NULL;
658 }
659
660 // Initialize 16-color lookup table
662
663 // Allocate error buffer for Floyd-Steinberg dithering
664 size_t pixel_count = (size_t)h * (size_t)w;
665 rgb_error_t *error_buffer;
666 error_buffer = SAFE_CALLOC(pixel_count, sizeof(rgb_error_t), rgb_error_t *);
667
668 // Calculate buffer size (same as non-dithered version)
669 // Space for ANSI codes + newlines
670 // Calculate with overflow checking
671 size_t h_times_w2;
672 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w2) != ASCIICHAT_OK) {
673 SAFE_FREE(error_buffer);
674 return NULL;
675 }
676
677 size_t h_times_w2_times_12;
678 if (checked_size_mul(h_times_w2, 12u, &h_times_w2_times_12) != ASCIICHAT_OK) {
679 SAFE_FREE(error_buffer);
680 return NULL;
681 }
682
683 size_t buffer_size;
684 if (checked_size_add(h_times_w2_times_12, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
685 SAFE_FREE(error_buffer);
686 return NULL;
687 }
688
689 char *buffer;
690 buffer = SAFE_MALLOC(buffer_size, char *);
691
692 char *ptr = buffer;
693 const char *reset_code = "\033[0m";
694
695 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
696 if (!utf8_cache) {
697 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache for 16-color dithered rendering");
698 SAFE_FREE(buffer);
699 SAFE_FREE(error_buffer);
700 return NULL;
701 }
702
703 for (int y = 0; y < h; y++) {
704 for (int x = 0; x < w; x++) {
705 rgb_pixel_t pixel = image->pixels[y * w + x];
706
707 // Convert RGB to 16-color index using dithering
708 uint8_t color_index = rgb_to_16color_dithered(pixel.r, pixel.g, pixel.b, x, y, w, h, error_buffer);
709 ptr = append_16color_fg(ptr, color_index);
710
711 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
712 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
713
714 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
715 uint8_t safe_luminance = clamp_rgb(luminance);
716 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
717 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
718
719 if (char_info) {
720 // Copy UTF-8 character bytes
721 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
722 *ptr++ = char_info->utf8_bytes[byte_idx];
723 }
724 } else {
725 // Fallback to simple ASCII if UTF-8 cache fails
726 size_t palette_len = strlen(palette);
727 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
728 *ptr++ = palette[palette_index];
729 }
730 }
731
732 // Add reset and newline at end of each row
733 SAFE_STRNCPY(ptr, reset_code, 5);
734 ptr += strlen(reset_code);
735 if (y < h - 1) {
736 *ptr++ = '\n';
737 }
738 }
739
740 *ptr = '\0';
741 SAFE_FREE(error_buffer); // Clean up error buffer
742 return buffer;
743}
744
745// 16-color image printing with Floyd-Steinberg dithering and background mode support
746char *image_print_16color_dithered_with_background(const image_t *image, bool use_background, const char *palette) {
747 if (!image || !image->pixels || !palette) {
748 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
749 return NULL;
750 }
751
752 int h = image->h;
753 int w = image->w;
754
755 if (h <= 0 || w <= 0) {
756 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color_dithered_with_background: invalid dimensions h=%d, w=%d", h, w);
757 return NULL;
758 }
759
760 // Initialize 16-color lookup table
762
763 // Allocate error buffer for Floyd-Steinberg dithering
764 size_t pixel_count = (size_t)h * (size_t)w;
765 rgb_error_t *error_buffer;
766 error_buffer = SAFE_CALLOC(pixel_count, sizeof(rgb_error_t), rgb_error_t *);
767
768 // Calculate buffer size (larger for background mode due to more ANSI sequences)
769 size_t buffer_size =
770 (size_t)h * (size_t)w * (use_background ? 24 : 12) + (size_t)h; // Space for ANSI codes + newlines
771 char *buffer;
772 buffer = SAFE_MALLOC(buffer_size, char *);
773
774 char *ptr = buffer;
775 const char *reset_code = "\033[0m";
776
777 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
778 if (!utf8_cache) {
779 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache for 16-color dithered bg rendering");
780 SAFE_FREE(buffer);
781 SAFE_FREE(error_buffer);
782 return NULL;
783 }
784
785 for (int y = 0; y < h; y++) {
786 for (int x = 0; x < w; x++) {
787 rgb_pixel_t pixel = image->pixels[y * w + x];
788
789 // Convert RGB to 16-color index using dithering
790 uint8_t color_index = rgb_to_16color_dithered(pixel.r, pixel.g, pixel.b, x, y, w, h, error_buffer);
791
792 if (use_background) {
793 // Background mode: use contrasting foreground on background color
794 uint8_t bg_r, bg_g, bg_b;
795 get_16color_rgb(color_index, &bg_r, &bg_g, &bg_b);
796
797 // Calculate luminance to choose contrasting foreground
798 int bg_luminance = (bg_r * 77 + bg_g * 150 + bg_b * 29) / 256;
799 uint8_t fg_color = (bg_luminance < 127) ? 15 : 0; // White on dark, black on bright
800
801 ptr = append_16color_bg(ptr, color_index); // Set background to pixel color
802 ptr = append_16color_fg(ptr, fg_color); // Set contrasting foreground
803 } else {
804 // Foreground mode: set foreground to pixel color
805 ptr = append_16color_fg(ptr, color_index);
806 }
807
808 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
809 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
810
811 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
812 uint8_t safe_luminance = clamp_rgb(luminance);
813
814 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
815
816 if (char_info) {
817 // Copy UTF-8 character bytes
818 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
819 *ptr++ = char_info->utf8_bytes[byte_idx];
820 }
821 } else {
822 // Fallback to simple ASCII if UTF-8 cache fails
823 size_t palette_len = strlen(palette);
824 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
825 *ptr++ = palette[palette_index];
826 }
827 }
828
829 // Add reset and newline at end of each row
830 SAFE_STRNCPY(ptr, reset_code, 5);
831 ptr += strlen(reset_code);
832 if (y < h - 1) {
833 *ptr++ = '\n';
834 }
835 }
836
837 *ptr = '\0';
838 SAFE_FREE(error_buffer); // Clean up error buffer
839 return buffer;
840}
ANSI escape sequence utilities and fast color code generation.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
⚙️ Common definitions, error codes, macros, and types shared throughout the application
char * image_print_color_utf8(const image_t *p, const char *palette)
Definition foreground.c:310
int buffer_size
Size of circular buffer.
Definition grep.c:90
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
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
uint8_t rgb_to_16color_dithered(int r, int g, int b, int x, int y, int width, int height, rgb_error_t *error_buffer)
Definition ansi.c:511
ansi_color_mode_t
Definition ansi.h:38
void ansi_rle_init(ansi_rle_context_t *ctx, char *buffer, size_t capacity, ansi_color_mode_t mode)
Definition ansi.c:248
void ansi_rle_finish(ansi_rle_context_t *ctx)
Definition ansi.c:303
void get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b)
Definition ansi.c:480
void ansi_rle_add_pixel(ansi_rle_context_t *ctx, uint8_t r, uint8_t g, uint8_t b, char ascii_char)
Definition ansi.c:261
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
@ ANSI_MODE_FOREGROUND
Definition ansi.h:39
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
void quantize_color(int *r, int *g, int *b, int levels)
Quantize color to specified number of levels.
Definition foreground.c:140
char * image_print_16color_dithered(const image_t *image, const char *palette)
Print image using 16-color ANSI mode with dithering.
Definition foreground.c:646
char * image_print(const image_t *p, const char *palette)
Print image as ASCII art (monochrome)
Definition foreground.c:27
uint8_t utf8_bytes[4]
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)
Get UTF-8 palette cache for a character set.
void ob_term(outbuf_t *ob)
Append null terminator to buffer.
char * image_print_color(const image_t *p, const char *palette)
Print image as ASCII art with color.
Definition foreground.c:194
char * rgb_to_ansi_bg(int r, int g, int b)
Convert RGB to ANSI background color code.
Definition foreground.c:375
void rgb_to_ansi_8bit(int r, int g, int b, int *fg_code, int *bg_code)
Convert RGB to 8-bit ANSI color codes.
Definition foreground.c:381
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 * image_print_16color(const image_t *image, const char *palette)
Print image using 16-color ANSI mode.
Definition foreground.c:533
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.
utf8_char_t cache64[64]
char * rgb_to_ansi_fg(int r, int g, int b)
Convert RGB to ANSI foreground color code.
Definition foreground.c:369
utf8_char_t cache[256]
char * image_print_16color_dithered_with_background(const image_t *image, bool use_background, const char *palette)
Print image using 16-color ANSI mode with dithering and background colors.
Definition foreground.c:746
char * image_print_256color(const image_t *image, const char *palette)
Print image using 256-color ANSI mode.
Definition foreground.c:431
🔢 Mathematical Utility Functions
✅ Safe Integer Arithmetic and Overflow Detection
ClangTool/LibTooling compatibility shim for stdbool.h.
size_t capacity
Definition ansi.h:47
char * buffer
Definition ansi.h:46
size_t length
Definition ansi.h:48
Image structure.
int w
Image width in pixels (must be > 0)
int h
Image height in pixels (must be > 0)
rgb_pixel_t * pixels
Pixel data array (width * height RGB pixels, row-major order)
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)
RGB pixel structure.
uint8_t b
Blue color component (0-255)
uint8_t g
Green color component (0-255)
uint8_t r
Red color component (0-255)
🖼️ Safe overflow-checked buffer size calculations for images and video frames
SIMD-optimized ASCII conversion interface.