7#include <ascii-chat/common.h>
8#include <ascii-chat/video/simd/ascii_simd.h>
9#include <ascii-chat/video/ansi_fast.h>
10#include <ascii-chat/util/math.h>
11#include <ascii-chat/platform/init.h>
20static char color256_strings[256][16];
21static bool color256_initialized =
false;
22static static_mutex_t g_color256_mutex = STATIC_MUTEX_INIT;
33 SAFE_MEMCPY(dst, 19,
"\033[38;2;", 7);
62 SAFE_MEMCPY(dst, 19,
"\033[48;2;", 7);
89 SAFE_MEMCPY(dst, 38,
"\033[38;2;", 7);
107 SAFE_MEMCPY(dst, 20,
";48;2;", 6);
129void ansi_rle_init(ansi_rle_context_t *ctx,
char *buffer,
size_t capacity, ansi_color_mode_t mode) {
130 ctx->buffer = buffer;
131 ctx->capacity = capacity;
134 ctx->first_pixel =
true;
144 bool color_changed = ctx->first_pixel || (r != ctx->last_r) || (g != ctx->last_g) || (b != ctx->last_b);
147 if (ctx->length + 40 >= ctx->capacity) {
149 log_warn_every(US_PER_SEC_INT,
"RLE_CAPACITY: Cannot add SGR at length=%zu, capacity=%zu (need 40 bytes)",
150 ctx->length, ctx->capacity);
154 if (color_changed && (ctx->length + 40 < ctx->capacity)) {
155 char *pos = ctx->buffer + ctx->length;
158 case ANSI_MODE_FOREGROUND:
161 case ANSI_MODE_BACKGROUND:
164 case ANSI_MODE_FOREGROUND_BACKGROUND:
170 ctx->length = (size_t)(pos - ctx->buffer);
174 ctx->first_pixel =
false;
178 if (ctx->length < ctx->capacity - 1) {
179 ctx->buffer[ctx->length++] = ascii_char;
186 if (ctx->length + 5 <= ctx->capacity) {
187 SAFE_MEMCPY(ctx->buffer + ctx->length, 4,
"\033[0m", 4);
192 if (ctx->length < ctx->capacity) {
193 ctx->buffer[ctx->length] =
'\0';
206 static_mutex_lock(&g_color256_mutex);
208 if (color256_initialized) {
209 static_mutex_unlock(&g_color256_mutex);
213 for (
int i = 0; i < 256; i++) {
214 SAFE_SNPRINTF(color256_strings[i],
sizeof(color256_strings[i]),
"\033[38;5;%dm", i);
217 color256_initialized =
true;
218 static_mutex_unlock(&g_color256_mutex);
223 const char *color_str = color256_strings[color_index];
224 size_t len = strlen(color_str);
225 SAFE_MEMCPY(dst, len, color_str, len);
234 int avg = (r + g + b) / 3;
235 int gray_diff = abs(r - avg) + abs(g - avg) + abs(b - avg);
237 if (gray_diff < 30) {
239 int gray_level = (avg * 23) / 255;
240 return (uint8_t)(232 + gray_level);
244 int r6 = (r * 5) / 255;
245 int g6 = (g * 5) / 255;
246 int b6 = (b * 5) / 255;
248 return (uint8_t)(16 + (r6 * 36) + (g6 * 6) + b6);
252static char color16_fg_strings[16][16];
253static char color16_bg_strings[16][16];
254static bool color16_initialized =
false;
255static static_mutex_t g_color16_mutex = STATIC_MUTEX_INIT;
258 static_mutex_lock(&g_color16_mutex);
260 if (color16_initialized) {
261 static_mutex_unlock(&g_color16_mutex);
266 const char *fg_codes[] = {
"30",
"31",
"32",
"33",
"34",
"35",
"36",
"37",
267 "90",
"91",
"92",
"93",
"94",
"95",
"96",
"97"};
268 const char *bg_codes[] = {
"40",
"41",
"42",
"43",
"44",
"45",
"46",
"47",
269 "100",
"101",
"102",
"103",
"104",
"105",
"106",
"107"};
271 for (
int i = 0; i < 16; i++) {
272 SAFE_SNPRINTF(color16_fg_strings[i],
sizeof(color16_fg_strings[i]),
"\033[%sm", fg_codes[i]);
273 SAFE_SNPRINTF(color16_bg_strings[i],
sizeof(color16_bg_strings[i]),
"\033[%sm", bg_codes[i]);
276 color16_initialized =
true;
277 static_mutex_unlock(&g_color16_mutex);
281 if (!color16_initialized) {
285 if (color_index >= 16) {
289 const char *color_str = color16_fg_strings[color_index];
291 *dst++ = *color_str++;
298 if (!color16_initialized) {
302 if (color_index >= 16) {
306 const char *color_str = color16_bg_strings[color_index];
308 *dst++ = *color_str++;
319 static const uint8_t ansi_colors[16][3] = {
338 uint8_t best_match = 0;
339 int min_distance = INT_MAX;
341 for (
int i = 0; i < 16; i++) {
342 int dr = (int)r - (
int)ansi_colors[i][0];
343 int dg = (int)g - (
int)ansi_colors[i][1];
344 int db = (int)b - (
int)ansi_colors[i][2];
345 int distance = dr * dr + dg * dg + db * db;
347 if (distance < min_distance) {
348 min_distance = distance;
359 static const uint8_t ansi_colors[16][3] = {
378 if (color_index >= 16) {
382 *r = ansi_colors[color_index][0];
383 *g = ansi_colors[color_index][1];
384 *b = ansi_colors[color_index][2];
392 size_t error_idx = (size_t)y * (
size_t)width + (size_t)x;
393 r += error_buffer[error_idx].r;
394 g += error_buffer[error_idx].g;
395 b += error_buffer[error_idx].b;
398 error_buffer[error_idx].r = 0;
399 error_buffer[error_idx].g = 0;
400 error_buffer[error_idx].b = 0;
404 uint8_t r_clamped = clamp_rgb((
int)r);
405 uint8_t g_clamped = clamp_rgb((
int)g);
406 uint8_t b_clamped = clamp_rgb((
int)b);
409 uint8_t closest_color =
rgb_to_16color(r_clamped, g_clamped, b_clamped);
413 uint8_t actual_r, actual_g, actual_b;
416 int error_r = r - (int)actual_r;
417 int error_g = g - (int)actual_g;
418 int error_b = b - (int)actual_b;
427 size_t right_idx = (size_t)y * (
size_t)width + (size_t)(x + 1);
428 error_buffer[right_idx].r += (error_r * 7) / 16;
429 error_buffer[right_idx].g += (error_g * 7) / 16;
430 error_buffer[right_idx].b += (error_b * 7) / 16;
434 if (y + 1 < height) {
437 size_t bl_idx = (size_t)(y + 1) * (size_t)width + (
size_t)(x - 1);
438 error_buffer[bl_idx].r += (error_r * 3) / 16;
439 error_buffer[bl_idx].g += (error_g * 3) / 16;
440 error_buffer[bl_idx].b += (error_b * 3) / 16;
444 size_t bottom_idx = (size_t)(y + 1) * (size_t)width + (
size_t)x;
445 error_buffer[bottom_idx].r += (error_r * 5) / 16;
446 error_buffer[bottom_idx].g += (error_g * 5) / 16;
447 error_buffer[bottom_idx].b += (error_b * 5) / 16;
451 size_t br_idx = (size_t)(y + 1) * (size_t)width + (
size_t)(x + 1);
452 error_buffer[br_idx].r += (error_r * 1) / 16;
453 error_buffer[br_idx].g += (error_g * 1) / 16;
454 error_buffer[br_idx].b += (error_b * 1) / 16;
459 return closest_color;
465 case COLOR_MODE_TRUECOLOR:
468 case COLOR_MODE_256_COLOR: {
473 case COLOR_MODE_16_COLOR: {
478 case COLOR_MODE_NONE:
479 case COLOR_MODE_AUTO:
char * append_16color_fg(char *dst, uint8_t color_index)
char * append_16color_bg(char *dst, uint8_t color_index)
char * append_truecolor_fg(char *dst, uint8_t r, uint8_t g, uint8_t b)
void ansi_fast_init_256color(void)
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_fast_init(void)
char * append_truecolor_fg_bg(char *dst, uint8_t fg_r, uint8_t fg_g, uint8_t fg_b, uint8_t bg_r, uint8_t bg_g, uint8_t bg_b)
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)
char * append_256color_fg(char *dst, uint8_t color_index)
char * append_color_fg_for_mode(char *dst, uint8_t r, uint8_t g, uint8_t b, terminal_color_mode_t mode)
uint8_t rgb_to_256color(uint8_t r, uint8_t g, uint8_t b)
void ansi_fast_init_16color(void)
char * append_truecolor_bg(char *dst, uint8_t r, uint8_t g, uint8_t b)
global_dec3_cache_t g_dec3_cache
void ascii_simd_init(void)