ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
backends.h File Reference

Go to the source code of this file.

Functions

char * digital_rain_render_at (digital_rain_t *rain, const char *frame, float seconds)
char * rainbow_render_ansi_at (const char *frame, float seconds)
asciichat_error_t test_pattern_render_at (struct test_pattern *pattern, int index, double time_ms, bool cadence)

Function Documentation

◆ digital_rain_render_at()

char * digital_rain_render_at ( digital_rain_t * rain,
const char * frame,
float seconds )

Definition at line 384 of file digital_rain.c.

384 {
385 if (!rain || !frame) {
386 log_error("NULL parameter");
387 return NULL;
388 }
389
390 if (!isfinite(seconds) || seconds < 0)
391 return NULL;
392 float elapsed = fmaxf(0.0f, seconds - rain->time);
393 rain->time = seconds;
394 float sim_time = seconds;
395 // brightness_decay retains its meaning as a blend factor at 60 Hz.
396 float decay = fminf(1.0f, fmaxf(0.0f, rain->brightness_decay));
397 float alpha = elapsed > 0 ? 1.0f - powf(1.0f - decay, elapsed * 60.0f) : 0.0f;
398
399 if (rain->rainbow_mode)
400 color_filter_calculate_rainbow(sim_time, &rain->color_r, &rain->color_g, &rain->color_b);
401
402 size_t input_len = strlen(frame);
403 if (input_len > (SIZE_MAX - 1024) / 48)
404 return NULL;
405 size_t output_capacity = input_len * 48 + 1024;
406 char *output = SAFE_MALLOC(output_capacity, char *);
407 if (!output)
408 return NULL;
409
410 const char *src = frame;
411 char *dst = output;
412 size_t remaining = output_capacity;
413 int col = 0, row = 0;
414 float foreground_luminance = 1.0f, background_luminance = 0.0f;
415 bool has_background = false;
416 bool colored = false;
417
418 while (*src && remaining > 100) {
419 if (*src == '\033') {
420 int r, g, b;
421 bool foreground;
422 const char *after = parse_ansi_color(src, &r, &g, &b, &foreground);
423 if (after) {
424 float luminance = fminf(1.0f, fmaxf(0.0f, (0.2126f * r + 0.7152f * g + 0.0722f * b) / 255.0f));
425 if (foreground)
426 foreground_luminance = luminance;
427 else {
428 background_luminance = luminance;
429 has_background = true;
430 }
431 src = after;
432 } else {
433 after = skip_ansi_sequence(src);
434 size_t count = (size_t)(after - src);
435 if ((count == 4 && memcmp(src, "\033[0m", 4) == 0) || (count == 3 && memcmp(src, "\033[m", 3) == 0)) {
436 foreground_luminance = 1.0f;
437 has_background = false;
438 }
439 memcpy(dst, src, count);
440 dst += count;
441 remaining -= count;
442 src = after;
443 }
444 continue;
445 }
446 if (*src == '\n' || *src == '\r') {
447 if (*src == '\n')
448 row++;
449 col = 0;
450 *dst++ = *src++;
451 remaining--;
452 continue;
453 }
454
455 float head;
456 float brightness = get_rain_brightness(rain, col, row, sim_time, &head);
457 // Update once per cell, independently of how many ANSI sequences precede it.
458 if (row < rain->num_rows && col < rain->num_columns) {
459 size_t idx = (size_t)row * (size_t)rain->num_columns + (size_t)col;
460 if (!rain->first_frame)
461 brightness = rain->previous_brightness[idx] + (brightness - rain->previous_brightness[idx]) * alpha;
462 rain->previous_brightness[idx] = brightness;
463 }
464 int count = generate_rain_color(dst, remaining, rain, brightness, head, foreground_luminance, true);
465 colored = true;
466 dst += count;
467 remaining -= (size_t)count;
468 if (has_background) {
469 count = generate_rain_color(dst, remaining, rain, brightness, head, background_luminance, false);
470 dst += count;
471 remaining -= (size_t)count;
472 }
473 uint32_t codepoint;
474 int bytes = utf8_decode((const uint8_t *)src, &codepoint);
475 if (bytes <= 0) {
476 bytes = 1;
477 codepoint = (uint8_t)*src;
478 }
479 if (bytes == 1 && codepoint > 32 && codepoint < 127) {
480 *dst++ = (char)mutate_glyph(codepoint, col, row, sim_time);
481 src++;
482 remaining--;
483 } else {
484 memcpy(dst, src, (size_t)bytes);
485 dst += bytes;
486 src += bytes;
487 remaining -= (size_t)bytes;
488 }
489 col++;
490 }
491 // Monochrome input has no trailing SGR reset to copy. The effect introduces
492 // colors itself, so every colored result must restore terminal attributes.
493 if (colored) {
494 const char reset[] = "\033[0m";
495 if (remaining < sizeof(reset)) {
496 SAFE_FREE(output);
497 return NULL;
498 }
499 memcpy(dst, reset, sizeof(reset));
500 } else {
501 *dst = '\0';
502 }
503 rain->first_frame = false;
504 return output;
505}
unsigned int uint32_t
Definition common.h:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned char uint8_t
Definition common.h:60
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
int utf8_decode(const uint8_t *s, uint32_t *codepoint)
Decode a UTF-8 sequence to a Unicode codepoint.
Definition utf8.c:27
void color_filter_calculate_rainbow(float time, uint8_t *r, uint8_t *g, uint8_t *b)
Calculate rainbow color from time (3.5s cycle).
uint8_t color_g
Matrix green G component.
uint8_t color_b
Matrix green B component.
float brightness_decay
Brightness smoothing factor (0-1).
float time
Current simulation time (accumulated).
uint8_t color_r
Matrix green R component.
bool first_frame
True if this is the first frame.
bool rainbow_mode
True if rainbow color cycling is enabled.
float * previous_brightness
Previous frame brightness (for smoothing).
int num_columns
Number of columns in grid.

