ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
video/image.c
Go to the documentation of this file.
1
7#include <stdbool.h>
8#include <stddef.h>
9#include <stdint.h>
10#include <limits.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <math.h>
15#ifndef _WIN32
16#include <unistd.h>
17#endif
18
19#include "../common.h"
20#include "output_buffer.h"
21#include "image.h"
22#include "ascii.h"
23#include "simd/ascii_simd.h"
24#include "video/simd/common.h"
25#include "ansi_fast.h"
26#include "options/options.h"
27#include "buffer_pool.h" // For buffer pool allocation functions
28#include "util/overflow.h"
29#include "util/image.h"
30#include "util/math.h"
31
32// NOTE: luminance_palette is now passed as parameter to functions instead of using global cache
33
34// ansi_fast functions are declared in ansi_fast.h (already included)
35
36image_t *image_new(size_t width, size_t height) {
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}
84
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
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}
124
125// Buffer pool allocation for video pipeline (consistent memory management)
126image_t *image_new_from_pool(size_t width, size_t height) {
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,
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}
170
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}
202
204 if (!p || !p->pixels) {
205 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: p or p->pixels is NULL");
206 return;
207 }
208 // CRITICAL: 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}
223
224inline rgb_pixel_t *image_pixel(image_t *p, const int x, const int y) {
225 // Add bounds checking to prevent buffer overflow on invalid coordinates
226 if (!p || !p->pixels || x < 0 || x >= p->w || y < 0 || y >= p->h) {
227 return NULL;
228 }
229 return &p->pixels[x + y * p->w];
230}
231
232void image_resize(const image_t *s, image_t *d) {
233 if (!s || !d) {
234 SET_ERRNO(ERROR_INVALID_PARAM, "image_resize: s or d is NULL");
235 return;
236 }
237
239}
240
241// Optimized interpolation function with better integer arithmetic and memory
242// access
243void image_resize_interpolation(const image_t *source, image_t *dest) {
244 if (!source || !dest || !source->pixels || !dest->pixels) {
245 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters to image_resize_interpolation");
246 return;
247 }
248
249 const int src_w = source->w;
250 const int src_h = source->h;
251 const int dst_w = dest->w;
252 const int dst_h = dest->h;
253
254 // Handle edge cases
255 if (src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0) {
256 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid image dimensions for resize");
257 return;
258 }
259
260 // Use fixed-point arithmetic for better performance
261 const uint32_t x_ratio = (((uint32_t)(unsigned int)src_w << 16) / (uint32_t)(unsigned int)dst_w) + 1;
262 const uint32_t y_ratio = (((uint32_t)(unsigned int)src_h << 16) / (uint32_t)(unsigned int)dst_h) + 1;
263
264 const rgb_pixel_t *src_pixels = source->pixels;
265 rgb_pixel_t *dst_pixels = dest->pixels;
266
267 for (int y = 0; y < dst_h; y++) {
268 const uint32_t src_y = ((uint32_t)(unsigned int)y * y_ratio) >> 16;
269 const uint32_t safe_src_y = (src_y >= (uint32_t)(unsigned int)src_h) ? (uint32_t)(src_h - 1) : src_y;
270 const rgb_pixel_t *src_row = src_pixels + (safe_src_y * (size_t)src_w);
271
272 rgb_pixel_t *dst_row = dst_pixels + ((size_t)y * (size_t)dst_w);
273
274 for (int x = 0; x < dst_w; x++) {
275 const uint32_t src_x = ((uint32_t)(unsigned int)x * x_ratio) >> 16;
276 const uint32_t safe_src_x = (src_x >= (uint32_t)(unsigned int)src_w) ? (uint32_t)(src_w - 1) : src_x;
277 dst_row[x] = src_row[safe_src_x];
278 }
279 }
280}
281
282// Note: luminance_palette is now handled by ascii_simd.c via g_ascii_cache.luminance_palette
283
284void precalc_rgb_palettes(const float red, const float green, const float blue) {
285 // Validate input parameters to prevent overflow
286 // Luminance weights should typically be in range 0.0-1.0
287 // But allow slightly larger values for brightness adjustment
288 const float max_weight = 255.0f; // Maximum value that won't overflow when multiplied by 255
289 const float min_weight = -255.0f; // Allow negative for color correction
290
291 // Note: isfinite() checks are skipped in Release builds due to -ffinite-math-only flag
292 // which disables NaN/infinity support for performance. Range checks below are sufficient.
293#if !defined(NDEBUG) && !defined(__FAST_MATH__)
294 if (!isfinite(red) || !isfinite(green) || !isfinite(blue)) {
295 log_error("Invalid weight values (non-finite): red=%f, green=%f, blue=%f", red, green, blue);
296 SET_ERRNO(ERROR_INVALID_PARAM, "precalc_rgb_palettes: non-finite weight values");
297 return;
298 }
299#endif
300
301 if (red < min_weight || red > max_weight || green < min_weight || green > max_weight || blue < min_weight ||
302 blue > max_weight) {
303 log_warn(
304 "precalc_rgb_palettes: Weight values out of expected range: red=%f, green=%f, blue=%f (clamping to safe range)",
305 red, green, blue);
306 }
307
308 // Clamp weights to safe range to prevent overflow
309 const float safe_red = (red < min_weight) ? min_weight : ((red > max_weight) ? max_weight : red);
310 const float safe_green = (green < min_weight) ? min_weight : ((green > max_weight) ? max_weight : green);
311 const float safe_blue = (blue < min_weight) ? min_weight : ((blue > max_weight) ? max_weight : blue);
312
313 const unsigned short max_ushort = 65535;
314 const unsigned short min_ushort = 0;
315
316 for (int n = 0; n < ASCII_LUMINANCE_LEVELS; ++n) {
317 // Compute with float, then clamp to unsigned short range
318 float red_val = (float)n * safe_red;
319 float green_val = (float)n * safe_green;
320 float blue_val = (float)n * safe_blue;
321
322 // Clamp to unsigned short range before assignment
323 if (red_val < (float)min_ushort) {
324 red_val = (float)min_ushort;
325 } else if (red_val > (float)max_ushort) {
326 red_val = (float)max_ushort;
327 }
328
329 if (green_val < (float)min_ushort) {
330 green_val = (float)min_ushort;
331 } else if (green_val > (float)max_ushort) {
332 green_val = (float)max_ushort;
333 }
334
335 if (blue_val < (float)min_ushort) {
336 blue_val = (float)min_ushort;
337 } else if (blue_val > (float)max_ushort) {
338 blue_val = (float)max_ushort;
339 }
340
341 RED[n] = (unsigned short)red_val;
342 GREEN[n] = (unsigned short)green_val;
343 BLUE[n] = (unsigned short)blue_val;
344 GRAY[n] = (unsigned short)n;
345 }
346}
347
348// Optimized image printing with better memory access patterns
349char *image_print(const image_t *p, const char *palette) {
350 if (!p || !palette) {
351 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: p=%p or palette=%p is NULL", p, palette);
352 return NULL;
353 }
354 if (!p->pixels) {
355 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: p->pixels is NULL");
356 return NULL;
357 }
358
359 const int h = p->h;
360 const int w = p->w;
361
362 if (h <= 0 || w <= 0) {
363 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: invalid dimensions h=%d, w=%d", h, w);
364 return NULL;
365 }
366
367 // Get UTF-8 character cache for proper multi-byte character support
368 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
369 if (!utf8_cache) {
370 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 palette cache for scalar rendering");
371 return NULL;
372 }
373
374 // Character index ramp is now part of UTF-8 cache - no separate cache needed
375
376 // Need space for h rows with UTF-8 characters, plus h-1 newlines, plus null terminator
377 const size_t max_char_bytes = 4; // Max UTF-8 character size
378
379 const rgb_pixel_t *pix = p->pixels;
380
381 // Use outbuf_t for efficient UTF-8 RLE emission (same as SIMD renderers)
382 outbuf_t ob = {0};
383
384 // Calculate buffer size with overflow checking
385 size_t w_times_bytes;
386 if (checked_size_mul((size_t)w, max_char_bytes, &w_times_bytes) != ASCIICHAT_OK) {
387 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: width too large for UTF-8 encoding");
388 return NULL;
389 }
390
391 size_t w_times_bytes_plus_one;
392 if (checked_size_add(w_times_bytes, 1, &w_times_bytes_plus_one) != ASCIICHAT_OK) {
393 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: width * bytes + 1 overflow");
394 return NULL;
395 }
396
397 if (checked_size_mul((size_t)h, w_times_bytes_plus_one, &ob.cap) != ASCIICHAT_OK) {
398 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: height * (width * bytes + 1) overflow");
399 return NULL;
400 }
401
402 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
403 if (!ob.buf) {
404 SET_ERRNO(ERROR_MEMORY, "Failed to allocate output buffer for scalar rendering");
405 return NULL;
406 }
407
408 // Process pixels with UTF-8 RLE emission (same approach as SIMD)
409 for (int y = 0; y < h; y++) {
410 const int row_offset = y * w;
411
412 for (int x = 0; x < w;) {
413 const rgb_pixel_t pixel = pix[row_offset + x];
414 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
415 const int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
416
417 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
418 uint8_t safe_luminance = clamp_rgb(luminance);
419 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
420 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
421
422 // Use same 64-entry cache as SIMD for consistency
423 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
424
425 // Find run length for same character (RLE optimization)
426 int j = x + 1;
427 while (j < w) {
428 const rgb_pixel_t next_pixel = pix[row_offset + j];
429 const int next_luminance = (77 * next_pixel.r + 150 * next_pixel.g + 29 * next_pixel.b + 128) >> 8;
430 uint8_t next_safe_luminance = clamp_rgb(next_luminance);
431 uint8_t next_luma_idx = (uint8_t)(next_safe_luminance >> 2); // 0-63 index (same as SIMD)
432 uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx]; // Map to character index (same as SIMD)
433 if (next_char_idx != char_idx)
434 break;
435 j++;
436 }
437 uint32_t run = (uint32_t)(j - x);
438
439 // Emit UTF-8 character with RLE (same as SIMD)
440 ob_write(&ob, char_info->utf8_bytes, char_info->byte_len);
441 if (rep_is_profitable(run)) {
442 emit_rep(&ob, run - 1);
443 } else {
444 for (uint32_t k = 1; k < run; k++) {
445 ob_write(&ob, char_info->utf8_bytes, char_info->byte_len);
446 }
447 }
448 x = j;
449 }
450
451 // Add newline between rows (except last row)
452 if (y != h - 1) {
453 ob_putc(&ob, '\n');
454 }
455 }
456
457 ob_term(&ob);
458 return ob.buf;
459}
460
461// Color quantization to reduce frame size and improve performance
462void quantize_color(int *r, int *g, int *b, int levels) {
463 if (!r || !g || !b) {
464 SET_ERRNO(ERROR_INVALID_PARAM, "quantize_color: r, g, or b is NULL");
465 return;
466 }
467
468 if (levels <= 0) {
469 SET_ERRNO(ERROR_INVALID_PARAM, "quantize_color: levels must be positive, got %d", levels);
470 return;
471 }
472
473 int step = 256 / levels;
474 *r = (*r / step) * step;
475 *g = (*g / step) * step;
476 *b = (*b / step) * step;
477}
478
513char *image_print_color(const image_t *p, const char *palette) {
514 if (!p || !palette) {
515 SET_ERRNO(ERROR_INVALID_PARAM, "p=%p or palette=%p is NULL", p, palette);
516 return NULL;
517 }
518 if (!p->pixels) {
519 SET_ERRNO(ERROR_INVALID_PARAM, "p->pixels is NULL");
520 return NULL;
521 }
522
523 // Get UTF-8 character cache for proper multi-byte character support
524 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
525 if (!utf8_cache) {
526 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 palette cache for scalar color rendering");
527 return NULL;
528 }
529
530 const int h = p->h;
531 const int w = p->w;
532
533 // Constants for ANSI escape codes (using exact sizes from ansi_fast.c)
534 const size_t max_fg_ansi = 19; // \033[38;2;255;255;255m
535 const size_t max_bg_ansi = 19; // \033[48;2;255;255;255m
536 const size_t reset_len = 4; // \033[0m
537
538 const size_t h_sz = (size_t)h;
539 const size_t w_sz = (size_t)w;
540
541 // Ensure h * w won't overflow
542 if (h_sz > 0 && w_sz > SIZE_MAX / h_sz) {
543 SET_ERRNO(ERROR_INVALID_STATE, "Image dimensions too large: %d x %d", h, w);
544 return NULL;
545 }
546
547 const size_t total_pixels = h_sz * w_sz;
548 const size_t bytes_per_pixel = 1 + max_fg_ansi + max_bg_ansi; // Conservative estimate for max possible
549
550 // Ensure total_pixels * bytes_per_pixel won't overflow
551 if (total_pixels > SIZE_MAX / bytes_per_pixel) {
552 SET_ERRNO(ERROR_INVALID_STATE, "Pixel data too large for buffer: %d x %d", h, w);
553 return NULL;
554 }
555
556 const size_t pixel_bytes = total_pixels * bytes_per_pixel;
557
558 // Per row: reset sequence + newline (except last row)
559 const size_t total_resets = h_sz * reset_len;
560 const size_t total_newlines = (h_sz > 0) ? (h_sz - 1) : 0;
561
562 // Final buffer size: pixel bytes + per-row extras + null terminator
563 const size_t extra_bytes = total_resets + total_newlines + 1;
564
565 if (pixel_bytes > SIZE_MAX - extra_bytes) {
566 SET_ERRNO(ERROR_INVALID_STATE, "Final buffer size would overflow: %d x %d", h, w);
567 return NULL;
568 }
569
570 const size_t lines_size = pixel_bytes + extra_bytes;
571 char *lines;
572 lines = SAFE_MALLOC(lines_size, char *);
573
574 const rgb_pixel_t *pix = p->pixels;
575 // char *current_pos = lines;
576 // const char *buffer_end = lines + lines_size - 1; // reserve space for '\0'
577
578 // Initialize the optimized RLE context for color sequence caching
579 // Note: This function should be called via image_print_with_capabilities() for proper per-client rendering
580 ansi_rle_context_t rle_ctx;
581 ansi_color_mode_t color_mode = ANSI_MODE_FOREGROUND; // Default to foreground-only for legacy usage
582 ansi_rle_init(&rle_ctx, lines, lines_size, color_mode);
583
584 // Process each pixel using the optimized RLE context
585 for (int y = 0; y < h; y++) {
586 const int row_offset = y * w;
587
588 for (int x = 0; x < w; x++) {
589 const rgb_pixel_t pixel = pix[row_offset + x];
590 int r = pixel.r, g = pixel.g, b = pixel.b;
591 // Standard ITU-R BT.601 luminance calculation
592 const int luminance = (77 * r + 150 * g + 29 * b + 128) >> 8;
593
594 // Use UTF-8 character cache for proper character selection
595 uint8_t safe_luminance = clamp_rgb(luminance);
596 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
597
598 // For RLE, we need to pass the first byte of the UTF-8 character
599 // Note: RLE system may need updates for full UTF-8 support
600 const char ascii_char = char_info->utf8_bytes[0];
601
602 ansi_rle_add_pixel(&rle_ctx, (uint8_t)r, (uint8_t)g, (uint8_t)b, ascii_char);
603 }
604
605 // Add newline between rows (except last row)
606 if (y != h - 1 && rle_ctx.length < rle_ctx.capacity - 1) {
607 rle_ctx.buffer[rle_ctx.length++] = '\n';
608 }
609 }
610
611 ansi_rle_finish(&rle_ctx);
612 return lines;
613}
614
615// RGB to ANSI color conversion functions
616char *rgb_to_ansi_fg(int r, int g, int b) {
617 _Thread_local static char color_code[20]; // \033[38;2;255;255;255m + \0 = 20 bytes max
618 SAFE_SNPRINTF(color_code, sizeof(color_code), "\033[38;2;%d;%d;%dm", r, g, b);
619 return color_code;
620}
621
622char *rgb_to_ansi_bg(int r, int g, int b) {
623 _Thread_local static char color_code[20]; // \033[48;2;255;255;255m + \0 = 20 bytes max
624 SAFE_SNPRINTF(color_code, sizeof(color_code), "\033[48;2;%d;%d;%dm", r, g, b);
625 return color_code;
626}
627
628void rgb_to_ansi_8bit(int r, int g, int b, int *fg_code, int *bg_code) {
629 if (!fg_code || !bg_code) {
630 SET_ERRNO(ERROR_INVALID_PARAM, "rgb_to_ansi_8bit: fg_code or bg_code is NULL");
631 return;
632 }
633
634 // Convert RGB to 8-bit color code (216 color cube + 24 grayscale)
635 if (r == g && g == b) {
636 // Grayscale
637 if (r < 8) {
638 *fg_code = 16;
639 } else if (r > 248) {
640 *fg_code = 231;
641 } else {
642 *fg_code = 232 + (r - 8) / 10;
643 }
644 } else {
645 // Color cube: 16 + 36*r + 6*g + b where r,g,b are 0-5
646 int r_level = (r * 5) / 255;
647 int g_level = (g * 5) / 255;
648 int b_level = (b * 5) / 255;
649 *fg_code = 16 + 36 * r_level + 6 * g_level + b_level;
650 }
651 *bg_code = *fg_code; // Same logic for background
652}
653
654// Capability-aware image printing function
655char *image_print_with_capabilities(const image_t *image, const terminal_capabilities_t *caps, const char *palette,
656 const char luminance_palette[256] __attribute__((unused))) {
657
658 if (!image || !image->pixels || !caps || !palette) {
659 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or caps=%p or palette=%p is NULL", image,
660 image->pixels, caps, palette);
661 return NULL;
662 }
663
664 // Handle half-block mode first (requires NEON)
665 if (caps->render_mode == RENDER_MODE_HALF_BLOCK) {
666#if SIMD_SUPPORT_NEON
667 // Use NEON half-block renderer
668 const uint8_t *rgb_data = (const uint8_t *)image->pixels;
669 return rgb_to_truecolor_halfblocks_neon(rgb_data, image->w, image->h, 0);
670#else
671 SET_ERRNO(ERROR_INVALID_STATE, "Half-block mode requires NEON support (ARM architecture)");
672 return NULL;
673#endif
674 }
675
676 // Standard color modes
677 bool use_background_mode = (caps->render_mode == RENDER_MODE_BACKGROUND);
678
679 char *result = NULL;
680
681 // Choose the appropriate printing method based on terminal capabilities
682 switch (caps->color_level) {
684 // Use existing truecolor printing function with client's palette
685#ifdef SIMD_SUPPORT
686 result = image_print_color_simd((image_t *)image, use_background_mode, false, palette);
687#else
688 result = image_print_color(image, palette);
689#endif
690 break;
691
692 case TERM_COLOR_256:
693#ifdef SIMD_SUPPORT
694 result = image_print_color_simd((image_t *)image, use_background_mode, true, palette);
695#else
696 // Use 256-color conversion
697 result = image_print_256color(image, palette);
698#endif
699 break;
700
701 case TERM_COLOR_16:
702 // Use 16-color conversion with Floyd-Steinberg dithering for better quality
703 result = image_print_16color_dithered_with_background(image, use_background_mode, palette);
704 break;
705
706 case TERM_COLOR_NONE:
707 default:
708 // Use grayscale/monochrome conversion with client's custom palette
709#ifdef SIMD_SUPPORT
710 result = image_print_simd((image_t *)image, palette);
711#else
712 result = image_print(image, palette);
713#endif
714 break;
715 }
716
717 // Note: Background mode is now handled via capabilities rather than global state
718 return result;
719}
720
721// 256-color image printing function using existing SIMD optimized code
722char *image_print_256color(const image_t *image, const char *palette) {
723 if (!image || !image->pixels || !palette) {
724 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
725 return NULL;
726 }
727
728 // Use the existing optimized SIMD colored printing (no background for 256-color mode)
729#ifdef SIMD_SUPPORT
730 char *result = image_print_color_simd((image_t *)image, false, true, palette);
731#else
732 char *result = image_print_color(image, palette);
733#endif
734
735 return result;
736}
737
738// 16-color image printing function using ansi_fast color conversion
739char *image_print_16color(const image_t *image, const char *palette) {
740 if (!image || !image->pixels || !palette) {
741 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
742 return NULL;
743 }
744
745 int h = image->h;
746 int w = image->w;
747
748 if (h <= 0 || w <= 0) {
749 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color: invalid dimensions h=%d, w=%d", h, w);
750 return NULL;
751 }
752
753 // Initialize 16-color lookup table
755
756 // Calculate buffer size (smaller than 256-color due to shorter ANSI sequences)
757 // Space for ANSI codes + newlines
758 // Calculate with overflow checking
759 size_t h_times_w;
760 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w) != ASCIICHAT_OK) {
761 return NULL;
762 }
763
764 size_t h_times_w_times_12;
765 if (checked_size_mul(h_times_w, 12u, &h_times_w_times_12) != ASCIICHAT_OK) {
766 return NULL;
767 }
768
769 size_t buffer_size;
770 if (checked_size_add(h_times_w_times_12, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
771 return NULL;
772 }
773
774 char *buffer;
775 buffer = SAFE_MALLOC(buffer_size, char *);
776
777 char *ptr = buffer;
778 const char *reset_code = "\033[0m";
779
780 for (int y = 0; y < h; y++) {
781 for (int x = 0; x < w; x++) {
782 rgb_pixel_t pixel = image->pixels[y * w + x];
783
784 // Convert RGB to 16-color index and generate ANSI sequence
785 uint8_t color_index = rgb_to_16color(pixel.r, pixel.g, pixel.b);
786 ptr = append_16color_fg(ptr, color_index);
787
788 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
789 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
790
791 // Use UTF-8 cache which contains character index ramp
792 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
793 if (!utf8_cache) {
794 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache");
795 return NULL;
796 }
797
798 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
799 uint8_t safe_luminance = clamp_rgb(luminance);
800 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
801 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
802
803 // Use direct palette character lookup (same as SIMD would do)
804 char ascii_char = palette[char_idx];
805 const utf8_char_t *char_info = &utf8_cache->cache[(unsigned char)ascii_char];
806
807 if (char_info) {
808 // Copy UTF-8 character bytes
809 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
810 *ptr++ = char_info->utf8_bytes[byte_idx];
811 }
812 } else {
813 // Fallback to simple ASCII if UTF-8 cache fails
814 size_t palette_len = strlen(palette);
815 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
816 *ptr++ = palette[palette_index];
817 }
818 }
819
820 // Add reset and newline at end of each row
821 SAFE_STRNCPY(ptr, reset_code, 5);
822 ptr += strlen(reset_code);
823 if (y < h - 1) {
824 *ptr++ = '\n';
825 }
826 }
827
828 *ptr = '\0';
829 return buffer;
830}
831
832// 16-color image printing with Floyd-Steinberg dithering
833char *image_print_16color_dithered(const image_t *image, const char *palette) {
834 if (!image || !image->pixels || !palette) {
835 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
836 return NULL;
837 }
838
839 int h = image->h;
840 int w = image->w;
841
842 if (h <= 0 || w <= 0) {
843 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color_dithered: invalid dimensions h=%d, w=%d", h, w);
844 return NULL;
845 }
846
847 // Initialize 16-color lookup table
849
850 // Allocate error buffer for Floyd-Steinberg dithering
851 size_t pixel_count = (size_t)h * (size_t)w;
852 rgb_error_t *error_buffer;
853 error_buffer = SAFE_CALLOC(pixel_count, sizeof(rgb_error_t), rgb_error_t *);
854
855 // Calculate buffer size (same as non-dithered version)
856 // Space for ANSI codes + newlines
857 // Calculate with overflow checking
858 size_t h_times_w2;
859 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w2) != ASCIICHAT_OK) {
860 SAFE_FREE(error_buffer);
861 return NULL;
862 }
863
864 size_t h_times_w2_times_12;
865 if (checked_size_mul(h_times_w2, 12u, &h_times_w2_times_12) != ASCIICHAT_OK) {
866 SAFE_FREE(error_buffer);
867 return NULL;
868 }
869
870 size_t buffer_size;
871 if (checked_size_add(h_times_w2_times_12, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
872 SAFE_FREE(error_buffer);
873 return NULL;
874 }
875
876 char *buffer;
877 buffer = SAFE_MALLOC(buffer_size, char *);
878
879 char *ptr = buffer;
880 const char *reset_code = "\033[0m";
881
882 for (int y = 0; y < h; y++) {
883 for (int x = 0; x < w; x++) {
884 rgb_pixel_t pixel = image->pixels[y * w + x];
885
886 // Convert RGB to 16-color index using dithering
887 uint8_t color_index = rgb_to_16color_dithered(pixel.r, pixel.g, pixel.b, x, y, w, h, error_buffer);
888 ptr = append_16color_fg(ptr, color_index);
889
890 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
891 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
892
893 // Use UTF-8 cache which contains character index ramp
894 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
895 if (!utf8_cache) {
896 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache");
897 return NULL;
898 }
899
900 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
901 uint8_t safe_luminance = clamp_rgb(luminance);
902 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
903 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
904
905 // Use direct palette character lookup (same as SIMD would do)
906 char ascii_char = palette[char_idx];
907 const utf8_char_t *char_info = &utf8_cache->cache[(unsigned char)ascii_char];
908
909 if (char_info) {
910 // Copy UTF-8 character bytes
911 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
912 *ptr++ = char_info->utf8_bytes[byte_idx];
913 }
914 } else {
915 // Fallback to simple ASCII if UTF-8 cache fails
916 size_t palette_len = strlen(palette);
917 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
918 *ptr++ = palette[palette_index];
919 }
920 }
921
922 // Add reset and newline at end of each row
923 SAFE_STRNCPY(ptr, reset_code, 5);
924 ptr += strlen(reset_code);
925 if (y < h - 1) {
926 *ptr++ = '\n';
927 }
928 }
929
930 *ptr = '\0';
931 SAFE_FREE(error_buffer); // Clean up error buffer
932 return buffer;
933}
934
935// 16-color image printing with Floyd-Steinberg dithering and background mode support
936char *image_print_16color_dithered_with_background(const image_t *image, bool use_background, const char *palette) {
937 if (!image || !image->pixels || !palette) {
938 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
939 return NULL;
940 }
941
942 int h = image->h;
943 int w = image->w;
944
945 if (h <= 0 || w <= 0) {
946 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color_dithered_with_background: invalid dimensions h=%d, w=%d", h, w);
947 return NULL;
948 }
949
950 // Initialize 16-color lookup table
952
953 // Allocate error buffer for Floyd-Steinberg dithering
954 size_t pixel_count = (size_t)h * (size_t)w;
955 rgb_error_t *error_buffer;
956 error_buffer = SAFE_CALLOC(pixel_count, sizeof(rgb_error_t), rgb_error_t *);
957
958 // Calculate buffer size (larger for background mode due to more ANSI sequences)
959 size_t buffer_size =
960 (size_t)h * (size_t)w * (use_background ? 24 : 12) + (size_t)h; // Space for ANSI codes + newlines
961 char *buffer;
962 buffer = SAFE_MALLOC(buffer_size, char *);
963
964 char *ptr = buffer;
965 const char *reset_code = "\033[0m";
966
967 for (int y = 0; y < h; y++) {
968 for (int x = 0; x < w; x++) {
969 rgb_pixel_t pixel = image->pixels[y * w + x];
970
971 // Convert RGB to 16-color index using dithering
972 uint8_t color_index = rgb_to_16color_dithered(pixel.r, pixel.g, pixel.b, x, y, w, h, error_buffer);
973
974 if (use_background) {
975 // Background mode: use contrasting foreground on background color
976 uint8_t bg_r, bg_g, bg_b;
977 get_16color_rgb(color_index, &bg_r, &bg_g, &bg_b);
978
979 // Calculate luminance to choose contrasting foreground
980 int bg_luminance = (bg_r * 77 + bg_g * 150 + bg_b * 29) / 256;
981 uint8_t fg_color = (bg_luminance < 127) ? 15 : 0; // White on dark, black on bright
982
983 ptr = append_16color_bg(ptr, color_index); // Set background to pixel color
984 ptr = append_16color_fg(ptr, fg_color); // Set contrasting foreground
985 } else {
986 // Foreground mode: set foreground to pixel color
987 ptr = append_16color_fg(ptr, color_index);
988 }
989
990 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
991 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
992
993 // Use UTF-8 cache which contains character index ramp
994 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
995 if (!utf8_cache) {
996 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache");
997 return NULL;
998 }
999
1000 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
1001 uint8_t safe_luminance = clamp_rgb(luminance);
1002 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
1003 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
1004
1005 // Use direct palette character lookup (same as SIMD would do)
1006 char ascii_char = palette[char_idx];
1007 const utf8_char_t *char_info = &utf8_cache->cache[(unsigned char)ascii_char];
1008
1009 if (char_info) {
1010 // Copy UTF-8 character bytes
1011 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
1012 *ptr++ = char_info->utf8_bytes[byte_idx];
1013 }
1014 } else {
1015 // Fallback to simple ASCII if UTF-8 cache fails
1016 size_t palette_len = strlen(palette);
1017 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
1018 *ptr++ = palette[palette_index];
1019 }
1020 }
1021
1022 // Add reset and newline at end of each row
1023 SAFE_STRNCPY(ptr, reset_code, 5);
1024 ptr += strlen(reset_code);
1025 if (y < h - 1) {
1026 *ptr++ = '\n';
1027 }
1028 }
1029
1030 *ptr = '\0';
1031 SAFE_FREE(error_buffer); // Clean up error buffer
1032 return buffer;
1033}
Fast ANSI escape sequence generation.
🖼️ ASCII Art Conversion and Output Interface
SIMD-optimized ASCII conversion interface.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
#define SAFE_MALLOC_SIMD(size, cast)
Definition common.h:308
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:389
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
#define SAFE_CALLOC(count, size, cast)
Definition common.h:218
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
unsigned short int GRAY[256]
Grayscale lookup table.
unsigned short int BLUE[256]
Blue channel lookup table.
unsigned short int GREEN[256]
Green channel lookup table.
unsigned short int RED[256]
Red channel lookup table.
@ RENDER_MODE_BACKGROUND
Background colors (block colors)
Definition terminal.h:471
@ RENDER_MODE_HALF_BLOCK
Unicode half-block characters (mixed foreground/background)
Definition terminal.h:473
@ TERM_COLOR_NONE
No color support (monochrome terminal)
Definition terminal.h:428
@ TERM_COLOR_16
16-color support (standard ANSI colors)
Definition terminal.h:430
@ TERM_COLOR_256
256-color support (extended ANSI palette)
Definition terminal.h:432
@ TERM_COLOR_TRUECOLOR
24-bit truecolor support (RGB colors)
Definition terminal.h:434
char * append_16color_fg(char *dst, uint8_t color_index)
Append 16-color foreground ANSI sequence.
Definition ansi_fast.c:243
void quantize_color(int *r, int *g, int *b, int levels)
Quantize color to specified number of levels.
void precalc_rgb_palettes(const float red, const float green, const float blue)
Precalculate RGB palettes with color adjustment.
void image_resize_interpolation(const image_t *source, image_t *dest)
Resize image using bilinear interpolation.
char * append_16color_bg(char *dst, uint8_t color_index)
Append 16-color background ANSI sequence.
Definition ansi_fast.c:260
char * image_print_16color_dithered(const image_t *image, const char *palette)
Print image using 16-color ANSI mode with dithering.
char * image_print(const image_t *p, const char *palette)
Print image as ASCII art (monochrome)
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)
Get or create UTF-8 palette cache.
uint8_t rgb_to_16color(uint8_t r, uint8_t g, uint8_t b)
Convert RGB to 16-color ANSI index.
Definition ansi_fast.c:277
void image_destroy_to_pool(image_t *image)
Destroy an image allocated from buffer pool.
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.
char * rgb_to_ansi_bg(int r, int g, int b)
Convert RGB to ANSI background color code.
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)
Convert RGB to 16-color with Floyd-Steinberg dithering.
Definition ansi_fast.c:351
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.
void ob_putc(outbuf_t *ob, char c)
Append a character to buffer.
ansi_color_mode_t
Color mode for ANSI generation.
Definition ansi_fast.h:40
bool rep_is_profitable(uint32_t runlen)
Check if run-length encoding is profitable.
void ansi_rle_init(ansi_rle_context_t *ctx, char *buffer, size_t capacity, ansi_color_mode_t mode)
Initialize run-length encoding context.
Definition ansi_fast.c:112
#define ASCII_LUMINANCE_LEVELS
Number of luminance levels supported (256)
Definition ascii.h:427
char * image_print_16color(const image_t *image, const char *palette)
Print image using 16-color ANSI mode.
void emit_rep(outbuf_t *ob, uint32_t extra)
Emit run-length encoded sequence.
#define IMAGE_MAX_WIDTH
Maximum image width (4K resolution)
void ob_write(outbuf_t *ob, const char *s, size_t n)
Append a string to buffer.
void ansi_rle_finish(ansi_rle_context_t *ctx)
Finish RLE sequence.
Definition ansi_fast.c:159
#define IMAGE_MAX_PIXELS_SIZE
Maximum pixel data size in bytes.
void get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b)
Get the actual RGB values for a 16-color ANSI index.
Definition ansi_fast.c:320
#define IMAGE_MAX_HEIGHT
Maximum image height (4K resolution)
void ansi_rle_add_pixel(ansi_rle_context_t *ctx, uint8_t r, uint8_t g, uint8_t b, char ascii_char)
Add a pixel with run-length encoding.
Definition ansi_fast.c:125
char * rgb_to_ansi_fg(int r, int g, int b)
Convert RGB to ANSI foreground color code.
void image_clear(image_t *p)
Clear image (set all pixels to black)
void image_resize(const image_t *s, image_t *d)
Resize image using nearest-neighbor interpolation.
char * image_print_simd(image_t *image, const char *ascii_chars)
Print image as ASCII using SIMD (monochrome)
Definition ascii_simd.c:252
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.
char * image_print_256color(const image_t *image, const char *palette)
Print image using 256-color ANSI mode.
image_t * image_new_from_pool(size_t width, size_t height)
Create a new image from buffer pool.
void ansi_fast_init_16color(void)
Initialize 16-color mode lookup tables.
Definition ansi_fast.c:225
char * image_print_color_simd(image_t *image, bool use_background_mode, bool use_256color, const char *ascii_chars)
Print image as ASCII with color using SIMD.
void image_destroy(image_t *p)
Destroy an image allocated with image_new()
Definition video/image.c:85
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
Definition video/image.c:36
@ IMAGE_ALLOC_POOL
Pixels allocated with buffer_pool_alloc()
@ IMAGE_ALLOC_SIMD
Pixels allocated with SAFE_MALLOC_SIMD()
@ ANSI_MODE_FOREGROUND
Definition ansi_fast.h:41
Application limits and constraints.
🔢 Mathematical Utility Functions
⚙️ Command-line options parsing and configuration management for ascii-chat
Dynamic Output Buffer with ANSI Sequence Support.
✅ Safe Integer Arithmetic and Overflow Detection
RGB pixel structure.
Definition video/image.h:80
Run-length encoded color output context.
Definition ansi_fast.h:115
Image structure.
int w
Image width in pixels (must be > 0)
uint8_t alloc_method
Allocation method (image_alloc_method_t) for correct deallocation.
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 error structure for dithering.
Definition ansi_fast.h:234
Complete terminal capabilities structure.
Definition terminal.h:485
terminal_color_mode_t color_level
Detected color support level (terminal_color_mode_t)
Definition terminal.h:487
render_mode_t render_mode
Preferred rendering mode (render_mode_t)
Definition terminal.h:497
UTF-8 character structure.
UTF-8 palette cache structure.
asciichat_error_t image_validate_dimensions(size_t width, size_t height)
Validate image dimensions (non-zero, within limits)
Definition util/image.c:100
🖼️ Safe overflow-checked buffer size calculations for images and video frames
rgb_pixel_t * image_pixel(image_t *p, const int x, const int y)
char * image_print_with_capabilities(const image_t *image, const terminal_capabilities_t *caps, const char *palette, const char luminance_palette[256] __attribute__((unused)))
Image Data Structures and Operations.
Common SIMD utilities and structures.