ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
image.c File Reference

🖨️ Image processing: format detection, decoding, scaling, and pixel format conversion More...

Go to the source code of this file.

Functions

image_t * image_new (size_t width, size_t height)
 
void image_destroy (image_t *p)
 
image_t * image_new_from_pool (size_t width, size_t height)
 
void image_destroy_to_pool (image_t *image)
 
void image_clear (image_t *p)
 
image_t * image_new_copy (const image_t *source)
 
rgb_pixel_t * image_pixel (image_t *p, const int x, const int y)
 
void image_resize (const image_t *s, image_t *d)
 
void image_resize_interpolation (const image_t *source, image_t *dest)
 
void precalc_rgb_palettes (const float red, const float green, const float blue)
 
char * image_print (const image_t *p, const char *palette)
 
void quantize_color (int *r, int *g, int *b, int levels)
 
char * image_print_color (const image_t *p, const char *palette)
 
char * rgb_to_ansi_fg (int r, int g, int b)
 
char * rgb_to_ansi_bg (int r, int g, int b)
 
void rgb_to_ansi_8bit (int r, int g, int b, int *fg_code, int *bg_code)
 
char * image_print_with_capabilities (const image_t *image, const terminal_capabilities_t *caps, const char *palette)
 
char * image_print_256color (const image_t *image, const char *palette)
 
char * image_print_16color (const image_t *image, const char *palette)
 
char * image_print_16color_dithered (const image_t *image, const char *palette)
 
char * image_print_16color_dithered_with_background (const image_t *image, bool use_background, const char *palette)
 

Detailed Description

🖨️ Image processing: format detection, decoding, scaling, and pixel format conversion

Definition in file video/image.c.

Function Documentation

◆ image_clear()

void image_clear ( image_t *  p)

Definition at line 203 of file video/image.c.

203 {
204 if (!p || !p->pixels) {
205 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: p or p->pixels is NULL");
206 return;
207 }
208 // Check for integer overflow before multiplication.
209 unsigned long w_ul = (unsigned long)p->w;
210 unsigned long h_ul = (unsigned long)p->h;
211 if (w_ul > 0 && h_ul > ULONG_MAX / w_ul) {
212 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: dimensions overflow: %d x %d", p->w, p->h);
213 return;
214 }
215 unsigned long pixel_count = w_ul * h_ul;
216 if (pixel_count > ULONG_MAX / sizeof(rgb_pixel_t)) {
217 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: buffer size overflow");
218 return;
219 }
220 size_t clear_size = pixel_count * sizeof(rgb_pixel_t);
221 SAFE_MEMSET(p->pixels, clear_size, 0, clear_size);
222}

Referenced by ascii_convert(), and ascii_convert_with_capabilities().

◆ image_destroy()

void image_destroy ( image_t *  p)

Definition at line 85 of file video/image.c.

85 {
86 if (!p) {
87 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy: p is NULL");
88 return;
89 }
90
91 // Check allocation method and deallocate appropriately
92 if (p->alloc_method == IMAGE_ALLOC_POOL) {
93 // Pool-allocated images: free entire contiguous buffer (image + pixels)
94 // Validate dimensions before calculating size (guard against corruption)
95 if (p->w <= 0 || p->h <= 0) {
96 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy: invalid dimensions %dx%d (pool-allocated)", p->w, p->h);
97 return;
98 }
99
100 // Calculate original allocation size for proper buffer pool free
101 size_t w = (size_t)p->w;
102 size_t h = (size_t)p->h;
103 size_t pixels_size;
104 if (checked_size_mul3(w, h, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
105 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy: dimensions would overflow: %dx%d", p->w, p->h);
106 return;
107 }
108 size_t total_size;
109 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
110 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy: total size would overflow: %dx%d", p->w, p->h);
111 return;
112 }
113
114 // Free the entire contiguous buffer back to pool
115 buffer_pool_free(NULL, p, total_size);
116 } else {
117 // SIMD-allocated images: free pixels and structure separately
118 // SAFE_MALLOC_SIMD allocates with aligned allocation (posix_memalign on macOS, aligned_alloc on Linux)
119 // These can be freed with standard free() / SAFE_FREE()
120 SAFE_FREE(p->pixels);
121 SAFE_FREE(p);
122 }
123}
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)

References buffer_pool_free().

Referenced by ascii_convert(), ascii_convert_with_capabilities(), benchmark_simd_color_conversion(), benchmark_simd_color_conversion_with_source(), benchmark_simd_conversion(), benchmark_simd_conversion_with_source(), create_mixed_ascii_frame_for_client(), ffmpeg_decoder_destroy(), session_display_convert_to_ascii(), session_host_add_client(), session_host_add_memory_participant(), session_host_destroy(), session_host_inject_frame(), session_host_remove_client(), and webcam_destroy().

◆ image_destroy_to_pool()

void image_destroy_to_pool ( image_t *  image)

Definition at line 171 of file video/image.c.

171 {
172 if (!image) {
173 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy_to_pool: image is NULL");
174 return;
175 }
176
177 // Validate dimensions before calculating size (guard against corruption)
178 if (image->w <= 0 || image->h <= 0) {
179 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy_to_pool: invalid dimensions %dx%d", image->w, image->h);
180 return;
181 }
182
183 // Calculate original allocation size for proper buffer pool free
184 // Check for overflow (defensive - should match original allocation)
185 size_t w = (size_t)image->w;
186 size_t h = (size_t)image->h;
187 size_t pixels_size;
188 if (checked_size_mul3(w, h, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
189 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy_to_pool: dimensions would overflow: %dx%d", image->w, image->h);
190 return;
191 }
192 size_t total_size;
193 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
194 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy_to_pool: total size would overflow: %dx%d", image->w, image->h);
195 return;
196 }
197
198 // Free the entire contiguous buffer back to pool
199 buffer_pool_free(NULL, image, total_size);
200 // Note: Don't set image = NULL here since caller owns the pointer
201}

References buffer_pool_free().

Referenced by create_mixed_ascii_frame_for_client().

◆ image_new()

image_t * image_new ( size_t  width,
size_t  height 
)

Definition at line 36 of file video/image.c.