References digital_rain::brightness_decay, digital_rain::color_b, color_filter_calculate_rainbow(), digital_rain::color_g, digital_rain::color_r, digital_rain::first_frame, log_error, digital_rain::num_columns, digital_rain::previous_brightness, digital_rain::rainbow_mode, SAFE_FREE, SAFE_MALLOC, digital_rain::time, and utf8_decode().

Referenced by animation_apply().

◆ rainbow_render_ansi_at()

char * rainbow_render_ansi_at ( const char * frame,
float seconds )

Definition at line 366 of file color_filter.c.

366 {
367 if (!ansi_string) {
368 return NULL;
369 }
370
371 // Calculate rainbow color once for this frame
372 uint8_t rainbow_r = 0, rainbow_g = 0, rainbow_b = 0;
373 color_filter_calculate_rainbow(time_seconds, &rainbow_r, &rainbow_g, &rainbow_b);
374
375 // Build the replacement ANSI code string once
377 snprintf(rainbow_code, sizeof(rainbow_code), "\x1b[38;2;%d;%d;%dm", rainbow_r, rainbow_g, rainbow_b);
378 size_t rainbow_code_len = strlen(rainbow_code);
379
380 // Check if there are any ANSI codes to replace
381 if (!strstr(ansi_string, "\x1b[38;2;")) {
382 return NULL; // No ANSI codes found, no replacement needed
383 }
384
385 size_t input_len = strlen(ansi_string);
386 size_t replacements = 0;
387 const char *scan = ansi_string;
388 while ((scan = strstr(scan, "\x1b[38;2;")) != NULL) {
389 const char *end = strchr(scan + 7, 'm');
390 if (!end)
391 break;
392 replacements++;
393 scan = end + 1;
394 }
395 if (input_len == SIZE_MAX || replacements > (SIZE_MAX - input_len - 1) / rainbow_code_len)
396 return NULL;
397 char *new_result = SAFE_MALLOC(input_len + replacements * rainbow_code_len + 1, char *);
398 if (!new_result) {
399 return NULL;
400 }
401
402 // Replace all RGB color codes with rainbow color
403 const char *src = ansi_string;
404 char *dst = new_result;
405
406 while (*src) {
407 const char *ansi_start = strstr(src, "\x1b[38;2;");
408 if (ansi_start) {
409 // Copy everything before this ANSI code
410 size_t before_len = (size_t)(ansi_start - src);
411 memcpy(dst, src, before_len);
412 dst += before_len;
413
414 // Skip the old ANSI code (find the 'm' that ends it)
415 const char *ansi_end = strchr(ansi_start + 7, 'm');
416 if (ansi_end) {
417 // Copy the rainbow replacement code
418 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary data
419 memcpy(dst, rainbow_code, rainbow_code_len);
420 dst += rainbow_code_len;
421
422 // Move source past the old code
423 src = ansi_end + 1;
424 } else {
425 // Preserve a malformed trailing sequence without duplicating the prefix.
426 size_t remaining = strlen(ansi_start);
427 memcpy(dst, ansi_start, remaining);
428 dst += remaining;
429 break;
430 }
431 } else {
432 // No more ANSI codes, copy the rest
433 size_t remaining = strlen(src);
434 memcpy(dst, src, remaining);
435 dst += remaining;
436 break;
437 }
438 }
439 *dst = '\0';
440
441 return new_result;
442}
#define VIDEO_RGBA_COLOR_FILTER_RAINBOW_CODE_BUFFER_SIZE
Definition color_filter.c:8

