ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
avx2/color.c
Go to the documentation of this file.
1
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <stdint.h>
11#include <stdbool.h>
14#include <ascii-chat/common.h>
19
20#if SIMD_SUPPORT_AVX2
21#include <immintrin.h>
22
23// Simple emission functions for direct buffer writing
24static inline char *emit_set_256_color_fg_simple(char *pos, uint8_t color_idx) {
25 *pos++ = '\x1b';
26 *pos++ = '[';
27 *pos++ = '3';
28 *pos++ = '8';
29 *pos++ = ';';
30 *pos++ = '5';
31 *pos++ = ';';
32 if (color_idx >= 100) {
33 *pos++ = '0' + (color_idx / 100);
34 *pos++ = '0' + ((color_idx / 10) % 10);
35 *pos++ = '0' + (color_idx % 10);
36 } else if (color_idx >= 10) {
37 *pos++ = '0' + (color_idx / 10);
38 *pos++ = '0' + (color_idx % 10);
39 } else {
40 *pos++ = '0' + color_idx;
41 }
42 *pos++ = 'm';
43 return pos;
44}
45
46static inline char *emit_set_256_color_bg_simple(char *pos, uint8_t color_idx) {
47 *pos++ = '\x1b';
48 *pos++ = '[';
49 *pos++ = '4';
50 *pos++ = '8';
51 *pos++ = ';';
52 *pos++ = '5';
53 *pos++ = ';';
54 if (color_idx >= 100) {
55 *pos++ = '0' + (color_idx / 100);
56 *pos++ = '0' + ((color_idx / 10) % 10);
57 *pos++ = '0' + (color_idx % 10);
58 } else if (color_idx >= 10) {
59 *pos++ = '0' + (color_idx / 10);
60 *pos++ = '0' + (color_idx % 10);
61 } else {
62 *pos++ = '0' + color_idx;
63 }
64 *pos++ = 'm';
65 return pos;
66}
67
68static inline char *emit_set_truecolor_fg_simple(char *pos, uint8_t r, uint8_t g, uint8_t b) {
69 *pos++ = '\x1b';
70 *pos++ = '[';
71 *pos++ = '3';
72 *pos++ = '8';
73 *pos++ = ';';
74 *pos++ = '2';
75 *pos++ = ';';
76 if (r >= 100) {
77 *pos++ = '0' + (r / 100);
78 *pos++ = '0' + ((r / 10) % 10);
79 *pos++ = '0' + (r % 10);
80 } else if (r >= 10) {
81 *pos++ = '0' + (r / 10);
82 *pos++ = '0' + (r % 10);
83 } else {
84 *pos++ = '0' + r;
85 }
86 *pos++ = ';';
87 if (g >= 100) {
88 *pos++ = '0' + (g / 100);
89 *pos++ = '0' + ((g / 10) % 10);
90 *pos++ = '0' + (g % 10);
91 } else if (g >= 10) {
92 *pos++ = '0' + (g / 10);
93 *pos++ = '0' + (g % 10);
94 } else {
95 *pos++ = '0' + g;
96 }
97 *pos++ = ';';
98 if (b >= 100) {
99 *pos++ = '0' + (b / 100);
100 *pos++ = '0' + ((b / 10) % 10);
101 *pos++ = '0' + (b % 10);
102 } else if (b >= 10) {
103 *pos++ = '0' + (b / 10);
104 *pos++ = '0' + (b % 10);
105 } else {
106 *pos++ = '0' + b;
107 }
108 *pos++ = 'm';
109 return pos;
110}
111
112static inline char *emit_set_truecolor_bg_simple(char *pos, uint8_t r, uint8_t g, uint8_t b) {
113 *pos++ = '\x1b';
114 *pos++ = '[';
115 *pos++ = '4';
116 *pos++ = '8';
117 *pos++ = ';';
118 *pos++ = '2';
119 *pos++ = ';';
120 if (r >= 100) {
121 *pos++ = '0' + (r / 100);
122 *pos++ = '0' + ((r / 10) % 10);
123 *pos++ = '0' + (r % 10);
124 } else if (r >= 10) {
125 *pos++ = '0' + (r / 10);
126 *pos++ = '0' + (r % 10);
127 } else {
128 *pos++ = '0' + r;
129 }
130 *pos++ = ';';
131 if (g >= 100) {
132 *pos++ = '0' + (g / 100);
133 *pos++ = '0' + ((g / 10) % 10);
134 *pos++ = '0' + (g % 10);
135 } else if (g >= 10) {
136 *pos++ = '0' + (g / 10);
137 *pos++ = '0' + (g % 10);
138 } else {
139 *pos++ = '0' + g;
140 }
141 *pos++ = ';';
142 if (b >= 100) {
143 *pos++ = '0' + (b / 100);
144 *pos++ = '0' + ((b / 10) % 10);
145 *pos++ = '0' + (b % 10);
146 } else if (b >= 10) {
147 *pos++ = '0' + (b / 10);
148 *pos++ = '0' + (b % 10);
149 } else {
150 *pos++ = '0' + b;
151 }
152 *pos++ = 'm';
153 return pos;
154}
155
156char *render_ascii_color_avx2(const image_t *image, bool use_background, bool use_256color, const char *ascii_chars) {
157 if (!image || !image->pixels) {
158 return NULL;
159 }
160
161 const int width = image->w;
162 const int height = image->h;
163
164 if (width <= 0 || height <= 0) {
165 char *empty;
166 empty = SAFE_MALLOC(1, char *);
167 empty[0] = '\0';
168 return empty;
169 }
170
171 // Get cached UTF-8 character mappings
172 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(ascii_chars);
173 if (!utf8_cache) {
174 log_error("Failed to get UTF-8 palette cache for AVX2 color");
175 return NULL;
176 }
177
178 // Use malloc for output buffer (will be freed by caller)
179 size_t bytes_per_pixel = use_256color ? 10u : 25u; // Conservative estimates
180
181 // Calculate buffer size with overflow checking
182 size_t height_times_width;
183 if (checked_size_mul((size_t)height, (size_t)width, &height_times_width) != ASCIICHAT_OK) {
184 log_error("Buffer size overflow: height * width overflow");
185 return NULL;
186 }
187
188 size_t pixel_data_size;
189 if (checked_size_mul(height_times_width, bytes_per_pixel, &pixel_data_size) != ASCIICHAT_OK) {
190 log_error("Buffer size overflow: (height * width) * bytes_per_pixel overflow");
191 return NULL;
192 }
193
194 size_t height_times_16;
195 if (checked_size_mul((size_t)height, 16u, &height_times_16) != ASCIICHAT_OK) {
196 log_error("Buffer size overflow: height * 16 overflow");
197 return NULL;
198 }
199
200 size_t temp;
201 if (checked_size_add(pixel_data_size, height_times_16, &temp) != ASCIICHAT_OK) {
202 log_error("Buffer size overflow: pixel_data + height*16 overflow");
203 return NULL;
204 }
205
206 size_t output_size;
207 if (checked_size_add(temp, 1024u, &output_size) != ASCIICHAT_OK) {
208 log_error("Buffer size overflow: total output size overflow");
209 return NULL;
210 }
211
212 char *output = SAFE_MALLOC(output_size, char *);
213 if (!output) {
214 log_error("Failed to allocate output buffer for AVX2 color rendering");
215 return NULL;
216 }
217
218 char *pos = output;
219 const rgb_pixel_t *pixels_data = (const rgb_pixel_t *)image->pixels;
220
221 // Track current color state
222 int curR = -1, curG = -1, curB = -1;
223 int cur_color_idx = -1;
224
225 // Generate output row by row with single-pass processing
226
227 for (int y = 0; y < height; y++) {
228 const rgb_pixel_t *row_pixels = &pixels_data[y * width];
229 int x = 0;
230
231 // AVX2 fast path: process 32 pixels at a time
232 while (x + 31 < width) {
233
234 // Process 32 pixels with AVX2 using thread-local buffers
235 avx2_load_rgb32_optimized(&row_pixels[x], avx2_r_buffer, avx2_g_buffer, avx2_b_buffer);
236 avx2_compute_luminance_32(avx2_r_buffer, avx2_g_buffer, avx2_b_buffer, avx2_luminance_buffer);
237
238 // Process each pixel in the chunk
239 int i = 0;
240 (void)x;
241 while (i < 32) {
242 const uint8_t R = avx2_r_buffer[i];
243 const uint8_t G = avx2_g_buffer[i];
244 const uint8_t B = avx2_b_buffer[i];
245 const uint8_t luma_idx = avx2_luminance_buffer[i] >> 2;
246 // Use luma_idx directly to index cache64 (0-63), not char_index (0-char_count)
247 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
248 // For RLE comparison, we need char_idx
249 const uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx];
250
251 if (use_256color) {
252 uint8_t color_idx = rgb_to_256color(R, G, B);
253
254 // Find run length
255 int run = 1;
256 while (i + run < 32 && x + run < width) {
257 const uint8_t next_R = avx2_r_buffer[i + run];
258 const uint8_t next_G = avx2_g_buffer[i + run];
259 const uint8_t next_B = avx2_b_buffer[i + run];
260 const uint8_t next_luma_idx = avx2_luminance_buffer[i + run] >> 2;
261 const uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx];
262 if (next_char_idx != char_idx)
263 break;
264 if (rgb_to_256color(next_R, next_G, next_B) != color_idx)
265 break;
266 run++;
267 }
268
269 // Set color if changed
270 if (color_idx != cur_color_idx) {
271 if (use_background) {
272 pos = emit_set_256_color_bg_simple(pos, color_idx);
273 } else {
274 pos = emit_set_256_color_fg_simple(pos, color_idx);
275 }
276 cur_color_idx = color_idx;
277 }
278
279 // Emit character with RLE
280 memcpy(pos, char_info->utf8_bytes, char_info->byte_len);
281 pos += char_info->byte_len;
282
283 if (rep_is_profitable(run)) {
284 pos = emit_rle_count(pos, run - 1);
285 } else {
286 for (int k = 1; k < run; k++) {
287 memcpy(pos, char_info->utf8_bytes, char_info->byte_len);
288 pos += char_info->byte_len;
289 }
290 }
291 i += run;
292 } else {
293 // Truecolor mode
294 // Find run length
295 int run = 1;
296 while (i + run < 32 && x + run < width) {
297 const uint8_t next_R = avx2_r_buffer[i + run];
298 const uint8_t next_G = avx2_g_buffer[i + run];
299 const uint8_t next_B = avx2_b_buffer[i + run];
300 const uint8_t next_luma_idx = avx2_luminance_buffer[i + run] >> 2;
301 const uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx];
302 if (next_char_idx != char_idx)
303 break;
304 if (next_R != R || next_G != G || next_B != B)
305 break;
306 run++;
307 }
308
309 // Set color if changed
310 if ((int)R != curR || (int)G != curG || (int)B != curB) {
311 if (use_background) {
312 pos = emit_set_truecolor_bg_simple(pos, R, G, B);
313 } else {
314 pos = emit_set_truecolor_fg_simple(pos, R, G, B);
315 }
316 curR = R;
317 curG = G;
318 curB = B;
319 }
320
321 // Emit character with RLE
322 memcpy(pos, char_info->utf8_bytes, char_info->byte_len);
323 pos += char_info->byte_len;
324
325 if (rep_is_profitable(run)) {
326 pos = emit_rle_count(pos, run - 1);
327 } else {
328 for (int k = 1; k < run; k++) {
329 memcpy(pos, char_info->utf8_bytes, char_info->byte_len);
330 pos += char_info->byte_len;
331 }
332 }
333 i += run;
334 }
335 }
336 x += 32;
337 }
338
339 // Scalar processing for remaining pixels (< 32)
340 while (x < width) {
341 const rgb_pixel_t *p = &row_pixels[x];
342 const uint8_t R = p->r, G = p->g, B = p->b;
343 const int luminance = (LUMA_RED * R + LUMA_GREEN * G + LUMA_BLUE * B + 128) >> 8;
344 const uint8_t luma_idx = luminance >> 2;
345 // Use luma_idx directly to index cache64 (0-63), not char_index (0-char_count)
346 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
347 // For RLE comparison, we need char_idx
348 const uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx];
349
350 if (use_256color) {
351 uint8_t color_idx = rgb_to_256color(R, G, B);
352
353 // Find run length
354 int run = 1;
355 while (x + run < width) {
356 const rgb_pixel_t *next_p = &row_pixels[x + run];
357 const int next_luminance = (LUMA_RED * next_p->r + LUMA_GREEN * next_p->g + LUMA_BLUE * next_p->b + 128) >> 8;
358 const uint8_t next_luma_idx = next_luminance >> 2;
359 const uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx];
360 if (next_char_idx != char_idx)
361 break;
362 if (rgb_to_256color(next_p->r, next_p->g, next_p->b) != color_idx)
363 break;
364 run++;
365 }
366
367 // Set color if changed
368 if (color_idx != cur_color_idx) {
369 if (use_background) {
370 pos = emit_set_256_color_bg_simple(pos, color_idx);
371 } else {
372 pos = emit_set_256_color_fg_simple(pos, color_idx);
373 }
374 cur_color_idx = color_idx;
375 }
376
377 // Emit character with RLE
378 memcpy(pos, char_info->utf8_bytes, char_info->byte_len);
379 pos += char_info->byte_len;
380
381 if (rep_is_profitable(run)) {
382 pos = emit_rle_count(pos, run - 1);
383 } else {
384 for (int k = 1; k < run; k++) {
385 memcpy(pos, char_info->utf8_bytes, char_info->byte_len);
386 pos += char_info->byte_len;
387 }
388 }
389 x += run;
390 } else {
391 // Truecolor mode
392 // Find run length
393 int run = 1;
394 while (x + run < width) {
395 const rgb_pixel_t *next_p = &row_pixels[x + run];
396 const int next_luminance = (LUMA_RED * next_p->r + LUMA_GREEN * next_p->g + LUMA_BLUE * next_p->b + 128) >> 8;
397 const uint8_t next_luma_idx = next_luminance >> 2;
398 const uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx];
399 if (next_char_idx != char_idx)
400 break;
401 if (next_p->r != R || next_p->g != G || next_p->b != B)
402 break;
403 run++;
404 }
405
406 // Set color if changed
407 if ((int)R != curR || (int)G != curG || (int)B != curB) {
408 if (use_background) {
409 pos = emit_set_truecolor_bg_simple(pos, R, G, B);
410 } else {
411 pos = emit_set_truecolor_fg_simple(pos, R, G, B);
412 }
413 curR = R;
414 curG = G;
415 curB = B;
416 }
417
418 // Emit character with RLE
419 memcpy(pos, char_info->utf8_bytes, char_info->byte_len);
420 pos += char_info->byte_len;
421
422 if (rep_is_profitable(run)) {
423 pos = emit_rle_count(pos, run - 1);
424 } else {
425 for (int k = 1; k < run; k++) {
426 memcpy(pos, char_info->utf8_bytes, char_info->byte_len);
427 pos += char_info->byte_len;
428 }
429 }
430 x += run;
431 }
432 }
433
434 // Add reset sequence and newline after each row (except last)
435 *pos++ = '\x1b';
436 *pos++ = '[';
437 *pos++ = '0';
438 *pos++ = 'm';
439 if (y < height - 1) {
440 *pos++ = '\n';
441 }
442 }
443
444 *pos = '\0'; // Null terminate
445
446 return output;
447}
448
449#endif /* SIMD_SUPPORT_AVX2 */
ANSI escape sequence utilities and fast color code generation.
AVX2-optimized ASCII rendering functions.
⚙️ Common definitions, error codes, macros, and types shared throughout the application
uint8_t rgb_to_256color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:360
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned char uint8_t
Definition common.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define LUMA_BLUE
Luminance blue coefficient (0.114 * 256 = 29)
#define LUMA_GREEN
Luminance green coefficient (0.587 * 256 = 150)
uint8_t utf8_bytes[4]
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)
Get UTF-8 palette cache for a character set.
bool rep_is_profitable(uint32_t runlen)
Check if run-length encoding is profitable.
utf8_char_t cache64[64]
#define LUMA_RED
Luminance red coefficient (0.299 * 256 = 77)
✅ Safe Integer Arithmetic and Overflow Detection
ClangTool/LibTooling compatibility shim for stdbool.h.
Image structure.
int w
Image width in pixels (must be > 0)
int h
Image height in pixels (must be > 0)
rgb_pixel_t * pixels
Pixel data array (width * height RGB pixels, row-major order)
RGB pixel structure.
uint8_t b
Blue color component (0-255)
uint8_t g
Green color component (0-255)
uint8_t r
Red color component (0-255)
Shared AVX2 helper functions.
SIMD-optimized ASCII conversion interface.