36 {
37 image_t *p;
38
39 p = SAFE_MALLOC(sizeof(image_t), image_t *);
40
41 // Validate dimensions are non-zero and within bounds
42 if (image_validate_dimensions(width, height) != ASCIICHAT_OK) {
43 SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions invalid or too large: %zu x %zu", width, height);
44 SAFE_FREE(p);
45 return NULL;
46 }
47
48 // Calculate pixel count with overflow checking
49 size_t total_pixels;
50 if (checked_size_mul(width, height, &total_pixels) != ASCIICHAT_OK) {
51 SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions would cause overflow: %zu x %zu", width, height);
52 SAFE_FREE(p);
53 return NULL;
54 }
55
56 // Calculate total pixel buffer size with overflow checking
57 size_t pixels_size;
58 if (checked_size_mul(total_pixels, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
59 SET_ERRNO(ERROR_INVALID_PARAM, "Image pixel buffer size would cause overflow");
60 SAFE_FREE(p);
61 return NULL;
62 }
63
64 if (pixels_size > IMAGE_MAX_PIXELS_SIZE) {
65 SET_ERRNO(ERROR_INVALID_PARAM, "Image size exceeds maximum allowed: %zu x %zu (%zu bytes)", width, height,
66 pixels_size);
67 SAFE_FREE(p);
68 return NULL;
69 }
70
71 // Use SIMD-aligned allocation for optimal NEON/AVX performance with vld3q_u8
72 p->pixels = SAFE_MALLOC_SIMD(pixels_size, rgb_pixel_t *);
73 if (!p->pixels) {
74 SET_ERRNO(ERROR_MEMORY, "Failed to allocate image pixels: %zu bytes", pixels_size);
75 SAFE_FREE(p);
76 return NULL;
77 }
78
79 p->w = (int)width;
80 p->h = (int)height;
81 p->alloc_method = IMAGE_ALLOC_SIMD; // Track allocation method for correct deallocation
82 return p;
83}
asciichat_error_t image_validate_dimensions(size_t width, size_t height)
Definition video.c:12

References image_validate_dimensions().

Referenced by ascii_convert(), ascii_convert_with_capabilities(), benchmark_simd_color_conversion(), benchmark_simd_color_conversion_with_source(), benchmark_simd_conversion(), benchmark_simd_conversion_with_source(), ffmpeg_decoder_create(), image_new_copy(), session_capture_process_for_transmission(), session_display_convert_to_ascii(), session_host_add_client(), session_host_add_memory_participant(), session_host_inject_frame(), and webcam_read().

◆ image_new_copy()

image_t * image_new_copy ( const image_t *  source)

Definition at line 224 of file video/image.c.

224 {
225 if (!source) {
226 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_copy: source is NULL");
227 return NULL;
228 }
229
230 // Create new image with same dimensions
231 image_t *copy = image_new((size_t)source->w, (size_t)source->h);
232 if (!copy) {
233 return NULL;
234 }
235
236 // Copy pixel data from source to copy
237 if (source->pixels && copy->pixels) {
238 size_t pixel_count = (size_t)source->w * (size_t)source->h;
239 size_t pixels_size = pixel_count * sizeof(rgb_pixel_t);
240 memcpy(copy->pixels, source->pixels, pixels_size);
241 }
242
243 return copy;
244}
image_t * image_new(size_t width, size_t height)
Definition video/image.c:36

References image_new().

Referenced by create_mixed_ascii_frame_for_client().

◆ image_new_from_pool()

image_t * image_new_from_pool ( size_t  width,
size_t  height 
)

Definition at line 126 of file video/image.c.

126 {
127 if (width == 0 || height == 0) {
128 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: invalid dimensions %zux%zu", width, height);
129 return NULL;
130 }
131
132 if (width > IMAGE_MAX_WIDTH || height > IMAGE_MAX_HEIGHT) {
133 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: dimensions %zux%zu exceed maximum %ux%u", width, height,
134 IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT);
135 return NULL;
136 }
137
138 // Calculate total allocation size (structure + pixel data in single buffer)
139 // Check for integer overflow before multiplication
140 size_t pixels_size;
141 if (checked_size_mul3(width, height, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
142 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: dimensions would overflow: %zux%zu", width, height);
143 return NULL;
144 }
145
146 size_t total_size;
147 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
148 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: total size would overflow");
149 return NULL;
150 }
151
152 // Allocate from buffer pool as single contiguous block
153 void *buffer = buffer_pool_alloc(NULL, total_size);
154 if (!buffer) {
155 SET_ERRNO(ERROR_MEMORY, "image_new_from_pool: buffer pool allocation failed for %zu bytes (%zux%zu)", total_size,
156 width, height);
157 return NULL;
158 }
159
160 // Set up image structure at start of buffer
161 image_t *image = (image_t *)buffer;
162 image->w = (int)width;
163 image->h = (int)height;
164 // Pixel data immediately follows the image structure
165 image->pixels = (rgb_pixel_t *)((uint8_t *)buffer + sizeof(image_t));
166 image->alloc_method = IMAGE_ALLOC_POOL; // Track allocation method for correct deallocation
167
168 return image;
169}
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Definition buffer_pool.c:99

References buffer_pool_alloc().

◆ image_pixel()

rgb_pixel_t * image_pixel ( image_t *  p,
const int  x,
const int  y 
)
inline

Definition at line 246 of file video/image.c.

246 {
247 // Add bounds checking to prevent buffer overflow on invalid coordinates
248 if (!p || !p->pixels || x < 0 || x >= p->w || y < 0 || y >= p->h) {
249 return NULL;
250 }
251 return &p->pixels[x + y * p->w];
252}

◆ image_print()

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

Definition at line 395 of file video/image.c.

