35 log_info(
"Initializing ASCII reader with webcam index %u", webcam_index);
43 log_error(
"Invalid file descriptor %d", fd);
49 if (!
GET_OPTION(snapshot_mode) && reset_terminal && testing_env == NULL) {
55 log_error(
"Failed to disable echo for fd %d", fd);
68 const bool _aspect_ratio,
const bool stretch,
const char *palette_chars,
69 const char luminance_palette[256]) {
70 if (original == NULL || !palette_chars || !luminance_palette) {
71 log_error(
"ascii_convert: invalid parameters");
76 if (palette_chars[0] ==
'\0' || luminance_palette[0] ==
'\0') {
77 log_error(
"ascii_convert: empty palette strings");
85 ssize_t resized_width = width;
86 ssize_t resized_height = height;
92 aspect_ratio(original->
w, original->
h, resized_width, resized_height, stretch, &resized_width, &resized_height);
97 size_t pad_height = 0;
101 ssize_t pad_width_ss = width > resized_width ? (width - resized_width) / 2 : 0;
102 pad_width = (size_t)pad_width_ss;
104 ssize_t pad_height_ss = height > resized_height ? (height - resized_height) / 2 : 0;
105 pad_height = (size_t)pad_height_ss;
109 if (resized_width <= 0 || resized_height <= 0) {
110 log_error(
"Invalid dimensions for resize: width=%zd, height=%zd", resized_width, resized_height);
115 if (resized_width > INT_MAX || resized_height > INT_MAX) {
116 log_error(
"Dimensions exceed INT_MAX: width=%zd, height=%zd", resized_width, resized_height);
121 image_t *resized =
image_new((
size_t)resized_width, (
size_t)resized_height);
123 log_error(
"Failed to allocate resized image");
137 ascii = rgb_to_truecolor_halfblocks_neon(rgb_data, resized->
w, resized->
h, 0);
139 log_error(
"Half-block mode requires NEON support (ARM architecture)");
162 log_error(
"Failed to convert image to ASCII");
167 size_t ascii_len = strlen(ascii);
168 if (ascii_len == 0) {
169 log_error(
"ASCII conversion returned empty string (resized dimensions: %dx%d)", resized->
w, resized->
h);
190 const bool stretch,
const char *palette_chars,
191 const char luminance_palette[256]) {
193 if (original == NULL || caps == NULL) {
194 log_error(
"Invalid parameters for ascii_convert_with_capabilities");
199 ssize_t resized_width = width;
200 ssize_t resized_height = height;
207 aspect_ratio(original->
w, original->
h, resized_width, resized_height, stretch, &resized_width, &resized_height);
212 size_t pad_width = 0;
213 size_t pad_height = 0;
215 if (use_aspect_ratio) {
216 ssize_t pad_width_ss = width > resized_width ? (width - resized_width) / 2 : 0;
217 pad_width = (size_t)pad_width_ss;
219 ssize_t pad_height_ss = height > resized_height ? (height - resized_height) / 2 : 0;
220 pad_height = (size_t)pad_height_ss;
224 if (resized_width <= 0 || resized_height <= 0) {
225 log_error(
"Invalid dimensions for resize: width=%zd, height=%zd", resized_width, resized_height);
230 if (resized_width > INT_MAX || resized_height > INT_MAX) {
231 log_error(
"Dimensions exceed INT_MAX: width=%zd, height=%zd", resized_width, resized_height);
236 struct timespec prof_alloc_start, prof_alloc_end, prof_resize_start, prof_resize_end;
237 (void)clock_gettime(CLOCK_MONOTONIC, &prof_alloc_start);
239 image_t *resized =
image_new((
size_t)resized_width, (
size_t)resized_height);
241 log_error(
"Failed to allocate resized image");
247 (void)clock_gettime(CLOCK_MONOTONIC, &prof_alloc_end);
248 (void)clock_gettime(CLOCK_MONOTONIC, &prof_resize_start);
252 (void)clock_gettime(CLOCK_MONOTONIC, &prof_resize_end);
255 struct timespec prof_print_start, prof_print_end;
256 (void)clock_gettime(CLOCK_MONOTONIC, &prof_print_start);
261 (void)clock_gettime(CLOCK_MONOTONIC, &prof_print_end);
264 ((
uint64_t)prof_alloc_start.tv_sec * 1000000 + (
uint64_t)prof_alloc_start.tv_nsec / 1000);
266 ((
uint64_t)prof_resize_start.tv_sec * 1000000 + (
uint64_t)prof_resize_start.tv_nsec / 1000);
268 ((
uint64_t)prof_print_start.tv_sec * 1000000 + (
uint64_t)prof_print_start.tv_nsec / 1000);
271 struct timespec prof_pad_start, prof_pad_end;
272 (void)clock_gettime(CLOCK_MONOTONIC, &prof_pad_start);
275 log_error(
"Failed to convert image to ASCII using terminal capabilities");
280 size_t ascii_len = strlen(ascii);
281 if (ascii_len == 0) {
282 log_error(
"Capability-aware ASCII conversion returned empty string (resized dimensions: %dx%d)", resized->
w,
295 (void)clock_gettime(CLOCK_MONOTONIC, &prof_pad_end);
298 ((
uint64_t)prof_pad_start.tv_sec * 1000000 + (
uint64_t)prof_pad_start.tv_nsec / 1000);
300 (void)resize_time_us;
314 log_warn(
"Attempted to write NULL frame");
320 if (!
GET_OPTION(snapshot_mode) && testing_env == NULL) {
324 size_t frame_len = strlen(frame);
325 size_t written = fwrite(frame, 1, frame_len, stdout);
326 if (written != frame_len) {
327 log_error(
"Failed to write ASCII frame");
341 if (!
GET_OPTION(snapshot_mode) && reset_terminal) {
349 log_warn(
"Failed to re-enable echo");
383 size_t orig_len = strlen(frame);
386 SAFE_MEMCPY(copy, orig_len + 1, frame, orig_len + 1);
392 size_t line_count = 1;
393 const char *char_in_frame = frame;
394 while (*char_in_frame) {
395 if (*char_in_frame ==
'\n') {
402 const size_t frame_len = strlen(frame);
403 const size_t left_padding_len = line_count * pad_left;
404 const size_t total_len = frame_len + left_padding_len;
410 bool at_line_start =
true;
411 const char *src = frame;
412 char *position = buffer;
417 size_t remaining = (size_t)((ptrdiff_t)(buffer + total_len + 1) - (ptrdiff_t)position);
418 SAFE_MEMSET(position, remaining,
' ', (
size_t)pad_left);
419 position += pad_left;
420 at_line_start =
false;
426 at_line_start =
true;
451 if (!sources || source_count <= 0 || width <= 0 || height <= 0 || !out_size) {
458 if (source_count == 1) {
461 size_t w = (size_t)width;
462 size_t h = (size_t)height;
464 if (checked_size_mul(w, h, &w_times_h) !=
ASCIICHAT_OK) {
469 size_t w_times_h_plus_h;
470 if (checked_size_add(w_times_h, h, &w_times_h_plus_h) !=
ASCIICHAT_OK) {
476 if (checked_size_add(w_times_h_plus_h, 1, &target_size) !=
ASCIICHAT_OK) {
482 SAFE_MEMSET(result, target_size,
' ', target_size - 1);
483 result[target_size - 1] =
'\0';
486 for (
int row = 0; row < height; row++) {
487 result[row * (width + 1) + width] =
'\n';
494 int src_size = (int)sources[0].frame_size;
497 if (!src_data || src_size <= 0) {
498 *out_size = target_size - 1;
504 for (
int i = 0; i < src_size; i++) {
505 if (src_data[i] ==
'\n')
509 int v_padding = (height - src_lines) / 2;
513 int dst_row = v_padding;
516 while (src_pos < src_size && dst_row < height) {
518 int line_start = src_pos;
520 while (src_pos < src_size && src_data[src_pos] !=
'\n') {
526 int h_padding = (width - line_len) / 2;
532 size_t row_offset = (size_t)dst_row * (
size_t)(width + 1);
533 size_t dst_pos = row_offset + (size_t)h_padding;
534 int copy_len = (line_len > width - h_padding) ? width - h_padding : line_len;
536 if (copy_len > 0 && dst_pos + (
size_t)copy_len < target_size) {
537 SAFE_MEMCPY(&result[dst_pos], target_size - dst_pos, &src_data[line_start], (
size_t)copy_len);
541 if (src_pos < src_size && src_data[src_pos] ==
'\n') {
548 *out_size = target_size - 1;
555 float char_aspect = 2.0f;
557 int grid_cols, grid_rows;
558 float best_score = -1.0f;
560 int best_rows = source_count;
563 for (
int test_cols = 1; test_cols <= source_count; test_cols++) {
564 int test_rows = (int)ceil((
double)source_count / test_cols);
567 int empty_cells = (test_cols * test_rows) - source_count;
568 if (empty_cells > source_count / 2)
572 int cell_width = (width - (test_cols - 1)) / test_cols;
573 int cell_height = (height - (test_rows - 1)) / test_rows;
576 if (cell_width < 10 || cell_height < 3)
580 float cell_aspect = ((float)cell_width / (
float)cell_height) / char_aspect;
584 float aspect_score = 1.0f - fabsf(logf(cell_aspect));
585 if (aspect_score < 0)
589 float utilization = (float)source_count / (
float)(test_cols * test_rows);
594 if (source_count == 2) {
596 total_score = aspect_score * 0.9f + utilization * 0.1f;
599 total_score = aspect_score * 0.7f + utilization * 0.3f;
603 if (test_cols == test_rows) {
604 total_score += 0.05f;
607 if (total_score > best_score) {
608 best_score = total_score;
609 best_cols = test_cols;
610 best_rows = test_rows;
614 grid_cols = best_cols;
615 grid_rows = best_rows;
618 int cell_width = (width - (grid_cols - 1)) / grid_cols;
619 int cell_height = (height - (grid_rows - 1)) / grid_rows;
621 if (cell_width < 10 || cell_height < 3) {
624 result =
SAFE_MALLOC(sources[0].frame_size + 1,
char *);
625 if (sources[0].frame_data && sources[0].frame_size > 0) {
626 SAFE_MEMCPY(result, sources[0].frame_size + 1, sources[0].frame_data, sources[0].frame_size);
628 *out_size = sources[0].frame_size;
639 size_t w_sz = (size_t)width;
640 size_t h_sz = (size_t)height;
641 if (w_sz > SIZE_MAX / h_sz) {
645 size_t mixed_size = w_sz * h_sz + h_sz + 1;
650 SAFE_MEMSET(mixed_frame, mixed_size,
' ', mixed_size - 1);
651 mixed_frame[mixed_size - 1] =
'\0';
654 for (
int row = 0; row < height; row++) {
655 mixed_frame[row * (width + 1) + width] =
'\n';
659 for (
int src = 0; src < source_count; src++) {
660 int grid_row = src / grid_cols;
661 int grid_col = src % grid_cols;
664 int start_row = grid_row * (cell_height + 1);
665 int start_col = grid_col * (cell_width + 1);
668 const char *src_data = sources[src].
frame_data;
672 while (src_pos < (
int)sources[src].
frame_size && src_row < cell_height && start_row + src_row < height) {
674 int line_start = src_pos;
675 while (src_pos < (
int)sources[src].
frame_size && src_data[src_pos] !=
'\n') {
678 int line_len = src_pos - line_start;
681 int copy_len = (line_len < cell_width) ? line_len : cell_width;
682 if (copy_len > 0 && start_col + copy_len <= width) {
683 int mixed_pos = (start_row + src_row) * (width + 1) + start_col;
684 SAFE_MEMCPY(mixed_frame + mixed_pos, mixed_size - (
size_t)mixed_pos, src_data + line_start, (
size_t)copy_len);
688 if (src_pos < (
int)sources[src].frame_size && src_data[src_pos] ==
'\n') {
695 if (grid_col < grid_cols - 1 && start_col + cell_width < width) {
697 for (
int row = start_row; row < start_row + cell_height && row < height; row++) {
698 size_t idx = (size_t)row * (
size_t)(width + 1) + (
size_t)(start_col + cell_width);
699 if (idx < mixed_size - 1) {
700 mixed_frame[idx] =
'|';
705 if (grid_row < grid_rows - 1 && start_row + cell_height < height) {
707 for (
int col = start_col; col < start_col + cell_width && col < width; col++) {
708 size_t idx = (size_t)(start_row + cell_height) * (size_t)(width + 1) + (size_t)col;
709 if (idx < mixed_size - 1) {
710 mixed_frame[idx] =
'_';
714 if (grid_col < grid_cols - 1 && start_col + cell_width < width) {
715 size_t idx = (size_t)(start_row + cell_height) * (size_t)(width + 1) + (size_t)(start_col + cell_width);
716 if (idx < mixed_size - 1) {
717 mixed_frame[idx] =
'+';
723 *out_size = strlen(mixed_frame);
745 size_t orig_len = strlen(frame);
748 SAFE_MEMCPY(copy, orig_len + 1, frame, orig_len + 1);
753 size_t frame_len = strlen(frame);
754 size_t top_padding_len = pad_top;
755 size_t total_len = top_padding_len + frame_len;
760 char *position = buffer;
763 for (
size_t i = 0; i < pad_top; i++) {
768 size_t remaining = total_len + 1 - pad_top;
769 SAFE_MEMCPY(position, remaining, frame, frame_len);
770 position += frame_len;
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
🖼️ ASCII Art Conversion and Output Interface
SIMD-optimized ASCII conversion interface.
📐 Aspect Ratio Calculation Functions
#define SAFE_MEMSET(dest, dest_size, ch, count)
#define SAFE_MALLOC(size, cast)
#define SAFE_GETENV(name)
unsigned long long uint64_t
#define SAFE_MEMCPY(dest, dest_size, src, count)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
void aspect_ratio(const ssize_t img_w, const ssize_t img_h, const ssize_t width, const ssize_t height, const bool stretch, ssize_t *out_width, ssize_t *out_height)
Calculate aspect ratio with terminal character correction.
char * image_print(const image_t *p, const char *palette)
Print image as ASCII art (monochrome)
asciichat_error_t ascii_write_init(int fd, bool reset_terminal)
Initialize ASCII write subsystem.
char * image_print_color(const image_t *p, const char *palette)
Print image as ASCII art with color.
void ascii_read_destroy(void)
Destroy ASCII read subsystem.
void ascii_write_destroy(int fd, bool reset_terminal)
Destroy ASCII write subsystem.
asciichat_error_t ascii_write(const char *frame)
Write ASCII frame to terminal.
char * ascii_pad_frame_width(const char *frame, size_t pad_left)
Add leading spaces (left-padding) to each line of a frame.
char * ascii_pad_frame_height(const char *frame, size_t pad_top)
Add blank lines (vertical padding) to center a frame vertically.
void image_clear(image_t *p)
Clear image (set all pixels to black)
void image_resize(const image_t *s, image_t *d)
Resize image using nearest-neighbor interpolation.
char * image_print_simd(image_t *image, const char *ascii_chars)
Print image as ASCII using SIMD (monochrome)
#define console_clear(fd)
Clear console and move cursor to home position.
char * ascii_convert(image_t *original, const ssize_t width, const ssize_t height, const bool color, const bool _aspect_ratio, const bool stretch, const char *palette_chars, const char luminance_palette[256])
Convert image to ASCII art.
#define cursor_reset(fd)
Reset cursor to home position.
asciichat_error_t ascii_read_init(unsigned short int webcam_index)
Initialize ASCII read subsystem (e.g., webcam)
char * image_print_color_simd(image_t *image, bool use_background_mode, bool use_256color, const char *ascii_chars)
Print image as ASCII with color using SIMD.
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.
void image_destroy(image_t *p)
Destroy an image allocated with image_new()
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
char * ascii_convert_with_capabilities(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 char luminance_palette[256])
Convert image to ASCII art with terminal capability awareness.
asciichat_error_t webcam_init(unsigned short int webcam_index)
Initialize global webcam interface.
void webcam_cleanup(void)
Clean up global webcam interface.
🔢 Mathematical Utility Functions
⚙️ Command-line options parsing and configuration management for ascii-chat
✅ Safe Integer Arithmetic and Overflow Detection
Frame source structure for grid layout.
size_t frame_size
Frame data size in bytes (length of frame string)
const char * frame_data
Frame data pointer (ASCII frame string, not owned)
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)
Complete terminal capabilities structure.
render_mode_t render_mode
Preferred rendering mode (render_mode_t)
🖥️ Cross-platform terminal interface for ascii-chat
⏱️ High-precision timing utilities using sokol_time.h and uthash
char * image_print_with_capabilities(const image_t *image, const terminal_capabilities_t *caps, const char *palette, const char luminance_palette[256] __attribute__((unused)))
Image Data Structures and Operations.