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

Go to the source code of this file.

Functions

int mirror_init (void)
char * mirror_convert_frame (uint8_t *rgba_data, int src_width, int src_height, int contain)
void mirror_free_string (char *ptr)
void mirror_submit_audio_samples (const float *samples, int count, int source)
char * mirror_render_audio_visualization (unsigned int width, unsigned int height, int source)
void mirror_cleanup (void)
void session_render_reset_stabilizer (void)
void session_render_cleanup (void)

Function Documentation

◆ mirror_cleanup()

void mirror_cleanup ( void )

Definition at line 96 of file mirror.c.

96 {
102}
void errno_destroy(void)
void buffer_system_cleanup(void)
Definition buffer.c:428
void platform_destroy(void)
Cleanup platform-specific subsystems.
void session_render_cleanup(void)
Definition mirror.c:86
void options_state_destroy(void)
Shutdown RCU options system.
Definition rcu.c:476

References buffer_system_cleanup(), errno_destroy(), options_state_destroy(), platform_destroy(), and session_render_cleanup().

Referenced by EMSCRIPTEN_BINDINGS(), and RendererSessionBinding::~RendererSessionBinding().

◆ mirror_convert_frame()

char * mirror_convert_frame ( uint8_t * rgba_data,
int src_width,
int src_height,
int contain )

Definition at line 108 of file mirror.c.