395 {
396 if (!p || !palette) {
397 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: p=%p or palette=%p is NULL", p, palette);
398 return NULL;
399 }
400 if (!p->pixels) {
401 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: p->pixels is NULL");
402 return NULL;
403 }
404
405 const int h = p->h;
406 const int w = p->w;
407
408 if (h <= 0 || w <= 0) {
409 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: invalid dimensions h=%d, w=%d", h, w);
410 return NULL;
411 }
412
413 // Get UTF-8 character cache for proper multi-byte character support
414 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
415 if (!utf8_cache) {
416 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 palette cache for scalar rendering");
417 return NULL;
418 }
419
420 // Character index ramp is now part of UTF-8 cache - no separate cache needed
421
422 // Need space for h rows with UTF-8 characters, plus h-1 newlines, plus null terminator
423 const size_t max_char_bytes = 4; // Max UTF-8 character size
424
425 const rgb_pixel_t *pix = p->pixels;
426
427 // Use outbuf_t for efficient UTF-8 RLE emission (same as SIMD renderers)
428 outbuf_t ob = {0};
429
430 // Calculate buffer size with overflow checking
431 size_t w_times_bytes;
432 if (checked_size_mul((size_t)w, max_char_bytes, &w_times_bytes) != ASCIICHAT_OK) {
433 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: width too large for UTF-8 encoding");
434 return NULL;
435 }
436
437 size_t w_times_bytes_plus_one;
438 if (checked_size_add(w_times_bytes, 1, &w_times_bytes_plus_one) != ASCIICHAT_OK) {
439 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: width * bytes + 1 overflow");
440 return NULL;
441 }
442
443 if (checked_size_mul((size_t)h, w_times_bytes_plus_one, &ob.cap) != ASCIICHAT_OK) {
444 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: height * (width * bytes + 1) overflow");
445 return NULL;
446 }
447
448 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
449 if (!ob.buf) {
450 SET_ERRNO(ERROR_MEMORY, "Failed to allocate output buffer for scalar rendering");
451 return NULL;
452 }
453
454 // Process pixels with UTF-8 RLE emission (same approach as SIMD)
455 for (int y = 0; y < h; y++) {
456 const int row_offset = y * w;
457
458 for (int x = 0; x < w;) {
459 const rgb_pixel_t pixel = pix[row_offset + x];
460 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
461 const int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
462
463 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
464 uint8_t safe_luminance = clamp_rgb(luminance);
465 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
466 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
467
468 // Use same 64-entry cache as SIMD for consistency
469 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
470
471 // Find run length for same character (RLE optimization)
472 int j = x + 1;
473 while (j < w) {
474 const rgb_pixel_t next_pixel = pix[row_offset + j];
475 const int next_luminance = (77 * next_pixel.r + 150 * next_pixel.g + 29 * next_pixel.b + 128) >> 8;
476 uint8_t next_safe_luminance = clamp_rgb(next_luminance);
477 uint8_t next_luma_idx = (uint8_t)(next_safe_luminance >> 2); // 0-63 index (same as SIMD)
478 uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx]; // Map to character index (same as SIMD)
479 if (next_char_idx != char_idx)
480 break;
481 j++;
482 }
483 uint32_t run = (uint32_t)(j - x);
484
485 // Emit UTF-8 character with RLE (same as SIMD)
486 ob_write(&ob, char_info->utf8_bytes, char_info->byte_len);
487 if (rep_is_profitable(run)) {
488 emit_rep(&ob, run - 1);
489 } else {
490 for (uint32_t k = 1; k < run; k++) {
491 ob_write(&ob, char_info->utf8_bytes, char_info->byte_len);
492 }
493 }
494 x = j;
495 }
496
497 // Add newline between rows (except last row)
498 if (y != h - 1) {
499 ob_putc(&ob, '\n');
500 }
501 }
502
503 ob_term(&ob);
504 return ob.buf;
505}
void ob_term(outbuf_t *ob)
void ob_putc(outbuf_t *ob, char c)
bool rep_is_profitable(uint32_t runlen)
void emit_rep(outbuf_t *ob, uint32_t extra)
void ob_write(outbuf_t *ob, const char *s, size_t n)
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)

References emit_rep(), get_utf8_palette_cache(), ob_putc(), ob_term(), ob_write(), and rep_is_profitable().

Referenced by ascii_convert(), benchmark_simd_conversion(), image_print_simd(), and image_print_with_capabilities().

◆ image_print_16color()

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

Definition at line 862 of file video/image.c.

862 {
863 if (!image || !image->pixels || !palette) {
864 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
865 return NULL;
866 }
867
868 int h = image->h;
869 int w = image->w;
870
871 if (h <= 0 || w <= 0) {
872 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color: invalid dimensions h=%d, w=%d", h, w);
873 return NULL;
874 }
875
876 // Initialize 16-color lookup table
878
879 // Calculate buffer size (smaller than 256-color due to shorter ANSI sequences)
880 // Space for ANSI codes + newlines
881 // Calculate with overflow checking
882 size_t h_times_w;
883 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w) != ASCIICHAT_OK) {
884 return NULL;
885 }
886
887 size_t h_times_w_times_12;
888 if (checked_size_mul(h_times_w, 12u, &h_times_w_times_12) != ASCIICHAT_OK) {
889 return NULL;
890 }
891
892 size_t buffer_size;
893 if (checked_size_add(h_times_w_times_12, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
894 return NULL;
895 }
896
897 char *buffer;
898 buffer = SAFE_MALLOC(buffer_size, char *);
899
900 char *ptr = buffer;
901 const char *reset_code = "\033[0m";
902
903 for (int y = 0; y < h; y++) {
904 for (int x = 0; x < w; x++) {
905 rgb_pixel_t pixel = image->pixels[y * w + x];
906
907 // Convert RGB to 16-color index and generate ANSI sequence
908 uint8_t color_index = rgb_to_16color(pixel.r, pixel.g, pixel.b);
909 ptr = append_16color_fg(ptr, color_index);
910
911 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
912 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
913
914 // Use UTF-8 cache which contains character index ramp
915 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
916 if (!utf8_cache) {
917 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache");
918 return NULL;
919 }
920
921 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
922 uint8_t safe_luminance = clamp_rgb(luminance);
923 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
924 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
925
926 // Use direct palette character lookup (same as SIMD would do)
927 char ascii_char = palette[char_idx];
928 const utf8_char_t *char_info = &utf8_cache->cache[(unsigned char)ascii_char];
929
930 if (char_info) {
931 // Copy UTF-8 character bytes
932 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
933 *ptr++ = char_info->utf8_bytes[byte_idx];
934 }
935 } else {
936 // Fallback to simple ASCII if UTF-8 cache fails
937 size_t palette_len = strlen(palette);
938 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
939 *ptr++ = palette[palette_index];
940 }
941 }
942
943 // Add reset and newline at end of each row
944 SAFE_STRNCPY(ptr, reset_code, 5);
945 ptr += strlen(reset_code);
946 if (y < h - 1) {
947 *ptr++ = '\n';
948 }
949 }
950
951 *ptr = '\0';
952 return buffer;
953}
char * append_16color_fg(char *dst, uint8_t color_index)
Definition ansi_fast.c:280
uint8_t rgb_to_16color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi_fast.c:314
void ansi_fast_init_16color(void)
Definition ansi_fast.c:257
int buffer_size
Size of circular buffer.
Definition grep.c:84