References color_filter_calculate_rainbow(), SAFE_MALLOC, and VIDEO_RGBA_COLOR_FILTER_RAINBOW_CODE_BUFFER_SIZE.

Referenced by animation_apply().

◆ test_pattern_render_at()

asciichat_error_t test_pattern_render_at ( struct test_pattern * pattern,
int index,
double time_ms,
bool cadence )

Definition at line 160 of file video/anim/test_pattern.c.

160 {
161 if (!p || !p->image || index < 0 || index > 1 || !isfinite(time_ms) || time_ms < 0)
162 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid test pattern frame parameters");
163 int w = p->image->w, h = p->image->h;
164 double phase = fmod(time_ms, index ? 4000 : 10000) / (index ? 4000 : 10000);
165 rgb_pixel_t dark = {16, 24, 32}, white = {255, 255, 255};
166 rgb_pixel_t first = hue(phase * 360, 0.5), last = hue(fmod(phase + 0.5, 1) * 360, 0.5);
167 double box = fmin(fmax(32, fmin(w, h) / 4.0), fmin(w, h) / 2.0);
168 double bx = fmod(time_ms / 11, w + box) - box, by = fmod(time_ms / 17, h + box) - box;
169 double stripe = w / 12.0, offset = phase * w;
170 int first_stripe = (int)ceil(-offset / stripe);
171 double radius = fmin(fmax(12, fmin(w, h) / 8.0), fmin(w, h) / 4.0);
172 double cx = w + radius - phase * (w + radius * 2), cy = h / 2.0 + sin(phase * 12.566370614359172) * h / 4.0;
174 for (int i = 0; i < 12; i++)
175 colors[i] = hue(i * 30, 0.55);
176 uint32_t frame = (uint32_t)fmod(floor(time_ms / (1000.0 / 60)), 512);
177 uint32_t code = frame ^ (frame >> 1);
178 const int slots[8] = {6, 4, 2, 0, 1, 3, 5, 7};
179 rgb_pixel_t *pixels = p->image->pixels;
180 if (index == 1) {
181 // The background is constant down each column. Rasterize one row, then copy.
182 for (int x = 0; x < w; x++) {
183 rgb_pixel_t pixel = dark;
184 if (cadence) {
185 int slot = (int)(x / fmax(2, w / 8.0));
186 if (slot < 8)
187 pixel = (code & (1u << slots[slot])) ? white : (rgb_pixel_t){0, 0, 0};
188 } else {
189 int k = (int)floor((x - offset) / stripe) + 1;
190 for (int j = k; j <= k + 1; j++)
191 if (j >= first_stripe && j <= first_stripe + 12)
192 blend(&pixel, colors[(j % 12 + 12) % 12], overlap(x, j * stripe + offset - stripe, j * stripe + offset));
193 }
194 pixels[x] = pixel;
195 }
196 for (int y = 1; y < h; y++)
197 memcpy(pixels + (size_t)y * w, pixels, (size_t)w * sizeof(*pixels));
198 if (cadence)
199 return ASCIICHAT_OK;
200 int top = (int)fmax(0, floor(cy - radius - 1)), bottom = (int)fmin(h, ceil(cy + radius + 1));
201 int left = (int)fmax(0, floor(cx - radius - 1)), right = (int)fmin(w, ceil(cx + radius + 1));
202 for (int y = top; y < bottom; y++)
203 for (int x = left; x < right; x++) {
204 double dx0 = x + 0.5 - cx, dy0 = y + 0.5 - cy, d2 = dx0 * dx0 + dy0 * dy0;
205 if (d2 > (radius + 0.75) * (radius + 0.75))
206 continue;
207 double coverage = 1;
208 if (d2 > (radius - 0.75) * (radius - 0.75)) {
209 int inside = 0;
210 for (int sy = 0; sy < 4; sy++)
211 for (int sx = 0; sx < 4; sx++) {
212 double dx = x + (sx + 0.5) / 4 - cx, dy = y + (sy + 0.5) / 4 - cy;
213 if (dx * dx + dy * dy < radius * radius)
214 inside++;
215 }
216 coverage = inside / 16.0;
217 }
218 blend(&pixels[(size_t)y * w + x], white, coverage);
219 }
220 } else {
221 // A fixed-point ramp avoids three floating-point conversions per source
222 // pixel. 4096 intervals keep rounding within one RGB level of Canvas.
224 for (int i = 0; i <= TEST_PATTERN_GRADIENT_INTERVALS; i++) {
225 double t = i / (double)TEST_PATTERN_GRADIENT_INTERVALS;
226 gradient[i] = (rgb_pixel_t){(uint8_t)(first.r * (1 - t) + last.r * t + 0.5),
227 (uint8_t)(first.g * (1 - t) + last.g * t + 0.5),
228 (uint8_t)(first.b * (1 - t) + last.b * t + 0.5)};
229 }
230 double scale = (double)TEST_PATTERN_GRADIENT_INTERVALS * 65536 / ((double)w * w + (double)h * h);
231 uint32_t step = (uint32_t)(w * scale + 0.5);
232 for (int y = 0; y < h; y++) {
233 uint32_t position = (uint32_t)((0.5 * w + (y + 0.5) * h) * scale + 0.5);
234 for (int x = 0; x < w; x++, position += step)
235 pixels[(size_t)y * w + x] = gradient[position >> 16];
236 }
237 int top = (int)fmax(0, floor(by)), bottom = (int)fmin(h, ceil(by + box));
238 int left = (int)fmax(0, floor(bx)), right = (int)fmin(w, ceil(bx + box));
239 for (int y = top; y < bottom; y++)
240 for (int x = left; x < right; x++)
241 blend(&pixels[(size_t)y * w + x], white, 0.8 * overlap(x, bx, bx + box) * overlap(y, by, by + box));
242 }
243 render_test_label(p->image);
244 return ASCIICHAT_OK;
245}
#define SET_ERRNO(code,...)
unsigned char colors[(512 *256 *2) *3]
Definition fft.c:22
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
static bool size_t index
Definition queries.h:8
uint8_t b
Blue color component (0-255).
uint8_t g
Green color component (0-255).
uint8_t r
Red color component (0-255).
#define TEST_PATTERN_GRADIENT_INTERVALS

References ASCIICHAT_OK, rgb_pixel_t::b, colors, ERROR_INVALID_PARAM, rgb_pixel_t::g, image_t::h, test_pattern::image, index, image_t::pixels, rgb_pixel_t::r, SET_ERRNO, TEST_PATTERN_GRADIENT_INTERVALS, and image_t::w.

Referenced by animation_apply().