ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
ansi_fast.c
Go to the documentation of this file.
1
7#include "common.h"
9#include "ansi_fast.h"
10#include "util/math.h"
11#include <string.h>
12#include <time.h>
13#ifndef _WIN32
14#include <unistd.h>
15#endif
16#include <limits.h>
17
18// 256-color lookup table (optional)
19static char color256_strings[256][16]; // Pre-built SGR strings like "\033[38;5;123m"
20static bool color256_initialized = false;
21
22// Fast foreground color: \033[38;2;R;G;Bm
23// Maximum output: 19 bytes (\033[38;2;255;255;255m)
24char *append_truecolor_fg(char *dst, uint8_t r, uint8_t g, uint8_t b) {
25 // Static prefix - 7 bytes
26 SAFE_MEMCPY(dst, 19, "\033[38;2;", 7);
27 dst += 7;
28
29 // Red component + semicolon
31 dst += g_dec3_cache.dec3_table[r].len;
32 *dst++ = ';';
33
34 // Green component + semicolon
36 dst += g_dec3_cache.dec3_table[g].len;
37 *dst++ = ';';
38
39 // Blue component + suffix
41 dst += g_dec3_cache.dec3_table[b].len;
42 *dst++ = 'm';
43
44 return dst;
45}
46
47// Fast background color: \033[48;2;R;G;Bm
48// Maximum output: 19 bytes (\033[48;2;255;255;255m)
49char *append_truecolor_bg(char *dst, uint8_t r, uint8_t g, uint8_t b) {
50 SAFE_MEMCPY(dst, 19, "\033[48;2;", 7);
51 dst += 7;
52
54 dst += g_dec3_cache.dec3_table[r].len;
55 *dst++ = ';';
56
58 dst += g_dec3_cache.dec3_table[g].len;
59 *dst++ = ';';
60
62 dst += g_dec3_cache.dec3_table[b].len;
63 *dst++ = 'm';
64
65 return dst;
66}
67
68// Combined foreground + background: \033[38;2;R;G;B;48;2;r;g;bm
69// Maximum output: 38 bytes (\033[38;2;255;255;255;48;2;255;255;255m)
70char *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,
71 uint8_t bg_b) {
72 SAFE_MEMCPY(dst, 38, "\033[38;2;", 7);
73 dst += 7;
74
75 // Foreground RGB (remaining: 31 bytes max)
77 dst += g_dec3_cache.dec3_table[fg_r].len;
78 *dst++ = ';';
79
80 // Remaining: 27 bytes max
82 dst += g_dec3_cache.dec3_table[fg_g].len;
83 *dst++ = ';';
84
85 // Remaining: 23 bytes max
87 dst += g_dec3_cache.dec3_table[fg_b].len;
88
89 // Background RGB (remaining: 20 bytes max)
90 SAFE_MEMCPY(dst, 20, ";48;2;", 6);
91 dst += 6;
92
93 // Remaining: 14 bytes max
95 dst += g_dec3_cache.dec3_table[bg_r].len;
96 *dst++ = ';';
97
98 // Remaining: 10 bytes max
100 dst += g_dec3_cache.dec3_table[bg_g].len;
101 *dst++ = ';';
102
103 // Remaining: 6 bytes max
105 dst += g_dec3_cache.dec3_table[bg_b].len;
106 *dst++ = 'm';
107
108 return dst;
109}
110
111// Initialize run-length encoding context
112void ansi_rle_init(ansi_rle_context_t *ctx, char *buffer, size_t capacity, ansi_color_mode_t mode) {
113 ctx->buffer = buffer;
114 ctx->capacity = capacity;
115 ctx->length = 0;
116 ctx->mode = mode;
117 ctx->first_pixel = true;
118 // Initialize with impossible color values to force first SGR
119 ctx->last_r = 0xFF;
120 ctx->last_g = 0xFF;
121 ctx->last_b = 0xFF;
122}
123
124// Add pixel with run-length encoding - only emit SGR when color changes
125void ansi_rle_add_pixel(ansi_rle_context_t *ctx, uint8_t r, uint8_t g, uint8_t b, char ascii_char) {
126 // Check if we need to emit a new SGR sequence
127 bool color_changed = ctx->first_pixel || (r != ctx->last_r) || (g != ctx->last_g) || (b != ctx->last_b);
128
129 if (color_changed && (ctx->length + 40 < ctx->capacity)) { // Reserve 40 bytes for SGR (FG+BG max is 38)
130 char *pos = ctx->buffer + ctx->length;
131
132 switch (ctx->mode) {
134 pos = append_truecolor_fg(pos, r, g, b);
135 break;
137 pos = append_truecolor_bg(pos, r, g, b);
138 break;
140 // For FG+BG mode, use a default background (black) or implement dual-color logic
141 pos = append_truecolor_fg_bg(pos, r, g, b, 0, 0, 0);
142 break;
143 }
144
145 ctx->length = (size_t)(pos - ctx->buffer);
146 ctx->last_r = r;
147 ctx->last_g = g;
148 ctx->last_b = b;
149 ctx->first_pixel = false;
150 }
151
152 // Add the ASCII character
153 if (ctx->length < ctx->capacity - 1) {
154 ctx->buffer[ctx->length++] = ascii_char;
155 }
156}
157
158// Finish RLE sequence with reset and null terminator
160 // Add reset sequence
161 if (ctx->length + 5 < ctx->capacity) {
162 SAFE_MEMCPY(ctx->buffer + ctx->length, 4, "\033[0m", 4);
163 ctx->length += 4;
164 }
165
166 // Null terminate
167 if (ctx->length < ctx->capacity) {
168 ctx->buffer[ctx->length] = '\0';
169 }
170}
171
172// Initialize the decimal lookup table (call once at startup)
173void ansi_fast_init(void) {
174 // Initialize the dec3 cache used by truecolor functions
176}
177
178// 256-color mode initialization (optional high-speed mode)
180 if (color256_initialized)
181 return;
182
183 for (int i = 0; i < 256; i++) {
184 SAFE_SNPRINTF(color256_strings[i], sizeof(color256_strings[i]), "\033[38;5;%dm", i);
185 }
186
187 color256_initialized = true;
188}
189
190// Fast 256-color foreground
191char *append_256color_fg(char *dst, uint8_t color_index) {
192 const char *color_str = color256_strings[color_index];
193 size_t len = strlen(color_str);
194 SAFE_MEMCPY(dst, len, color_str, len);
195 return dst + len;
196}
197
198// Convert RGB to closest 256-color palette index
200 // Map to 6x6x6 color cube (216 colors) + grayscale ramp
201
202 // Check if it's close to grayscale
203 int avg = (r + g + b) / 3;
204 int gray_diff = abs(r - avg) + abs(g - avg) + abs(b - avg);
205
206 if (gray_diff < 30) {
207 // Use grayscale ramp (colors 232-255)
208 int gray_level = (avg * 23) / 255;
209 return (uint8_t)(232 + gray_level);
210 }
211
212 // Use 6x6x6 color cube (colors 16-231)
213 int r6 = (r * 5) / 255;
214 int g6 = (g * 5) / 255;
215 int b6 = (b * 5) / 255;
216
217 return (uint8_t)(16 + (r6 * 36) + (g6 * 6) + b6);
218}
219
220// 16-color mode support
221static char color16_fg_strings[16][16];
222static char color16_bg_strings[16][16];
223static bool color16_initialized = false;
224
226 if (color16_initialized)
227 return;
228
229 // Standard ANSI color codes
230 const char *fg_codes[] = {"30", "31", "32", "33", "34", "35", "36", "37", // Normal colors (30-37)
231 "90", "91", "92", "93", "94", "95", "96", "97"}; // Bright colors (90-97)
232 const char *bg_codes[] = {"40", "41", "42", "43", "44", "45", "46", "47", // Normal colors (40-47)
233 "100", "101", "102", "103", "104", "105", "106", "107"}; // Bright colors (100-107)
234
235 for (int i = 0; i < 16; i++) {
236 SAFE_SNPRINTF(color16_fg_strings[i], sizeof(color16_fg_strings[i]), "\033[%sm", fg_codes[i]);
237 SAFE_SNPRINTF(color16_bg_strings[i], sizeof(color16_bg_strings[i]), "\033[%sm", bg_codes[i]);
238 }
239
240 color16_initialized = true;
241}
242
243char *append_16color_fg(char *dst, uint8_t color_index) {
244 if (!color16_initialized) {
246 }
247
248 if (color_index >= 16) {
249 color_index = 7; // Default to white
250 }
251
252 const char *color_str = color16_fg_strings[color_index];
253 while (*color_str) {
254 *dst++ = *color_str++;
255 }
256
257 return dst;
258}
259
260char *append_16color_bg(char *dst, uint8_t color_index) {
261 if (!color16_initialized) {
263 }
264
265 if (color_index >= 16) {
266 color_index = 0; // Default to black background
267 }
268
269 const char *color_str = color16_bg_strings[color_index];
270 while (*color_str) {
271 *dst++ = *color_str++;
272 }
273
274 return dst;
275}
276
278 // Convert RGB to the closest 16-color ANSI color
279 // This uses a simple distance-based approach
280
281 // Define the 16 ANSI colors in RGB
282 static const uint8_t ansi_colors[16][3] = {
283 {0, 0, 0}, // 0: Black
284 {128, 0, 0}, // 1: Dark Red
285 {0, 128, 0}, // 2: Dark Green
286 {128, 128, 0}, // 3: Dark Yellow (Brown)
287 {0, 0, 128}, // 4: Dark Blue
288 {128, 0, 128}, // 5: Dark Magenta
289 {0, 128, 128}, // 6: Dark Cyan
290 {192, 192, 192}, // 7: Light Gray
291 {128, 128, 128}, // 8: Dark Gray
292 {255, 0, 0}, // 9: Bright Red
293 {0, 255, 0}, // 10: Bright Green
294 {255, 255, 0}, // 11: Bright Yellow
295 {0, 0, 255}, // 12: Bright Blue
296 {255, 0, 255}, // 13: Bright Magenta
297 {0, 255, 255}, // 14: Bright Cyan
298 {255, 255, 255} // 15: White
299 };
300
301 uint8_t best_match = 0;
302 int min_distance = INT_MAX;
303
304 for (int i = 0; i < 16; i++) {
305 int dr = (int)r - (int)ansi_colors[i][0];
306 int dg = (int)g - (int)ansi_colors[i][1];
307 int db = (int)b - (int)ansi_colors[i][2];
308 int distance = dr * dr + dg * dg + db * db;
309
310 if (distance < min_distance) {
311 min_distance = distance;
312 best_match = i;
313 }
314 }
315
316 return best_match;
317}
318
319// Get the actual RGB values for a 16-color ANSI index
320void get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b) {
321 // Same color table as rgb_to_16color()
322 static const uint8_t ansi_colors[16][3] = {
323 {0, 0, 0}, // 0: Black
324 {128, 0, 0}, // 1: Dark Red
325 {0, 128, 0}, // 2: Dark Green
326 {128, 128, 0}, // 3: Dark Yellow (Brown)
327 {0, 0, 128}, // 4: Dark Blue
328 {128, 0, 128}, // 5: Dark Magenta
329 {0, 128, 128}, // 6: Dark Cyan
330 {192, 192, 192}, // 7: Light Gray
331 {128, 128, 128}, // 8: Dark Gray
332 {255, 0, 0}, // 9: Bright Red
333 {0, 255, 0}, // 10: Bright Green
334 {255, 255, 0}, // 11: Bright Yellow
335 {0, 0, 255}, // 12: Bright Blue
336 {255, 0, 255}, // 13: Bright Magenta
337 {0, 255, 255}, // 14: Bright Cyan
338 {255, 255, 255} // 15: White
339 };
340
341 if (color_index >= 16) {
342 color_index = 7; // Default to light gray
343 }
344
345 *r = ansi_colors[color_index][0];
346 *g = ansi_colors[color_index][1];
347 *b = ansi_colors[color_index][2];
348}
349
350// Floyd-Steinberg dithering for 16-color terminals
351uint8_t rgb_to_16color_dithered(int r, int g, int b, int x, int y, int width, int height, rgb_error_t *error_buffer) {
352 // Add accumulated error from previous pixels
353 if (error_buffer) {
354 // Use size_t for index calculation to prevent integer overflow on large images
355 size_t error_idx = (size_t)y * (size_t)width + (size_t)x;
356 r += error_buffer[error_idx].r;
357 g += error_buffer[error_idx].g;
358 b += error_buffer[error_idx].b;
359
360 // Reset error for this pixel
361 error_buffer[error_idx].r = 0;
362 error_buffer[error_idx].g = 0;
363 error_buffer[error_idx].b = 0;
364 }
365
366 // Clamp values to [0, 255]
367 uint8_t r_clamped = clamp_rgb((int)r);
368 uint8_t g_clamped = clamp_rgb((int)g);
369 uint8_t b_clamped = clamp_rgb((int)b);
370
371 // Find the closest 16-color match
372 uint8_t closest_color = rgb_to_16color(r_clamped, g_clamped, b_clamped);
373
374 // Calculate quantization error if dithering is enabled
375 if (error_buffer) {
376 uint8_t actual_r, actual_g, actual_b;
377 get_16color_rgb(closest_color, &actual_r, &actual_g, &actual_b);
378
379 int error_r = r - (int)actual_r;
380 int error_g = g - (int)actual_g;
381 int error_b = b - (int)actual_b;
382
383 // Distribute error using Floyd-Steinberg weights:
384 // * 7/16
385 // 3/16 5/16 1/16
386
387 // Error to right pixel (x+1, y)
388 // Use size_t for all index calculations to prevent integer overflow
389 if (x + 1 < width) {
390 size_t right_idx = (size_t)y * (size_t)width + (size_t)(x + 1);
391 error_buffer[right_idx].r += (error_r * 7) / 16;
392 error_buffer[right_idx].g += (error_g * 7) / 16;
393 error_buffer[right_idx].b += (error_b * 7) / 16;
394 }
395
396 // Error to pixels on next row (y+1)
397 if (y + 1 < height) {
398 // Bottom-left pixel (x-1, y+1)
399 if (x - 1 >= 0) {
400 size_t bl_idx = (size_t)(y + 1) * (size_t)width + (size_t)(x - 1);
401 error_buffer[bl_idx].r += (error_r * 3) / 16;
402 error_buffer[bl_idx].g += (error_g * 3) / 16;
403 error_buffer[bl_idx].b += (error_b * 3) / 16;
404 }
405
406 // Bottom pixel (x, y+1)
407 size_t bottom_idx = (size_t)(y + 1) * (size_t)width + (size_t)x;
408 error_buffer[bottom_idx].r += (error_r * 5) / 16;
409 error_buffer[bottom_idx].g += (error_g * 5) / 16;
410 error_buffer[bottom_idx].b += (error_b * 5) / 16;
411
412 // Bottom-right pixel (x+1, y+1)
413 if (x + 1 < width) {
414 size_t br_idx = (size_t)(y + 1) * (size_t)width + (size_t)(x + 1);
415 error_buffer[br_idx].r += (error_r * 1) / 16;
416 error_buffer[br_idx].g += (error_g * 1) / 16;
417 error_buffer[br_idx].b += (error_b * 1) / 16;
418 }
419 }
420 }
421
422 return closest_color;
423}
424
425// Terminal capability-aware color function
427 switch (mode) {
429 return append_truecolor_fg(dst, r, g, b);
430
432 uint8_t color_index = rgb_to_256color(r, g, b);
433 return append_256color_fg(dst, color_index);
434 }
435
436 case COLOR_MODE_16_COLOR: {
437 uint8_t color_index = rgb_to_16color(r, g, b);
438 return append_16color_fg(dst, color_index);
439 }
440
441 case COLOR_MODE_NONE:
442 case COLOR_MODE_AUTO:
443 default:
444 // No color output for monochrome mode or auto mode (fallback)
445 return dst;
446 }
447}
Fast ANSI escape sequence generation.
SIMD-optimized ASCII conversion interface.
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
unsigned char uint8_t
Definition common.h:56
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:388
#define COLOR_MODE_16_COLOR
16-color mode (full name)
Definition options.h:159
#define COLOR_MODE_256_COLOR
256-color mode (full name)
Definition options.h:161
#define COLOR_MODE_TRUECOLOR
24-bit truecolor mode
Definition options.h:162
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
Definition options.h:156
#define COLOR_MODE_NONE
Monochrome mode.
Definition options.h:157
terminal_color_mode_t
Terminal color support levels.
Definition terminal.h:424
global_dec3_cache_t g_dec3_cache
Global decimal cache instance.
Definition ascii_simd.c:23
dec3_t dec3_table[256]
Definition ascii_simd.h:98
char * append_16color_fg(char *dst, uint8_t color_index)
Append 16-color foreground ANSI sequence.
Definition ansi_fast.c:243
char * append_16color_bg(char *dst, uint8_t color_index)
Append 16-color background ANSI sequence.
Definition ansi_fast.c:260
char * append_truecolor_fg(char *dst, uint8_t r, uint8_t g, uint8_t b)
Append truecolor foreground ANSI sequence.
Definition ansi_fast.c:24
void ansi_fast_init_256color(void)
Initialize 256-color mode lookup tables.
Definition ansi_fast.c:179
uint8_t len
Definition ascii_simd.h:88
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
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
ansi_color_mode_t
Color mode for ANSI generation.
Definition ansi_fast.h:40
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
void ansi_fast_init(void)
Initialize the decimal lookup table.
Definition ansi_fast.c:173
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)
Append truecolor foreground and background ANSI sequence.
Definition ansi_fast.c:70
void ansi_rle_finish(ansi_rle_context_t *ctx)
Finish RLE sequence.
Definition ansi_fast.c:159
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
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 * append_256color_fg(char *dst, uint8_t color_index)
Append 256-color foreground ANSI sequence.
Definition ansi_fast.c:191
char s[3]
Definition ascii_simd.h:89
char * append_color_fg_for_mode(char *dst, uint8_t r, uint8_t g, uint8_t b, terminal_color_mode_t mode)
Append color foreground sequence for specified mode.
Definition ansi_fast.c:426
void ascii_simd_init(void)
Initialize SIMD subsystem.
Definition ascii_simd.c:90
uint8_t rgb_to_256color(uint8_t r, uint8_t g, uint8_t b)
Convert RGB to 256-color palette index.
Definition ansi_fast.c:199
void ansi_fast_init_16color(void)
Initialize 16-color mode lookup tables.
Definition ansi_fast.c:225
char * append_truecolor_bg(char *dst, uint8_t r, uint8_t g, uint8_t b)
Append truecolor background ANSI sequence.
Definition ansi_fast.c:49
@ ANSI_MODE_FOREGROUND_BACKGROUND
Definition ansi_fast.h:43
@ ANSI_MODE_FOREGROUND
Definition ansi_fast.h:41
@ ANSI_MODE_BACKGROUND
Definition ansi_fast.h:42
Application limits and constraints.
🔢 Mathematical Utility Functions
Run-length encoded color output context.
Definition ansi_fast.h:115
ansi_color_mode_t mode
Definition ansi_fast.h:120
RGB error structure for dithering.
Definition ansi_fast.h:234
⏱️ High-precision timing utilities using sokol_time.h and uthash