References ansi_fast_init_16color(), append_16color_fg(), buffer_size, get_utf8_palette_cache(), and rgb_to_16color().

◆ image_print_16color_dithered()

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

Definition at line 956 of file video/image.c.

956 {
957 if (!image || !image->pixels || !palette) {
958 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
959 return NULL;
960 }
961
962 int h = image->h;
963 int w = image->w;
964
965 if (h <= 0 || w <= 0) {
966 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color_dithered: invalid dimensions h=%d, w=%d", h, w);
967 return NULL;
968 }
969
970 // Initialize 16-color lookup table
972
973 // Allocate error buffer for Floyd-Steinberg dithering
974 size_t pixel_count = (size_t)h * (size_t)w;
975 rgb_error_t *error_buffer;
976 error_buffer = SAFE_CALLOC(pixel_count, sizeof(rgb_error_t), rgb_error_t *);
977
978 // Calculate buffer size (same as non-dithered version)
979 // Space for ANSI codes + newlines
980 // Calculate with overflow checking
981 size_t h_times_w2;
982 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w2) != ASCIICHAT_OK) {
983 SAFE_FREE(error_buffer);
984 return NULL;
985 }
986
987 size_t h_times_w2_times_12;
988 if (checked_size_mul(h_times_w2, 12u, &h_times_w2_times_12) != ASCIICHAT_OK) {
989 SAFE_FREE(error_buffer);
990 return NULL;
991 }
992
993 size_t buffer_size;
994 if (checked_size_add(h_times_w2_times_12, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
995 SAFE_FREE(error_buffer);
996 return NULL;
997 }
998
999 char *buffer;
1000 buffer = SAFE_MALLOC(buffer_size, char *);
1001
1002 char *ptr = buffer;
1003 const char *reset_code = "\033[0m";
1004
1005 for (int y = 0; y < h; y++) {
1006 for (int x = 0; x < w; x++) {
1007 rgb_pixel_t pixel = image->pixels[y * w + x];
1008
1009 // Convert RGB to 16-color index using dithering
1010 uint8_t color_index = rgb_to_16color_dithered(pixel.r, pixel.g, pixel.b, x, y, w, h, error_buffer);
1011 ptr = append_16color_fg(ptr, color_index);
1012
1013 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
1014 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
1015
1016 // Use UTF-8 cache which contains character index ramp
1017 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
1018 if (!utf8_cache) {
1019 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache");
1020 return NULL;
1021 }
1022
1023 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
1024 uint8_t safe_luminance = clamp_rgb(luminance);
1025 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
1026 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
1027
1028 // Use direct palette character lookup (same as SIMD would do)
1029 char ascii_char = palette[char_idx];
1030 const utf8_char_t *char_info = &utf8_cache->cache[(unsigned char)ascii_char];
1031
1032 if (char_info) {
1033 // Copy UTF-8 character bytes
1034 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
1035 *ptr++ = char_info->utf8_bytes[byte_idx];
1036 }
1037 } else {
1038 // Fallback to simple ASCII if UTF-8 cache fails
1039 size_t palette_len = strlen(palette);
1040 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
1041 *ptr++ = palette[palette_index];
1042 }
1043 }
1044
1045 // Add reset and newline at end of each row
1046 SAFE_STRNCPY(ptr, reset_code, 5);
1047 ptr += strlen(reset_code);
1048 if (y < h - 1) {
1049 *ptr++ = '\n';
1050 }
1051 }
1052
1053 *ptr = '\0';
1054 SAFE_FREE(error_buffer); // Clean up error buffer
1055 return buffer;
1056}
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_fast.c:388

References ansi_fast_init_16color(), append_16color_fg(), buffer_size, get_utf8_palette_cache(), and rgb_to_16color_dithered().

◆ 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 1059 of file video/image.c.

