ascii-chat 0.8.38
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 <ascii-chat/common.h>
20#include <ascii-chat/video/output_buffer.h>
21#include <ascii-chat/video/image.h>
22#include <ascii-chat/video/ascii.h>
23#include <ascii-chat/video/simd/ascii_simd.h>
24#include <ascii-chat/video/simd/common.h>
25#include <ascii-chat/video/ansi_fast.h>
26#include <ascii-chat/options/options.h>
27#include <ascii-chat/buffer_pool.h> // For buffer pool allocation functions
28#include <ascii-chat/util/overflow.h>
29#include <ascii-chat/util/image.h>
30#include <ascii-chat/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
85void image_destroy(image_t *p) {
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}
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,
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}
170
171void image_destroy_to_pool(image_t *image) {
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
203void image_clear(image_t *p) {
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}
223
224image_t *image_new_copy(const image_t *source) {
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}
245
246inline rgb_pixel_t *image_pixel(image_t *p, const int x, const int y) {
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}
253
254void image_resize(const image_t *s, image_t *d) {
255 if (!s || !d) {
256 SET_ERRNO(ERROR_INVALID_PARAM, "image_resize: s or d is NULL");
257 return;
258 }
259
261}
262
263// Optimized interpolation function with better integer arithmetic and memory
264// access
265void image_resize_interpolation(const image_t *source, image_t *dest) {
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}
327
328// Note: luminance_palette is now handled by ascii_simd.c via g_ascii_cache.luminance_palette
329
330void precalc_rgb_palettes(const float red, const float green, const float blue) {
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}
393
394// Optimized image printing with better memory access patterns
395char *image_print(const image_t *p, const char *palette) {
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}
506
507// Color quantization to reduce frame size and improve performance
508void quantize_color(int *r, int *g, int *b, int levels) {
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}
524
559char *image_print_color(const image_t *p, const char *palette) {
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}
717
718// RGB to ANSI color conversion functions
719char *rgb_to_ansi_fg(int r, int g, int b) {
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}
724
725char *rgb_to_ansi_bg(int r, int g, int b) {
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}
730
731void rgb_to_ansi_8bit(int r, int g, int b, int *fg_code, int *bg_code) {
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}
756
757// Capability-aware image printing function
758char *image_print_with_capabilities(const image_t *image, const terminal_capabilities_t *caps, const char *palette) {
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}
843
844// 256-color image printing function using existing SIMD optimized code
845char *image_print_256color(const image_t *image, const char *palette) {
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}
860
861// 16-color image printing function using ansi_fast color conversion
862char *image_print_16color(const image_t *image, const char *palette) {
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}
954
955// 16-color image printing with Floyd-Steinberg dithering
956char *image_print_16color_dithered(const image_t *image, const char *palette) {
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}
1057
1058// 16-color image printing with Floyd-Steinberg dithering and background mode support
1059char *image_print_16color_dithered_with_background(const image_t *image, bool use_background, const char *palette) {
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_fg(char *dst, uint8_t color_index)
Definition ansi_fast.c:280
char * append_16color_bg(char *dst, uint8_t color_index)
Definition ansi_fast.c:297
uint8_t rgb_to_16color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi_fast.c:314
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
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 get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b)
Definition ansi_fast.c:357
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
void ansi_fast_init_16color(void)
Definition ansi_fast.c:257
char * image_print_simd(image_t *image, const char *ascii_chars)
Definition ascii_simd.c:260
char * image_print_color_simd(image_t *image, bool use_background_mode, bool use_256color, const char *ascii_chars)
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Definition buffer_pool.c:99
int buffer_size
Size of circular buffer.
Definition grep.c:84
unsigned short int GREEN[256]
unsigned short int GRAY[256]
unsigned short int RED[256]
unsigned short int BLUE[256]
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)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
image_t * image_new_copy(const image_t *source)
void quantize_color(int *r, int *g, int *b, int levels)
void precalc_rgb_palettes(const float red, const float green, const float blue)
void image_resize_interpolation(const image_t *source, image_t *dest)
char * image_print_16color_dithered(const image_t *image, const char *palette)
char * image_print(const image_t *p, const char *palette)
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)
void image_destroy_to_pool(image_t *image)
char * image_print_color(const image_t *p, const char *palette)
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)
void image_resize(const image_t *s, image_t *d)
char * image_print_16color(const image_t *image, const char *palette)
char * rgb_to_ansi_fg(int r, int g, int b)
void image_clear(image_t *p)
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)
image_t * image_new_from_pool(size_t width, size_t height)
void image_destroy(image_t *p)
Definition video/image.c:85
image_t * image_new(size_t width, size_t height)
Definition video/image.c:36
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)
asciichat_error_t image_validate_dimensions(size_t width, size_t height)
Definition video.c:12