108 {
109 if (!rgba_data || src_width <= 0 || src_height <= 0) {
110 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid browser frame pixels or dimensions");
111 return NULL;
112 }
113
114 g_frame_count++;
115 // Derive a time value from frame count using actual target FPS from options
116 int target_fps = (int)GET_OPTION(fps);
117 if (target_fps <= 0) {
118 target_fps = 60; // Default to 60 FPS if not set
119 }
120 float time_seconds = (float)g_frame_count / (float)target_fps;
121
122 // Get current settings from options or terminal detection
123 int dst_width = (int)terminal_get_effective_width(GET_OPTION(width), 80);
124 int dst_height = (int)terminal_get_effective_height(GET_OPTION(height), 24);
125 color_filter_t filter = (color_filter_t)GET_OPTION(color_filter);
126 terminal_color_mode_t color_mode = (terminal_color_mode_t)GET_OPTION(color_mode);
127 if (g_frame_count <= 3 || g_frame_count % 120 == 0) {
128 log_debug("frame=%u color_filter=%d color_mode=%d", g_frame_count, (int)filter, (int)color_mode);
129 }
130 if (color_mode == TERM_COLOR_AUTO) {
131 color_mode = TERM_COLOR_TRUECOLOR; // xterm.js supports truecolor
132 }
133 palette_type_t palette_type = (palette_type_t)GET_OPTION(palette_type);
134 bool preserve_aspect_ratio = true; // Preserve webcam aspect ratio
135 bool stretch = false; // Don't stretch - maintain proportions
136
137 // Build terminal capabilities structure with user's color mode
138 terminal_capabilities_t caps = {0}; // Zero-initialize all fields first
139 caps.color_level = color_mode;
140 caps.capabilities = 0;
141 caps.color_count = (color_mode == TERM_COLOR_NONE) ? 0
142 : (color_mode == TERM_COLOR_16) ? 16
143 : (color_mode == TERM_COLOR_256) ? 256
144 : 16777216;
145 caps.utf8_support = true;
146 caps.detection_reliable = true;
147 caps.render_mode = (terminal_render_mode_t)GET_OPTION(render_mode);
148 caps.text_renderer = GET_OPTION(renderer);
149 caps.stabilize = GET_OPTION(stabilize);
150 caps.palette_explicit = GET_OPTION(palette_explicit);
151 caps.wants_background = (caps.render_mode == RENDER_MODE_BACKGROUND);
152 caps.wants_padding = true;
153 caps.palette_type = palette_type;
154 caps.desired_fps = 60;
155 caps.color_filter = filter;
156
157 // Convert RGBA to RGB (strip alpha channel) and optionally flip horizontally
158 rgb_pixel_t *rgb_pixels = SAFE_MALLOC(src_width * src_height * sizeof(rgb_pixel_t), rgb_pixel_t *);
159 if (!rgb_pixels) {
160 SET_ERRNO(ERROR_MEMORY, "Cannot allocate browser frame pixels");
161 return NULL;
162 }
163
164 bool flip_x = GET_OPTION(flip_x);
165 bool flip_y = GET_OPTION(flip_y);
166 for (int y = 0; y < src_height; y++) {
167 for (int x = 0; x < src_width; x++) {
168 int src_x = flip_x ? (src_width - 1 - x) : x;
169 int src_y = flip_y ? (src_height - 1 - y) : y;
170 int src_idx = (src_y * src_width + src_x) * 4;
171 int dst_idx = y * src_width + x;
172
173 rgb_pixels[dst_idx].r = rgba_data[src_idx + 0];
174 rgb_pixels[dst_idx].g = rgba_data[src_idx + 1];
175 rgb_pixels[dst_idx].b = rgba_data[src_idx + 2];
176 // Alpha (rgba_data[src_idx + 3]) is discarded
177 }
178 }
179
180 // Apply color filter to pixels if needed (except rainbow - handled in ANSI stage)
181 if (filter != COLOR_FILTER_NONE && filter != COLOR_FILTER_RAINBOW) {
182 // color_filter operates on packed RGB24 format
183 uint8_t *rgb24 = (uint8_t *)rgb_pixels;
184 int stride = src_width * 3;
185 apply_color_filter(rgb24, src_width, src_height, stride, filter, time_seconds);
186 }
187
188 // Create image structure
189 image_t img = {.w = src_width, .h = src_height, .pixels = rgb_pixels};
190
191 // Get palette characters based on selected palette type
192 const char *palette_chars;
193 switch (palette_type) {
194 case PALETTE_BLOCKS:
195 palette_chars = PALETTE_CHARS_BLOCKS;
196 break;
197 case PALETTE_DIGITAL:
198 palette_chars = PALETTE_CHARS_DIGITAL;
199 break;
200 case PALETTE_MINIMAL:
201 palette_chars = PALETTE_CHARS_MINIMAL;
202 break;
203 case PALETTE_COOL:
204 palette_chars = PALETTE_CHARS_COOL;
205 break;
206 case PALETTE_CUSTOM:
207 // Custom palette uses user-provided characters from options
208 palette_chars = GET_OPTION(palette_custom);
209 if (!palette_chars || palette_chars[0] == '\0') {
210 palette_chars = PALETTE_CHARS_STANDARD; // Fallback
211 }
212 break;
213 case PALETTE_STANDARD:
214 default:
215 palette_chars = PALETTE_CHARS_STANDARD;
216 break;
217 }
218
219 // Convert to ASCII using capability-aware function
220 char *ascii_output = ascii_convert_with_buffers_stabilized(&img, dst_width, dst_height, &caps, preserve_aspect_ratio,
221 stretch, palette_chars, contain != 0, NULL, NULL,
222 caps.stabilize ? &g_stabilizer : NULL);
223 if (!caps.stabilize)
224 ascii_stabilizer_reset(&g_stabilizer);
225
226 // Clean up
227 SAFE_FREE(rgb_pixels);
228
229 if (!ascii_output) {
230 log_error("ascii_convert_with_capabilities returned NULL");
231 return NULL;
232 }
233
234 // Apply rainbow color filter to ANSI output by replacing RGB values
235 // This preserves character selection while applying rainbow colors
236 if (filter == COLOR_FILTER_RAINBOW) {
237 char *rainbow_output = rainbow_replace_ansi_colors(ascii_output, time_seconds);
238 if (rainbow_output) {
239 SAFE_FREE(ascii_output);
240 ascii_output = rainbow_output;
241 }
242 }
243
244 bool matrix_rain = GET_OPTION(matrix_rain);
245 if (matrix_rain) {
246 // Initialize digital rain context if needed or dimensions changed
247 if (!g_digital_rain || g_digital_rain->num_columns != dst_width || g_digital_rain->num_rows != dst_height) {
248 if (g_digital_rain) {
249 digital_rain_destroy(g_digital_rain);
250 }
251 g_digital_rain = digital_rain_init(dst_width, dst_height);
252 if (!g_digital_rain) {
253 log_error("Failed to initialize digital rain effect");
254 goto validate_output;
255 }
256 g_last_rain_update_time = emscripten_get_now();
257 }
258
259 // Update color from active filter (allows filter changes after initialization)
260 digital_rain_set_color_from_filter(g_digital_rain, filter);
261
262 // Always apply rain to the newest input. Caching an entire ANSI frame freezes
263 // both the animation and webcam while the browser keeps counting presentations.
264 double current_time = emscripten_get_now();
265 float delta_time = (float)((current_time - g_last_rain_update_time) / 1000.0);
266 g_last_rain_update_time = current_time;
267 char *rain_output = digital_rain_apply(g_digital_rain, ascii_output, delta_time);
268 if (rain_output) {
269 SAFE_FREE(ascii_output);
270 ascii_output = rain_output;
271 }
272 } else {
273 // Clean up digital rain context if it exists but effect is disabled
274 if (g_digital_rain) {
275 digital_rain_destroy(g_digital_rain);
276 g_digital_rain = NULL;
277 }
278 g_last_rain_update_time = 0.0;
279 }
280
281validate_output:
282 if (ascii_output) {
283 bool glyph_match = caps.text_renderer == ASCII_RENDERER_GLYPH && caps.render_mode != RENDER_MODE_HALF_BLOCK &&
284 dst_width > 0 && dst_height > 0 && dst_width <= IMAGE_MAX_WIDTH / 8 &&
285 dst_height <= IMAGE_MAX_HEIGHT / 8 &&
286 (!caps.palette_explicit || ascii_glyph_palette_supported(palette_chars));
287 if (!glyph_match && (size_t)utf8_display_width(palette_chars) != utf8_char_count(palette_chars)) {
288 ascii_frame_source_t source = {.frame_data = ascii_output, .frame_size = strlen(ascii_output)};
289 size_t normalized_size;
290 char *rectangular = ascii_create_grid(&source, 1, dst_width, dst_height, &normalized_size);
291 SAFE_FREE(ascii_output);
292 ascii_output = rectangular;
293 }
294 if (!ascii_output)
295 return NULL;
296 acip_render_settings_t rendering = {.caps = caps};
297 const char *contract_palette =
298 glyph_match && !caps.palette_explicit
299 ? " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
300 : palette_chars;
301 SAFE_STRNCPY(rendering.palette, contract_palette, sizeof(rendering.palette));
302 uint32_t effects = matrix_rain ? FRAME_RENDER_CONTRACT_RAIN : 0;
303 if (session_frame_validate_contract(ascii_output, strlen(ascii_output), dst_width, dst_height, &rendering,
304 effects) != ASCIICHAT_OK) {
305 SAFE_FREE(ascii_output);
306 return NULL;
307 }
308 }
309 return ascii_output;
310}
char * ascii_convert_with_buffers_stabilized(image_t *original, const ssize_t width, const ssize_t height, const terminal_capabilities_t *caps, const bool use_aspect_ratio, const bool stretch, const char *palette_chars, const bool contain, image_t **workspace, outbuf_t *output, ascii_stabilizer_t *stabilizer)
Definition ascii.c:293
#define SET_ERRNO(code,...)
int color_mode
Definition fft.c:25
audio_visualization_source_t source
Definition fft.c:24
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
asciichat_error_t session_frame_validate_contract(const char *text, size_t length, uint32_t width, uint32_t height, const acip_render_settings_t *settings, uint32_t effects)
unsigned int uint32_t
Definition common.h:62
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#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
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
@ ASCII_RENDERER_GLYPH
Definition options.h:966
#define PALETTE_CHARS_MINIMAL
Minimal ASCII palette character string.
Definition palette.h:198
#define PALETTE_CHARS_DIGITAL
Digital/glitch palette character string.
Definition palette.h:189
palette_type_t
Built-in palette type enumeration.
Definition palette.h:90
#define PALETTE_CHARS_STANDARD
Standard ASCII palette character string.
Definition palette.h:171
#define PALETTE_CHARS_BLOCKS
Unicode block palette character string.
Definition palette.h:180
#define PALETTE_CHARS_COOL
Cool ascending blocks palette character string.
Definition palette.h:207
@ PALETTE_BLOCKS
Unicode block characters: " ░░▒▒▓▓██".
Definition palette.h:96
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:104
@ PALETTE_COOL
Ascending blocks: " ▁▂▃▄▅▆▇█".
Definition palette.h:102
@ PALETTE_STANDARD
Standard ASCII palette: " ...',;:clodxkO0KXNWM".
Definition palette.h:94
@ PALETTE_DIGITAL
Digital/glitch aesthetic: " -=≡≣▰▱◼".
Definition palette.h:98
@ PALETTE_MINIMAL
Simple ASCII: " .-+*#".
Definition palette.h:100
int utf8_display_width(const char *str)
Calculate terminal display width of a UTF-8 string.
Definition utf8.c:98
size_t utf8_char_count(const char *str)
Count UTF-8 characters (not bytes).
Definition utf8.c:108
char * rainbow_replace_ansi_colors(const char *ansi_string, float time_seconds)
Replace all RGB ANSI codes with rainbow color.
int apply_color_filter(uint8_t *pixels, uint32_t width, uint32_t height, uint32_t stride, color_filter_t filter, float time)
Apply color filter to entire image in-place.
void digital_rain_set_color_from_filter(digital_rain_t *rain, color_filter_t filter)
Set digital rain color from color filter.
#define IMAGE_MAX_WIDTH
An owned RGB image with dimensions and row-major pixel storage.
#define IMAGE_MAX_HEIGHT
Maximum image height (4K resolution).
void digital_rain_destroy(digital_rain_t *rain)
Destroy digital rain effect context.
char * digital_rain_apply(digital_rain_t *rain, const char *frame, float delta_time)
Apply digital rain effect to ASCII frame.
digital_rain_t * digital_rain_init(int num_columns, int num_rows)
Initialize digital rain effect context.
char * ascii_create_grid(ascii_frame_source_t *sources, int source_count, int width, int height, size_t *out_size)
Create a grid layout from multiple ASCII frames.
Definition ascii.c:742
bool ascii_glyph_palette_supported(const char *palette)
void ascii_stabilizer_reset(ascii_stabilizer_t *state)
#define FRAME_RENDER_CONTRACT_RAIN
acip render settings keeps the values that move through the subsystem: caps, width,...
Frame source structure for grid layout.
Definition ascii.h:352
SIMD-friendly storage for one RGB pixel. r, g, and b hold the color channels and padding keeps the la...
uint8_t b
Blue color component (0-255).
uint8_t g
Green color component (0-255).
uint8_t r
Red color component (0-255).