1059 {
1060 if (!image || !image->pixels || !palette) {
1061 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
1062 return NULL;
1063 }
1064
1065 int h = image->h;
1066 int w = image->w;
1067
1068 if (h <= 0 || w <= 0) {
1069 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color_dithered_with_background: invalid dimensions h=%d, w=%d", h, w);
1070 return NULL;
1071 }
1072
1073 // Initialize 16-color lookup table
1075
1076 // Allocate error buffer for Floyd-Steinberg dithering
1077 size_t pixel_count = (size_t)h * (size_t)w;
1078 rgb_error_t *error_buffer;
1079 error_buffer = SAFE_CALLOC(pixel_count, sizeof(rgb_error_t), rgb_error_t *);
1080
1081 // Calculate buffer size (larger for background mode due to more ANSI sequences)
1082 size_t buffer_size =
1083 (size_t)h * (size_t)w * (use_background ? 24 : 12) + (size_t)h; // Space for ANSI codes + newlines
1084 char *buffer;
1085 buffer = SAFE_MALLOC(buffer_size, char *);
1086
1087 char *ptr = buffer;
1088 const char *reset_code = "\033[0m";
1089
1090 for (int y = 0; y < h; y++) {
1091 for (int x = 0; x < w; x++) {
1092 rgb_pixel_t pixel = image->pixels[y * w + x];
1093
1094 // Convert RGB to 16-color index using dithering
1095 uint8_t color_index = rgb_to_16color_dithered(pixel.r, pixel.g, pixel.b, x, y, w, h, error_buffer);
1096
1097 if (use_background) {
1098 // Background mode: use contrasting foreground on background color
1099 uint8_t bg_r, bg_g, bg_b;
1100 get_16color_rgb(color_index, &bg_r, &bg_g, &bg_b);
1101
1102 // Calculate luminance to choose contrasting foreground
1103 int bg_luminance = (bg_r * 77 + bg_g * 150 + bg_b * 29) / 256;
1104 uint8_t fg_color = (bg_luminance < 127) ? 15 : 0; // White on dark, black on bright
1105
1106 ptr = append_16color_bg(ptr, color_index); // Set background to pixel color
1107 ptr = append_16color_fg(ptr, fg_color); // Set contrasting foreground
1108 } else {
1109 // Foreground mode: set foreground to pixel color
1110 ptr = append_16color_fg(ptr, color_index);
1111 }
1112
1113 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
1114 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
1115
1116 // Use UTF-8 cache which contains character index ramp
1117 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
1118 if (!utf8_cache) {
1119 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache");
1120 return NULL;
1121 }
1122
1123 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
1124 uint8_t safe_luminance = clamp_rgb(luminance);
1125 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
1126 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
1127
1128 // Use direct palette character lookup (same as SIMD would do)
1129 char ascii_char = palette[char_idx];
1130 const utf8_char_t *char_info = &utf8_cache->cache[(unsigned char)ascii_char];
1131
1132 if (char_info) {
1133 // Copy UTF-8 character bytes
1134 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
1135 *ptr++ = char_info->utf8_bytes[byte_idx];
1136 }
1137 } else {
1138 // Fallback to simple ASCII if UTF-8 cache fails
1139 size_t palette_len = strlen(palette);
1140 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
1141 *ptr++ = palette[palette_index];
1142 }
1143 }
1144
1145 // Add reset and newline at end of each row
1146 SAFE_STRNCPY(ptr, reset_code, 5);
1147 ptr += strlen(reset_code);
1148 if (y < h - 1) {
1149 *ptr++ = '\n';
1150 }
1151 }
1152
1153 *ptr = '\0';
1154 SAFE_FREE(error_buffer); // Clean up error buffer
1155 return buffer;
1156}
char * append_16color_bg(char *dst, uint8_t color_index)
Definition ansi_fast.c:297
void get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b)
Definition ansi_fast.c:357

References ansi_fast_init_16color(), append_16color_bg(), append_16color_fg(), buffer_size, get_16color_rgb(), get_utf8_palette_cache(), and rgb_to_16color_dithered().

Referenced by image_print_with_capabilities().

◆ image_print_256color()

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

Definition at line 845 of file video/image.c.

845 {
846 if (!image || !image->pixels || !palette) {
847 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
848 return NULL;
849 }
850
851 // Use the existing optimized SIMD colored printing (no background for 256-color mode)
852#ifdef SIMD_SUPPORT
853 char *result = image_print_color_simd((image_t *)image, false, true, palette);
854#else
855 char *result = image_print_color(image, palette);
856#endif
857
858 return result;
859}
char * image_print_color_simd(image_t *image, bool use_background_mode, bool use_256color, const char *ascii_chars)
char * image_print_color(const image_t *p, const char *palette)

References image_print_color(), and image_print_color_simd().

Referenced by image_print_with_capabilities().

◆ 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 559 of file video/image.c.

