ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
foreground.h File Reference

Go to the source code of this file.

Functions

char * image_print (const image_t *p, const char *palette)
 
char * image_print_color (const image_t *p, const char *palette)
 
char * image_print_256color (const image_t *p, const char *palette)
 
char * image_print_16color (const image_t *p, const char *palette)
 
char * image_print_color_utf8 (const image_t *p, const char *palette)
 
char * image_print_16color_dithered (const image_t *p, const char *palette)
 
char * image_print_16color_dithered_with_background (const image_t *image, bool use_background, const char *palette)
 

Function Documentation

◆ image_print()

char * image_print ( const image_t *  p,
const char *  palette 
)

Definition at line 27 of file foreground.c.

27 {
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}
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
#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
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.
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.
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]
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)

◆ image_print_16color()

char * image_print_16color ( const image_t *  p,
const char *  palette 
)

Definition at line 533 of file foreground.c.

533 {
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}
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
uint8_t rgb_to_16color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:437
void ansi_fast_init_16color(void)
Definition ansi.c:398
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376

◆ image_print_16color_dithered()

char * image_print_16color_dithered ( const image_t *  p,
const char *  palette 
)

Definition at line 646 of file foreground.c.

646 {
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}
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
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274

◆ image_print_16color_dithered_with_background()

char * image_print_16color_dithered_with_background ( const image_t *  image,
bool  use_background,
const char *  palette 
)

Definition at line 746 of file foreground.c.

746 {
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}
char * append_16color_bg(char *dst, uint8_t color_index)
Definition ansi.c:421
void get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b)
Definition ansi.c:480
utf8_char_t cache[256]

◆ image_print_256color()

char * image_print_256color ( const image_t *  p,
const char *  palette 
)

Definition at line 431 of file foreground.c.

431 {
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}
void ansi_fast_init_256color(void)
Definition ansi.c:334
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

◆ image_print_color()

char * image_print_color ( const image_t *  p,
const char *  palette 
)

Converts an image to colored ASCII art with ANSI escape codes.

This function generates a string representation of an image where each pixel is converted to an ASCII character with ANSI color codes. The character is chosen based on luminance, and colors are applied using 24-bit RGB ANSI escape sequences.

Buffer allocation is precisely calculated to avoid waste and prevent overflows:

  • Each pixel: 1 ASCII char + foreground ANSI code (19 bytes max)
  • Background mode: adds background ANSI code (19 bytes max per pixel)
  • Each row: reset sequence (\033[0m = 4 bytes) + newline (except last row)
  • At the end: null terminator (1 byte)

Color modes:

  • Foreground only (default): ASCII characters with colored foreground
  • Background mode (opt_background_color): colored background with contrasting foreground (black on bright backgrounds, white on dark backgrounds)

ANSI escape code format:

  • Foreground: \033[38;2;R;G;Bm (11-19 bytes depending on RGB values)
  • Background: \033[48;2;R;G;Bm (11-19 bytes depending on RGB values)
  • Reset: \033[0m (4 bytes)
Parameters
pPointer to image_t structure containing pixel data
Returns
Dynamically allocated string containing colored ASCII art, or NULL on error. Caller is responsible for freeing the returned string.
Note
The function performs overflow checks to prevent integer overflow when calculating buffer sizes for very large images.
Uses global opt_background_color to determine color mode.
Exits with ERROR_BUFFER if buffer overflow is detected during string construction (should never happen with correct calculation).

Definition at line 194 of file foreground.c.

194 {
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}
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 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
@ ANSI_MODE_FOREGROUND
Definition ansi.h:39
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
size_t capacity
Definition ansi.h:47
char * buffer
Definition ansi.h:46
size_t length
Definition ansi.h:48

◆ image_print_color_utf8()

char * image_print_color_utf8 ( const image_t *  p,
const char *  palette 
)

Definition at line 310 of file foreground.c.

310 {
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}

References rgb_pixel_t::b, utf8_char_t::byte_len, utf8_palette_cache::cache, rgb_pixel_t::g, get_utf8_palette_cache(), image_t::h, image_t::pixels, rgb_pixel_t::r, SAFE_MALLOC, SAFE_SNPRINTF, utf8_char_t::utf8_bytes, and image_t::w.

Referenced by ascii_convert(), and image_print_with_capabilities().