References apply_color_filter(), ascii_convert_with_buffers_stabilized(), ascii_create_grid(), ascii_glyph_palette_supported(), ASCII_RENDERER_GLYPH, ascii_stabilizer_reset(), ASCIICHAT_OK, rgb_pixel_t::b, color_mode, digital_rain_apply(), digital_rain_destroy(), digital_rain_init(), digital_rain_set_color_from_filter(), ERROR_INVALID_PARAM, ERROR_MEMORY, FRAME_RENDER_CONTRACT_RAIN, rgb_pixel_t::g, GET_OPTION, height, IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, log_debug, log_error, acip_render_settings_t::palette, PALETTE_BLOCKS, PALETTE_CHARS_BLOCKS, PALETTE_CHARS_COOL, PALETTE_CHARS_DIGITAL, PALETTE_CHARS_MINIMAL, PALETTE_CHARS_STANDARD, PALETTE_COOL, PALETTE_CUSTOM, PALETTE_DIGITAL, PALETTE_MINIMAL, PALETTE_STANDARD, rgb_pixel_t::r, rainbow_replace_ansi_colors(), SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, session_frame_validate_contract(), SET_ERRNO, source, utf8_char_count(), utf8_display_width(), and width.

Referenced by MirrorFrameBinding::convert().

◆ mirror_free_string()