559 {
560 if (!p || !palette) {
561 SET_ERRNO(ERROR_INVALID_PARAM, "p=%p or palette=%p is NULL", p, palette);
562 return NULL;
563 }
564 if (!p->pixels) {
565 SET_ERRNO(ERROR_INVALID_PARAM, "p->pixels is NULL");
566 return NULL;
567 }
568
569 // Get UTF-8 character cache for proper multi-byte character support
570 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
571 if (!utf8_cache) {
572 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 palette cache for scalar color rendering");
573 return NULL;
574 }
575
576 const int h = p->h;
577 const int w = p->w;
578
579 // Constants for ANSI escape codes (using exact sizes from ansi_fast.c)
580 const size_t max_fg_ansi = 19; // \033[38;2;255;255;255m
581 const size_t max_bg_ansi = 19; // \033[48;2;255;255;255m
582 const size_t reset_len = 4; // \033[0m
583
584 const size_t h_sz = (size_t)h;
585 const size_t w_sz = (size_t)w;
586
587 // Ensure h * w won't overflow
588 if (h_sz > 0 && w_sz > SIZE_MAX / h_sz) {
589 SET_ERRNO(ERROR_INVALID_STATE, "Image dimensions too large: %d x %d", h, w);
590 return NULL;
591 }
592
593 const size_t total_pixels = h_sz * w_sz;
594 const size_t bytes_per_pixel = 1 + max_fg_ansi + max_bg_ansi; // Conservative estimate for max possible
595
596 // Ensure total_pixels * bytes_per_pixel won't overflow
597 if (total_pixels > SIZE_MAX / bytes_per_pixel) {
598 SET_ERRNO(ERROR_INVALID_STATE, "Pixel data too large for buffer: %d x %d", h, w);
599 return NULL;
600 }
601
602 const size_t pixel_bytes = total_pixels * bytes_per_pixel;
603
604 // Per row: newline (except last row)
605 // Final reset added once by ansi_rle_finish()
606 const size_t total_newlines = (h_sz > 0) ? (h_sz - 1) : 0;
607 const size_t final_reset = reset_len; // One reset at end (from ansi_rle_finish)
608
609 // Final buffer size: pixel bytes + per-row newlines + final reset + null terminator
610 const size_t extra_bytes = total_newlines + final_reset + 1;
611
612 if (pixel_bytes > SIZE_MAX - extra_bytes) {
613 SET_ERRNO(ERROR_INVALID_STATE, "Final buffer size would overflow: %d x %d", h, w);
614 return NULL;
615 }
616
617 const size_t lines_size = pixel_bytes + extra_bytes;
618 char *lines;
619 lines = SAFE_MALLOC(lines_size, char *);
620
621 const rgb_pixel_t *pix = p->pixels;
622 // char *current_pos = lines;
623 // const char *buffer_end = lines + lines_size - 1; // reserve space for '\0'
624
625 // Initialize the optimized RLE context for color sequence caching
626 // Note: This function should be called via image_print_with_capabilities() for proper per-client rendering
627 ansi_rle_context_t rle_ctx;
628 ansi_color_mode_t color_mode = ANSI_MODE_FOREGROUND; // Default to foreground-only for legacy usage
629 ansi_rle_init(&rle_ctx, lines, lines_size, color_mode);
630
631 // Process each pixel using the optimized RLE context
632 for (int y = 0; y < h; y++) {
633 const int row_offset = y * w;
634
635 for (int x = 0; x < w; x++) {
636 const rgb_pixel_t pixel = pix[row_offset + x];
637 int r = pixel.r, g = pixel.g, b = pixel.b;
638 // Standard ITU-R BT.601 luminance calculation
639 const int luminance = (77 * r + 150 * g + 29 * b + 128) >> 8;
640
641 // Use UTF-8 character cache for proper character selection
642 uint8_t safe_luminance = clamp_rgb(luminance);
643 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
644
645 // For RLE, we need to pass the first byte of the UTF-8 character
646 // Note: RLE system may need updates for full UTF-8 support
647 const char ascii_char = char_info->utf8_bytes[0];
648
649 ansi_rle_add_pixel(&rle_ctx, (uint8_t)r, (uint8_t)g, (uint8_t)b, ascii_char);
650 }
651
652 // Add newline after each row (except the last row)
653 if (y != h - 1 && rle_ctx.length < rle_ctx.capacity - 1) {
654 rle_ctx.buffer[rle_ctx.length++] = '\n';
655 }
656 }
657
658 ansi_rle_finish(&rle_ctx);
659
660 // DEBUG: Validate frame integrity right after rendering
661 // Check for incomplete ANSI sequences line by line
662 {
663 const char *line_start = lines;
664 int line_num = 0;
665 while (*line_start) {
666 const char *line_end = line_start;
667 while (*line_end && *line_end != '\n')
668 line_end++;
669
670 // Check for incomplete ANSI sequence at end of this line
671 for (const char *p = line_start; p < line_end;) {
672 if (*p == '\033' && p + 1 < line_end && *(p + 1) == '[') {
673 const char *seq_start = p;
674 p += 2;
675 // Scan for terminator (@ through ~, i.e., 0x40-0x7E)
676 while (p < line_end && !(*p >= '@' && *p <= '~'))
677 p++;
678 if (p >= line_end) {
679 // Incomplete sequence found
680 size_t incomplete_len = (size_t)(line_end - seq_start);
681 log_error("RENDER_INCOMPLETE_ANSI: Line %d (w=%d h=%d) has incomplete ANSI sequence (%zu bytes)", line_num,
682 w, h, incomplete_len);
683 // Show the incomplete sequence
684 char debug_buf[128] = {0};
685 size_t debug_pos = 0;
686 for (const char *d = seq_start; d < line_end && debug_pos < sizeof(debug_buf) - 10; d++) {
687 unsigned char c = (unsigned char)*d;
688 if (c == '\033') {
689 debug_buf[debug_pos++] = '<';
690 debug_buf[debug_pos++] = 'E';
691 debug_buf[debug_pos++] = 'S';
692 debug_buf[debug_pos++] = 'C';
693 debug_buf[debug_pos++] = '>';
694 } else if (c >= 0x20 && c < 0x7F) {
695 debug_buf[debug_pos++] = (char)c;
696 } else {
697 debug_pos += (size_t)safe_snprintf(debug_buf + debug_pos, sizeof(debug_buf) - debug_pos, "<%02X>", c);
698 }
699 }
700 log_error(" Incomplete seq: %s", debug_buf);
701 break;
702 } else {
703 p++; // Skip terminator
704 }
705 } else {
706 p++;
707 }
708 }
709
710 line_num++;
711 line_start = *line_end ? line_end + 1 : line_end;
712 }
713 }
714
715 return lines;
716}
void ansi_rle_init(ansi_rle_context_t *ctx, char *buffer, size_t capacity, ansi_color_mode_t mode)
Definition ansi_fast.c:129
void ansi_rle_finish(ansi_rle_context_t *ctx)
Definition ansi_fast.c:184
void ansi_rle_add_pixel(ansi_rle_context_t *ctx, uint8_t r, uint8_t g, uint8_t b, char ascii_char)
Definition ansi_fast.c:142
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456

References ansi_rle_add_pixel(), ansi_rle_finish(), ansi_rle_init(), get_utf8_palette_cache(), and safe_snprintf().

Referenced by ascii_convert(), benchmark_simd_color_conversion(), benchmark_simd_conversion_with_source(), image_print_256color(), image_print_color_simd(), and image_print_with_capabilities().

◆ image_print_with_capabilities()

char * image_print_with_capabilities ( const image_t *  image,
const terminal_capabilities_t *  caps,
const char *  palette 
)

Definition at line 758 of file video/image.c.

