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>
28#include <ascii-chat/util/overflow.h>
29#include <ascii-chat/util/image.h>
30#include <ascii-chat/util/math.h>
39 p = SAFE_MALLOC(
sizeof(image_t), image_t *);
43 SET_ERRNO(ERROR_INVALID_PARAM,
"Image dimensions invalid or too large: %zu x %zu", width, height);
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);
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");
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,
72 p->pixels = SAFE_MALLOC_SIMD(pixels_size, rgb_pixel_t *);
74 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate image pixels: %zu bytes", pixels_size);
81 p->alloc_method = IMAGE_ALLOC_SIMD;
87 SET_ERRNO(ERROR_INVALID_PARAM,
"image_destroy: p is NULL");
92 if (p->alloc_method == IMAGE_ALLOC_POOL) {
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);
101 size_t w = (size_t)p->w;
102 size_t h = (size_t)p->h;
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);
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);
120 SAFE_FREE(p->pixels);
127 if (width == 0 || height == 0) {
128 SET_ERRNO(ERROR_INVALID_PARAM,
"image_new_from_pool: invalid dimensions %zux%zu", width, height);
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);
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);
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");
155 SET_ERRNO(ERROR_MEMORY,
"image_new_from_pool: buffer pool allocation failed for %zu bytes (%zux%zu)", total_size,
161 image_t *image = (image_t *)buffer;
162 image->w = (int)width;
163 image->h = (int)height;
165 image->pixels = (rgb_pixel_t *)((uint8_t *)buffer +
sizeof(image_t));
166 image->alloc_method = IMAGE_ALLOC_POOL;
173 SET_ERRNO(ERROR_INVALID_PARAM,
"image_destroy_to_pool: image is NULL");
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);
185 size_t w = (size_t)image->w;
186 size_t h = (size_t)image->h;
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);
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);
204 if (!p || !p->pixels) {
205 SET_ERRNO(ERROR_INVALID_PARAM,
"image_clear: p or p->pixels is NULL");
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);
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");
220 size_t clear_size = pixel_count *
sizeof(rgb_pixel_t);
221 SAFE_MEMSET(p->pixels, clear_size, 0, clear_size);
226 SET_ERRNO(ERROR_INVALID_PARAM,
"image_new_copy: source is NULL");
231 image_t *copy =
image_new((
size_t)source->w, (
size_t)source->h);
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);
246inline rgb_pixel_t *
image_pixel(image_t *p,
const int x,
const int y) {
248 if (!p || !p->pixels || x < 0 || x >= p->w || y < 0 || y >= p->h) {
251 return &p->pixels[x + y * p->w];
256 SET_ERRNO(ERROR_INVALID_PARAM,
"image_resize: s or d is NULL");
266 if (!source || !dest || !source->pixels || !dest->pixels) {
267 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters to image_resize_interpolation");
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;
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,
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");
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);
294 const rgb_pixel_t *src_pixels = source->pixels;
295 rgb_pixel_t *dst_pixels = dest->pixels;
297 for (
int y = 0; y < dst_h; y++) {
298 const uint32_t src_y = ((uint32_t)(
unsigned int)y * y_ratio) >> 16;
300 const uint32_t safe_src_y = src_y >= (uint32_t)(
unsigned int)src_h ? (uint32_t)(src_h - 1) : src_y;
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);
308 const rgb_pixel_t *src_row = src_pixels + (safe_src_y * (size_t)src_w);
310 rgb_pixel_t *dst_row = dst_pixels + ((size_t)y * (
size_t)dst_w);
312 for (
int x = 0; x < dst_w; x++) {
313 const uint32_t src_x = ((uint32_t)(
unsigned int)x * x_ratio) >> 16;
315 const uint32_t safe_src_x = src_x >= (uint32_t)(
unsigned int)src_w ? (uint32_t)(src_w - 1) : src_x;
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);
323 dst_row[x] = src_row[safe_src_x];
334 const float max_weight = 255.0f;
335 const float min_weight = -255.0f;
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");
347 if (red < min_weight || red > max_weight || green < min_weight || green > max_weight || blue < min_weight ||
350 "precalc_rgb_palettes: Weight values out of expected range: red=%f, green=%f, blue=%f (clamping to safe range)",
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);
359 const unsigned short max_ushort = 65535;
360 const unsigned short min_ushort = 0;
362 for (
int n = 0; n < ASCII_LUMINANCE_LEVELS; ++n) {
364 float red_val = (float)n * safe_red;
365 float green_val = (float)n * safe_green;
366 float blue_val = (float)n * safe_blue;
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;
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;
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;
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;
396 if (!p || !palette) {
397 SET_ERRNO(ERROR_INVALID_PARAM,
"image_print: p=%p or palette=%p is NULL", p, palette);
401 SET_ERRNO(ERROR_INVALID_PARAM,
"image_print: p->pixels is NULL");
408 if (h <= 0 || w <= 0) {
409 SET_ERRNO(ERROR_INVALID_PARAM,
"image_print: invalid dimensions h=%d, w=%d", h, w);
416 SET_ERRNO(ERROR_INVALID_STATE,
"Failed to get UTF-8 palette cache for scalar rendering");
423 const size_t max_char_bytes = 4;
425 const rgb_pixel_t *pix = p->pixels;
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");
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");
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");
448 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1,
char *);
450 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate output buffer for scalar rendering");
455 for (
int y = 0; y < h; y++) {
456 const int row_offset = y * w;
458 for (
int x = 0; x < w;) {
459 const rgb_pixel_t pixel = pix[row_offset + x];
461 const int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
464 uint8_t safe_luminance = clamp_rgb(luminance);
465 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2);
466 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx];
469 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
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);
478 uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx];
479 if (next_char_idx != char_idx)
483 uint32_t run = (uint32_t)(j - x);
486 ob_write(&ob, char_info->utf8_bytes, char_info->byte_len);
490 for (uint32_t k = 1; k < run; k++) {
491 ob_write(&ob, char_info->utf8_bytes, char_info->byte_len);
509 if (!r || !g || !b) {
510 SET_ERRNO(ERROR_INVALID_PARAM,
"quantize_color: r, g, or b is NULL");
515 SET_ERRNO(ERROR_INVALID_PARAM,
"quantize_color: levels must be positive, got %d", levels);
519 int step = 256 / levels;
520 *r = (*r / step) * step;
521 *g = (*g / step) * step;
522 *b = (*b / step) * step;
560 if (!p || !palette) {
561 SET_ERRNO(ERROR_INVALID_PARAM,
"p=%p or palette=%p is NULL", p, palette);
565 SET_ERRNO(ERROR_INVALID_PARAM,
"p->pixels is NULL");
572 SET_ERRNO(ERROR_INVALID_STATE,
"Failed to get UTF-8 palette cache for scalar color rendering");
580 const size_t max_fg_ansi = 19;
581 const size_t max_bg_ansi = 19;
582 const size_t reset_len = 4;
584 const size_t h_sz = (size_t)h;
585 const size_t w_sz = (size_t)w;
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);
593 const size_t total_pixels = h_sz * w_sz;
594 const size_t bytes_per_pixel = 1 + max_fg_ansi + max_bg_ansi;
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);
602 const size_t pixel_bytes = total_pixels * bytes_per_pixel;
606 const size_t total_newlines = (h_sz > 0) ? (h_sz - 1) : 0;
607 const size_t final_reset = reset_len;
610 const size_t extra_bytes = total_newlines + final_reset + 1;
612 if (pixel_bytes > SIZE_MAX - extra_bytes) {
613 SET_ERRNO(ERROR_INVALID_STATE,
"Final buffer size would overflow: %d x %d", h, w);
617 const size_t lines_size = pixel_bytes + extra_bytes;
619 lines = SAFE_MALLOC(lines_size,
char *);
621 const rgb_pixel_t *pix = p->pixels;
627 ansi_rle_context_t rle_ctx;
628 ansi_color_mode_t color_mode = ANSI_MODE_FOREGROUND;
632 for (
int y = 0; y < h; y++) {
633 const int row_offset = y * w;
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;
639 const int luminance = (77 * r + 150 * g + 29 * b + 128) >> 8;
642 uint8_t safe_luminance = clamp_rgb(luminance);
643 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
647 const char ascii_char = char_info->utf8_bytes[0];
653 if (y != h - 1 && rle_ctx.length < rle_ctx.capacity - 1) {
654 rle_ctx.buffer[rle_ctx.length++] =
'\n';
663 const char *line_start = lines;
665 while (*line_start) {
666 const char *line_end = line_start;
667 while (*line_end && *line_end !=
'\n')
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;
676 while (p < line_end && !(*p >=
'@' && *p <=
'~'))
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);
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;
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;
697 debug_pos += (size_t)
safe_snprintf(debug_buf + debug_pos,
sizeof(debug_buf) - debug_pos,
"<%02X>", c);
700 log_error(
" Incomplete seq: %s", debug_buf);
711 line_start = *line_end ? line_end + 1 : line_end;
720 _Thread_local
static char color_code[20];
721 SAFE_SNPRINTF(color_code,
sizeof(color_code),
"\033[38;2;%d;%d;%dm", r, g, b);
726 _Thread_local
static char color_code[20];
727 SAFE_SNPRINTF(color_code,
sizeof(color_code),
"\033[48;2;%d;%d;%dm", r, g, b);
732 if (!fg_code || !bg_code) {
733 SET_ERRNO(ERROR_INVALID_PARAM,
"rgb_to_ansi_8bit: fg_code or bg_code is NULL");
738 if (r == g && g == b) {
742 }
else if (r > 248) {
745 *fg_code = 232 + (r - 8) / 10;
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;
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);
767 if (caps->render_mode == RENDER_MODE_HALF_BLOCK) {
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);
773 SET_ERRNO(ERROR_INVALID_STATE,
"Half-block mode requires NEON support (ARM architecture)");
779 bool use_background_mode = (caps->render_mode == RENDER_MODE_BACKGROUND);
784 switch (caps->color_level) {
785 case TERM_COLOR_TRUECOLOR:
788 START_TIMER(
"print_color_simd_truecolor");
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)");
793 START_TIMER(
"print_color");
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)");
801 log_info(
"Using 256-COLOR rendering path");
803 START_TIMER(
"print_color_simd_256");
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)");
809 START_TIMER(
"print_256color");
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)");
818 START_TIMER(
"print_16color_dithered");
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)");
824 case TERM_COLOR_NONE:
828 START_TIMER(
"print_simd");
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)");
833 START_TIMER(
"print");
835 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT,
"print",
"PRINT: Complete (%.2f ms)");
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);
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);
871 if (h <= 0 || w <= 0) {
872 SET_ERRNO(ERROR_INVALID_STATE,
"image_print_16color: invalid dimensions h=%d, w=%d", h, w);
883 if (checked_size_mul((
size_t)h, (
size_t)w, &h_times_w) != ASCIICHAT_OK) {
887 size_t h_times_w_times_12;
888 if (checked_size_mul(h_times_w, 12u, &h_times_w_times_12) != ASCIICHAT_OK) {
893 if (checked_size_add(h_times_w_times_12, (
size_t)h, &
buffer_size) != ASCIICHAT_OK) {
901 const char *reset_code =
"\033[0m";
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];
912 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
917 SET_ERRNO(ERROR_INVALID_STATE,
"Failed to get UTF-8 cache");
922 uint8_t safe_luminance = clamp_rgb(luminance);
923 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2);
924 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx];
927 char ascii_char = palette[char_idx];
928 const utf8_char_t *char_info = &utf8_cache->cache[(
unsigned char)ascii_char];
932 for (
int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
933 *ptr++ = char_info->utf8_bytes[byte_idx];
937 size_t palette_len = strlen(palette);
938 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
939 *ptr++ = palette[palette_index];
944 SAFE_STRNCPY(ptr, reset_code, 5);
945 ptr += strlen(reset_code);
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);
965 if (h <= 0 || w <= 0) {
966 SET_ERRNO(ERROR_INVALID_STATE,
"image_print_16color_dithered: invalid dimensions h=%d, w=%d", h, w);
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 *);
982 if (checked_size_mul((
size_t)h, (
size_t)w, &h_times_w2) != ASCIICHAT_OK) {
983 SAFE_FREE(error_buffer);
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);
994 if (checked_size_add(h_times_w2_times_12, (
size_t)h, &
buffer_size) != ASCIICHAT_OK) {
995 SAFE_FREE(error_buffer);
1003 const char *reset_code =
"\033[0m";
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];
1014 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
1019 SET_ERRNO(ERROR_INVALID_STATE,
"Failed to get UTF-8 cache");
1024 uint8_t safe_luminance = clamp_rgb(luminance);
1025 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2);
1026 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx];
1029 char ascii_char = palette[char_idx];
1030 const utf8_char_t *char_info = &utf8_cache->cache[(
unsigned char)ascii_char];
1034 for (
int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
1035 *ptr++ = char_info->utf8_bytes[byte_idx];
1039 size_t palette_len = strlen(palette);
1040 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
1041 *ptr++ = palette[palette_index];
1046 SAFE_STRNCPY(ptr, reset_code, 5);
1047 ptr += strlen(reset_code);
1054 SAFE_FREE(error_buffer);
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);
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);
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 *);
1083 (size_t)h * (
size_t)w * (use_background ? 24 : 12) + (
size_t)h;
1088 const char *reset_code =
"\033[0m";
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];
1097 if (use_background) {
1099 uint8_t bg_r, bg_g, bg_b;
1103 int bg_luminance = (bg_r * 77 + bg_g * 150 + bg_b * 29) / 256;
1104 uint8_t fg_color = (bg_luminance < 127) ? 15 : 0;
1114 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
1119 SET_ERRNO(ERROR_INVALID_STATE,
"Failed to get UTF-8 cache");
1124 uint8_t safe_luminance = clamp_rgb(luminance);
1125 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2);
1126 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx];
1129 char ascii_char = palette[char_idx];
1130 const utf8_char_t *char_info = &utf8_cache->cache[(
unsigned char)ascii_char];
1134 for (
int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
1135 *ptr++ = char_info->utf8_bytes[byte_idx];
1139 size_t palette_len = strlen(palette);
1140 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
1141 *ptr++ = palette[palette_index];
1146 SAFE_STRNCPY(ptr, reset_code, 5);
1147 ptr += strlen(reset_code);
1154 SAFE_FREE(error_buffer);
char * append_16color_fg(char *dst, uint8_t color_index)
char * append_16color_bg(char *dst, uint8_t color_index)
uint8_t rgb_to_16color(uint8_t r, uint8_t g, uint8_t b)
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)
void ansi_rle_init(ansi_rle_context_t *ctx, char *buffer, size_t capacity, ansi_color_mode_t mode)
void ansi_rle_finish(ansi_rle_context_t *ctx)
void get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b)
void ansi_rle_add_pixel(ansi_rle_context_t *ctx, uint8_t r, uint8_t g, uint8_t b, char ascii_char)
void ansi_fast_init_16color(void)
char * image_print_simd(image_t *image, const char *ascii_chars)
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)
int buffer_size
Size of circular buffer.
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.
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)
image_t * image_new(size_t width, size_t height)
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)
asciichat_error_t image_validate_dimensions(size_t width, size_t height)