void mirror_free_string ( char * ptr)

Definition at line 336 of file mirror.c.

336 {
337 SAFE_FREE(ptr);
338}

References SAFE_FREE.

Referenced by MirrorFrameBinding::convert(), and MirrorFrameBinding::renderAudio().

◆ mirror_init()

int mirror_init ( void )

Browser-facing C interface implemented in mirror.c.

Initialize mirror mode with native defaults.

Returns
0 on success, -1 on error

Definition at line 51 of file mirror.c.

51 {
52 // Initialize platform layer
54 if (err != ASCIICHAT_OK) {
55 return -1;
56 }
57
58 char *argv[] = {"mirror", NULL};
59 err = options_init(1, argv);
60
61 if (err != ASCIICHAT_OK) {
62 return -1;
63 }
64
66 return -1;
67
68 // Honor parsed verbosity and let INFO use stdout (console.log).
69 log_init(NULL, GET_OPTION(log_level), false, false);
70 // Use debug-style format in WASM so browser console shows full log headers.
72
73 // Log option values after initialization
74 log_info("Mirror options initialized");
75
76 // Initialize ANSI color code generation (dec3 cache for RGB values)
78
79 return 0;
80}
asciichat_error_t audio_visualization_fft_init(void)
Definition fft.c:61
void ansi_fast_init(void)
Definition ansi.c:227
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
Initialize the logging system.
Definition log.c:569
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
asciichat_error_t log_set_format(const char *format_str, bool console_only)
Set a custom log format string.
Definition log.c:790
asciichat_error_t options_init(int argc, char **argv)
Initialize options by parsing command-line arguments with unified mode detection.
Definition options.c:2417
#define OPT_LOG_TEMPLATE_DEFAULT_DEBUG
Default log format string - debug mode (verbose with thread name, file relative path,...
Definition options.h:378
asciichat_error_t platform_init(void)
Initialize platform-specific subsystems.

References ansi_fast_init(), ASCIICHAT_OK, audio_visualization_fft_init(), GET_OPTION, log_info, log_init(), log_set_format(), OPT_LOG_TEMPLATE_DEFAULT_DEBUG, options_init(), and platform_init().

Referenced by RendererSessionBinding::RendererSessionBinding().

◆ mirror_render_audio_visualization()

char * mirror_render_audio_visualization ( unsigned int width,
unsigned int height,
int source )

Definition at line 320 of file mirror.c.

320 {
321 audio_visualization_source_t audio_source =
323 char *text =
324 GET_OPTION(fft)
326 GET_OPTION(flip_x), GET_OPTION(flip_y))
328 GET_OPTION(flip_x), GET_OPTION(flip_y));
329 if (text && session_frame_validate(text, strlen(text), width, height) != ASCIICHAT_OK) {
330 SAFE_FREE(text);
331 return NULL;
332 }
333 return text;
334}
char * audio_visualization_render_fft(unsigned int width, unsigned int height, audio_visualization_source_t source, bool use_color, int color_mode, bool flip_x, bool flip_y)
Definition fft.c:91
asciichat_error_t session_frame_validate(const char *text, size_t length, uint32_t width, uint32_t height)
return true
Definition queries.h:13
audio_visualization_source_t
@ AUDIO_VISUALIZATION_SOURCE_MEDIA
@ AUDIO_VISUALIZATION_SOURCE_MIC
#define AUDIO_VISUALIZATION_COLOR_BRIGHTER
char * audio_visualization_render_waveform(unsigned int width, unsigned int height, audio_visualization_source_t source, bool use_color, int color_mode, bool flip_x, bool flip_y)
Definition waveform.c:39