758 {
759
760 if (!image || !image->pixels || !caps || !palette) {
761 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or caps=%p or palette=%p is NULL", image,
762 image->pixels, caps, palette);
763 return NULL;
764 }
765
766 // Handle half-block mode first (requires NEON)
767 if (caps->render_mode == RENDER_MODE_HALF_BLOCK) {
768#if SIMD_SUPPORT_NEON
769 // Use NEON half-block renderer
770 const uint8_t *rgb_data = (const uint8_t *)image->pixels;
771 return rgb_to_truecolor_halfblocks_neon(rgb_data, image->w, image->h, 0);
772#else
773 SET_ERRNO(ERROR_INVALID_STATE, "Half-block mode requires NEON support (ARM architecture)");
774 return NULL;
775#endif
776 }
777
778 // Standard color modes
779 bool use_background_mode = (caps->render_mode == RENDER_MODE_BACKGROUND);
780
781 char *result = NULL;
782
783 // Choose the appropriate printing method based on terminal capabilities
784 switch (caps->color_level) {
785 case TERM_COLOR_TRUECOLOR:
786 // Use existing truecolor printing function with client's palette
787#ifdef SIMD_SUPPORT
788 START_TIMER("print_color_simd_truecolor");
789 result = image_print_color_simd((image_t *)image, use_background_mode, false, palette);
790 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "print_color_simd_truecolor",
791 "PRINT_SIMD_TRUECOLOR: Complete (%.2f ms)");
792#else
793 START_TIMER("print_color");
794 result = image_print_color(image, palette);
795 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "print_color",
796 "PRINT_COLOR: Complete (%.2f ms)");
797#endif
798 break;
799
800 case TERM_COLOR_256:
801 log_info("Using 256-COLOR rendering path");
802#ifdef SIMD_SUPPORT
803 START_TIMER("print_color_simd_256");
804 result = image_print_color_simd((image_t *)image, use_background_mode, true, palette);
805 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "print_color_simd_256",
806 "PRINT_SIMD_256: Complete (%.2f ms)");
807#else
808 // Use 256-color conversion
809 START_TIMER("print_256color");
810 result = image_print_256color(image, palette);
811 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "print_256color",
812 "PRINT_256COLOR: Complete (%.2f ms)");
813#endif
814 break;
815
816 case TERM_COLOR_16:
817 // Use 16-color conversion with Floyd-Steinberg dithering for better quality
818 START_TIMER("print_16color_dithered");
819 result = image_print_16color_dithered_with_background(image, use_background_mode, palette);
820 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "print_16color_dithered",
821 "PRINT_16COLOR_DITHERED: Complete (%.2f ms)");
822 break;
823
824 case TERM_COLOR_NONE:
825 default:
826 // Use grayscale/monochrome conversion with client's custom palette
827#ifdef SIMD_SUPPORT
828 START_TIMER("print_simd");
829 result = image_print_simd((image_t *)image, palette);
830 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "print_simd",
831 "PRINT_SIMD: Complete (%.2f ms)");
832#else
833 START_TIMER("print");
834 result = image_print(image, palette);
835 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "print", "PRINT: Complete (%.2f ms)");
836#endif
837 break;
838 }
839
840 // Note: Background mode is now handled via capabilities rather than global state
841 return result;
842}
char * image_print_simd(image_t *image, const char *ascii_chars)
Definition ascii_simd.c:260
char * image_print(const image_t *p, const char *palette)
char * image_print_16color_dithered_with_background(const image_t *image, bool use_background, const char *palette)
char * image_print_256color(const image_t *image, const char *palette)

References image_print(), image_print_16color_dithered_with_background(), image_print_256color(), image_print_color(), image_print_color_simd(), and image_print_simd().

Referenced by ascii_convert_with_capabilities().

◆ image_resize()

void image_resize ( const image_t *  s,
image_t *  d 
)

Definition at line 254 of file video/image.c.

254 {
255 if (!s || !d) {
256 SET_ERRNO(ERROR_INVALID_PARAM, "image_resize: s or d is NULL");
257 return;
258 }
259
261}
void image_resize_interpolation(const image_t *source, image_t *dest)

References image_resize_interpolation().

Referenced by ascii_convert(), ascii_convert_with_capabilities(), and session_capture_process_for_transmission().

◆ image_resize_interpolation()

void image_resize_interpolation ( const image_t *  source,
image_t *  dest 
)

Definition at line 265 of file video/image.c.

265 {
266 if (!source || !dest || !source->pixels || !dest->pixels) {
267 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters to image_resize_interpolation");
268 return;
269 }
270
271 const int src_w = source->w;
272 const int src_h = source->h;
273 const int dst_w = dest->w;
274 const int dst_h = dest->h;
275
276 // Handle edge cases
277 if (src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0) {
278 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid image dimensions for resize: src=%dx%d dst=%dx%d", src_w, src_h, dst_w,
279 dst_h);
280 return;
281 }
282
283 // Defensive checks to prevent invalid shift (UBSan protection)
284 if (src_w < 1 || src_h < 1 || dst_w < 1 || dst_h < 1) {
285 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid dimensions detected after first check");
286 return;
287 }
288
289 // Use fixed-point arithmetic for better performance
290 // Use uint64_t intermediate to prevent overflow when shifting large dimensions
291 const uint32_t x_ratio = (uint32_t)((((uint64_t)src_w << 16) / (uint64_t)dst_w) + 1);
292 const uint32_t y_ratio = (uint32_t)((((uint64_t)src_h << 16) / (uint64_t)dst_h) + 1);
293
294 const rgb_pixel_t *src_pixels = source->pixels;
295 rgb_pixel_t *dst_pixels = dest->pixels;
296
297 for (int y = 0; y < dst_h; y++) {
298 const uint32_t src_y = ((uint32_t)(unsigned int)y * y_ratio) >> 16;
299 // Explicitly clamp to valid range [0, src_h-1]
300 const uint32_t safe_src_y = src_y >= (uint32_t)(unsigned int)src_h ? (uint32_t)(src_h - 1) : src_y;
301
302 // Bounds check: ensure safe_src_y is valid
303 if (safe_src_y >= (uint32_t)(unsigned int)src_h) {
304 SET_ERRNO(ERROR_INVALID_PARAM, "safe_src_y out of bounds: %u >= %d", safe_src_y, src_h);
305 return;
306 }
307
308 const rgb_pixel_t *src_row = src_pixels + (safe_src_y * (size_t)src_w);
309
310 rgb_pixel_t *dst_row = dst_pixels + ((size_t)y * (size_t)dst_w);
311
312 for (int x = 0; x < dst_w; x++) {
313 const uint32_t src_x = ((uint32_t)(unsigned int)x * x_ratio) >> 16;
314 // Explicitly clamp to valid range [0, src_w-1]
315 const uint32_t safe_src_x = src_x >= (uint32_t)(unsigned int)src_w ? (uint32_t)(src_w - 1) : src_x;
316
317 // Bounds check: ensure safe_src_x is valid
318 if (safe_src_x >= (uint32_t)(unsigned int)src_w) {
319 SET_ERRNO(ERROR_INVALID_PARAM, "safe_src_x out of bounds: %u >= %d", safe_src_x, src_w);
320 return;
321 }
322
323 dst_row[x] = src_row[safe_src_x];
324 }
325 }
326}