References ASCIICHAT_OK, AUDIO_VISUALIZATION_COLOR_BRIGHTER, audio_visualization_render_fft(), audio_visualization_render_waveform(), AUDIO_VISUALIZATION_SOURCE_MEDIA, AUDIO_VISUALIZATION_SOURCE_MIC, GET_OPTION, height, SAFE_FREE, session_frame_validate(), source, and width.

Referenced by MirrorFrameBinding::renderAudio().

◆ mirror_submit_audio_samples()

void mirror_submit_audio_samples ( const float * samples,
int count,
int source )

Definition at line 312 of file mirror.c.

312 {
313 if (!samples || count <= 0 || count > 8192)
314 return;
317 samples, (size_t)count);
318}
void audio_visualization_submit(audio_visualization_source_t source, const float *samples, size_t count)

References AUDIO_VISUALIZATION_SOURCE_MEDIA, AUDIO_VISUALIZATION_SOURCE_MIC, audio_visualization_submit(), and source.

Referenced by MirrorFrameBinding::submitAudio().

◆ session_render_cleanup()

void session_render_cleanup ( void )

Definition at line 86 of file mirror.c.

86 {
88 if (g_digital_rain) {
89 digital_rain_destroy(g_digital_rain);
90 g_digital_rain = NULL;
91 }
92 g_last_rain_update_time = 0.0;
93 g_frame_count = 0;
94}
void session_render_reset_stabilizer(void)
Definition mirror.c:82

References digital_rain_destroy(), and session_render_reset_stabilizer().

Referenced by EMSCRIPTEN_BINDINGS(), and mirror_cleanup().

◆ session_render_reset_stabilizer()

void session_render_reset_stabilizer ( void )

Definition at line 82 of file mirror.c.

82 {
83 ascii_stabilizer_reset(&g_stabilizer);
84}

References ascii_stabilizer_reset().

Referenced by EMSCRIPTEN_BINDINGS(), and session_render_cleanup().