Referenced by image_resize().

◆ precalc_rgb_palettes()

void precalc_rgb_palettes ( const float  red,
const float  green,
const float  blue 
)

Definition at line 330 of file video/image.c.

330 {
331 // Validate input parameters to prevent overflow
332 // Luminance weights should typically be in range 0.0-1.0
333 // But allow slightly larger values for brightness adjustment
334 const float max_weight = 255.0f; // Maximum value that won't overflow when multiplied by 255
335 const float min_weight = -255.0f; // Allow negative for color correction
336
337 // Note: isfinite() checks are skipped in Release builds due to -ffinite-math-only flag
338 // which disables NaN/infinity support for performance. Range checks below are sufficient.
339#if !defined(NDEBUG) && !defined(__FAST_MATH__)
340 if (!isfinite(red) || !isfinite(green) || !isfinite(blue)) {
341 log_error("Invalid weight values (non-finite): red=%f, green=%f, blue=%f", red, green, blue);
342 SET_ERRNO(ERROR_INVALID_PARAM, "precalc_rgb_palettes: non-finite weight values");
343 return;
344 }
345#endif
346
347 if (red < min_weight || red > max_weight || green < min_weight || green > max_weight || blue < min_weight ||
348 blue > max_weight) {
349 log_warn(
350 "precalc_rgb_palettes: Weight values out of expected range: red=%f, green=%f, blue=%f (clamping to safe range)",
351 red, green, blue);
352 }
353
354 // Clamp weights to safe range to prevent overflow
355 const float safe_red = (red < min_weight) ? min_weight : ((red > max_weight) ? max_weight : red);
356 const float safe_green = (green < min_weight) ? min_weight : ((green > max_weight) ? max_weight : green);
357 const float safe_blue = (blue < min_weight) ? min_weight : ((blue > max_weight) ? max_weight : blue);
358
359 const unsigned short max_ushort = 65535;
360 const unsigned short min_ushort = 0;
361
362 for (int n = 0; n < ASCII_LUMINANCE_LEVELS; ++n) {
363 // Compute with float, then clamp to unsigned short range
364 float red_val = (float)n * safe_red;
365 float green_val = (float)n * safe_green;
366 float blue_val = (float)n * safe_blue;
367
368 // Clamp to unsigned short range before assignment
369 if (red_val < (float)min_ushort) {
370 red_val = (float)min_ushort;
371 } else if (red_val > (float)max_ushort) {
372 red_val = (float)max_ushort;
373 }
374
375 if (green_val < (float)min_ushort) {
376 green_val = (float)min_ushort;
377 } else if (green_val > (float)max_ushort) {
378 green_val = (float)max_ushort;
379 }
380
381 if (blue_val < (float)min_ushort) {
382 blue_val = (float)min_ushort;
383 } else if (blue_val > (float)max_ushort) {
384 blue_val = (float)max_ushort;
385 }
386
387 RED[n] = (unsigned short)red_val;
388 GREEN[n] = (unsigned short)green_val;
389 BLUE[n] = (unsigned short)blue_val;
390 GRAY[n] = (unsigned short)n;
391 }
392}
unsigned short int GREEN[256]
unsigned short int GRAY[256]
unsigned short int RED[256]
unsigned short int BLUE[256]

References BLUE, GRAY, GREEN, and RED.

Referenced by server_main().

◆ quantize_color()

void quantize_color ( int *  r,
int *  g,
int *  b,
int  levels 
)

Definition at line 508 of file video/image.c.

508 {
509 if (!r || !g || !b) {
510 SET_ERRNO(ERROR_INVALID_PARAM, "quantize_color: r, g, or b is NULL");
511 return;
512 }
513
514 if (levels <= 0) {
515 SET_ERRNO(ERROR_INVALID_PARAM, "quantize_color: levels must be positive, got %d", levels);
516 return;
517 }
518
519 int step = 256 / levels;
520 *r = (*r / step) * step;
521 *g = (*g / step) * step;
522 *b = (*b / step) * step;
523}

◆ rgb_to_ansi_8bit()

void rgb_to_ansi_8bit ( int  r,
int  g,
int  b,
int *  fg_code,
int *  bg_code 
)

Definition at line 731 of file video/image.c.

731 {
732 if (!fg_code || !bg_code) {
733 SET_ERRNO(ERROR_INVALID_PARAM, "rgb_to_ansi_8bit: fg_code or bg_code is NULL");
734 return;
735 }
736
737 // Convert RGB to 8-bit color code (216 color cube + 24 grayscale)
738 if (r == g && g == b) {
739 // Grayscale
740 if (r < 8) {
741 *fg_code = 16;
742 } else if (r > 248) {
743 *fg_code = 231;
744 } else {
745 *fg_code = 232 + (r - 8) / 10;
746 }
747 } else {
748 // Color cube: 16 + 36*r + 6*g + b where r,g,b are 0-5
749 int r_level = (r * 5) / 255;
750 int g_level = (g * 5) / 255;
751 int b_level = (b * 5) / 255;
752 *fg_code = 16 + 36 * r_level + 6 * g_level + b_level;
753 }
754 *bg_code = *fg_code; // Same logic for background
755}

◆ rgb_to_ansi_bg()

char * rgb_to_ansi_bg ( int  r,
int  g,
int  b 
)

Definition at line 725 of file video/image.c.

725 {
726 _Thread_local static char color_code[20]; // \033[48;2;255;255;255m + \0 = 20 bytes max
727 SAFE_SNPRINTF(color_code, sizeof(color_code), "\033[48;2;%d;%d;%dm", r, g, b);
728 return color_code;
729}

◆ rgb_to_ansi_fg()

char * rgb_to_ansi_fg ( int  r,
int  g,
int  b 
)

Definition at line 719 of file video/image.c.

719 {
720 _Thread_local static char color_code[20]; // \033[38;2;255;255;255m + \0 = 20 bytes max
721 SAFE_SNPRINTF(color_code, sizeof(color_code), "\033[38;2;%d;%d;%dm", r, g, b);
722 return color_code;
723}