ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Video to ASCII Conversion

🖼️ Everything necessary to turn pixels into text art More...

Modules

 Video Frame Buffers
 📹 High-performance video frame management with double-buffering and lock-free operations
 

Files

file  digital_rain.c
 Matrix-style digital rain effect implementation.
 
file  common.c
 ARM NEON-accelerated ASCII rendering utilities.
 
file  halfblock.c
 ARM NEON-accelerated ASCII half-block rendering.
 
file  background.c
 Scalar ASCII background color rendering (truecolor)
 
file  ansi.c
 ANSI escape sequence utilities and fast color code generation with SIMD-accelerated terminal output.
 
file  digital_rain.h
 Matrix-style digital rain effect for ASCII frames.
 
file  avx2.h
 AVX2-optimized ASCII rendering functions.
 
file  common.h
 SIMD-optimized ASCII conversion interface.
 
file  neon.h
 NEON-optimized ASCII rendering functions.
 
file  common.h
 ARM NEON-accelerated ASCII rendering utilities (declarations)
 
file  sgr.h
 ANSI SGR (Select Graphic Rendition) color sequence generation.
 
file  sse2.h
 SSE2-optimized ASCII rendering functions.
 
file  ssse3.h
 SSSE3-optimized ASCII rendering functions.
 
file  sve.h
 SVE-optimized ASCII rendering functions.
 
file  ansi.h
 ANSI escape sequence utilities and fast color code generation.
 

Data Structures

struct  digital_rain_column_t
 Digital rain column state. More...
 
struct  digital_rain_t
 Digital rain effect context. More...
 
struct  ascii_frame_source_t
 Frame source structure for grid layout. More...
 
struct  dec3_t
 Decimal conversion cache structure (1-3 digits) More...
 
struct  global_dec3_cache_t
 Global decimal cache for digit conversion. More...
 
struct  Str
 Dynamic string buffer structure. More...
 
struct  RLEState
 Run-length encoding state for ANSI color optimization. More...
 
struct  ImageRGB
 ImageRGB structure for NEON renderers. More...
 
struct  simd_benchmark_t
 SIMD benchmark results structure. More...
 
struct  utf8_char_t
 
struct  utf8_palette_cache
 
struct  outbuf_t
 Dynamic output buffer (auto-expanding) More...
 
struct  color_filter_def_t
 Color filter metadata structure. More...
 
struct  rgb_pixel_t
 RGB pixel structure. More...
 
struct  rgb_pixel_simd_t
 SIMD-aligned RGB pixel structure for optimal NEON/AVX performance. More...
 
struct  image_t
 Image structure. More...
 

Macros

#define DIGITAL_RAIN_DEFAULT_FALL_SPEED   3.0f
 Default fall speed for raindrops.
 
#define DIGITAL_RAIN_DEFAULT_RAINDROP_LENGTH   12.0f
 Default raindrop length.
 
#define DIGITAL_RAIN_DEFAULT_BRIGHTNESS_DECAY   0.1f
 Default brightness decay factor.
 
#define DIGITAL_RAIN_DEFAULT_ANIMATION_SPEED   1.0f
 Default animation speed multiplier.
 
#define DIGITAL_RAIN_DEFAULT_COLOR_R   0
 Default Matrix green color (R, G, B)
 
#define DIGITAL_RAIN_DEFAULT_COLOR_G   255
 
#define DIGITAL_RAIN_DEFAULT_COLOR_B   80
 
#define DIGITAL_RAIN_DEFAULT_CURSOR_BRIGHTNESS   2.0f
 Cursor brightness multiplier.
 
#define ASCII_LUMINANCE_LEVELS   256
 Number of luminance levels supported (256)
 
#define ASCII_SLEEP_NS   50000L
 Sleep duration in nanoseconds between frames (50000 ns = 50 μs)
 
#define ANSI_FG_PREFIX   "\033[38;2;"
 ANSI foreground color prefix (truecolor mode)
 
#define ANSI_BG_PREFIX   "\033[48;2;"
 ANSI background color prefix (truecolor mode)
 
#define ANSI_COLOR_SUFFIX   "m"
 ANSI color suffix.
 
#define print(s)   fwrite(s, 1, sizeof(s) / sizeof((s)[0]), stdout)
 Print string to stdout.
 
#define console_clear(fd)   (terminal_clear_screen(), terminal_cursor_home(fd))
 Clear console and move cursor to home position.
 
#define cursor_reset(fd)   terminal_cursor_home(fd)
 Reset cursor to home position.
 
#define ascii_clear_screen(fd)   terminal_clear_screen()
 Clear terminal screen.
 
#define ascii_zzz()   time_sleep_ns(ASCII_SLEEP_NS)
 Sleep for frame rate limiting.
 
#define RAMP64_SIZE   64
 UTF-8 character palette cache.
 
#define CACHE_FREQUENCY_DECAY_TIME   10000
 
#define CACHE_RECENCY_SCALE   100
 
#define CACHE_MAX_LIFETIME   3600
 
#define IMAGE_MAX_WIDTH   3840
 Maximum image width (4K resolution)
 
#define IMAGE_MAX_HEIGHT   2160
 Maximum image height (4K resolution)
 
#define IMAGE_MAX_PIXELS_SIZE   (IMAGE_MAX_WIDTH * IMAGE_MAX_HEIGHT * sizeof(rgb_pixel_t))
 Maximum pixel data size in bytes.
 

Typedefs

typedef struct utf8_palette_cache utf8_palette_cache_t
 

Enumerations

enum  image_alloc_method_t { IMAGE_ALLOC_SIMD = 0 , IMAGE_ALLOC_POOL = 1 }
 Pixel allocation method tracking. More...
 

Functions

digital_rain_t * digital_rain_init (int num_columns, int num_rows)
 Initialize digital rain effect context.
 
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.
 
void digital_rain_reset (digital_rain_t *rain)
 Reset digital rain effect state.
 
void digital_rain_set_fall_speed (digital_rain_t *rain, float speed)
 Set digital rain fall speed.
 
void digital_rain_set_raindrop_length (digital_rain_t *rain, float length)
 Set digital rain raindrop length.
 
void digital_rain_set_color (digital_rain_t *rain, uint8_t r, uint8_t g, uint8_t b)
 Set digital rain color.
 
void digital_rain_set_color_from_filter (digital_rain_t *rain, color_filter_t filter)
 Set digital rain color from color filter.
 
asciichat_error_t ascii_write_init (int fd, bool reset_terminal)
 Initialize ASCII write subsystem.
 
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.
 
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)
 Convert image to ASCII art with terminal capability awareness.
 
char * image_print_with_capabilities (const image_t *image, const terminal_capabilities_t *caps, const char *palette)
 Convert image to ASCII art with terminal capabilities.
 
asciichat_error_t ascii_write (const char *frame)
 Write ASCII frame to terminal.
 
void ascii_write_destroy (int fd, bool reset_terminal)
 Destroy ASCII write subsystem.
 
char * ascii_pad_frame_width (const char *frame, size_t pad)
 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.
 
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.
 
char * get_lum_palette (void)
 Get luminance palette for character mapping.
 
void init_dec3 (void)
 Initialize decimal lookup table.
 
void ascii_simd_init (void)
 Initialize SIMD subsystem.
 
void init_default_luminance_palette (void)
 Initialize default luminance palette.
 
void str_init (Str *s)
 Initialize string buffer.
 
void str_destroy (Str *s)
 Free string buffer.
 
void str_reserve (Str *s, size_t need)
 Reserve space in string buffer.
 
void str_append_bytes (Str *s, const void *src, size_t n)
 Append bytes to string buffer.
 
void str_append_c (Str *s, char c)
 Append character to string buffer.
 
void str_printf (Str *s, const char *fmt,...)
 Append formatted string to buffer.
 
ImageRGB alloc_image (int w, int h)
 Allocate a new ImageRGB (RGB8 format)
 
void convert_pixels_scalar (const rgb_pixel_t *pixels, char *ascii_chars, int count, const char luminance_palette[256])
 Convert pixels to ASCII (scalar fallback)
 
char * convert_pixels_scalar_with_newlines (image_t *image, const char luminance_palette[256])
 Convert image to ASCII with newlines (scalar fallback)
 
simd_benchmark_t benchmark_simd_conversion (int width, int height, int iterations)
 Benchmark SIMD conversion methods (monochrome)
 
simd_benchmark_t benchmark_simd_color_conversion (int width, int height, int iterations, bool background_mode)
 Benchmark SIMD color conversion methods.
 
simd_benchmark_t benchmark_simd_conversion_with_source (int width, int height, int iterations, bool background_mode, const image_t *source_image, bool use_256color)
 Benchmark SIMD conversion with source image.
 
simd_benchmark_t benchmark_simd_color_conversion_with_source (int width, int height, int iterations, bool background_mode, const image_t *source_image, bool use_256color)
 Benchmark SIMD color conversion with source image.
 
void print_simd_capabilities (void)
 Print detected SIMD capabilities.
 
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.
 
void set_color_quality_mode (bool high_quality)
 Set color quality mode.
 
bool get_256_color_fast_path (void)
 Get 256-color fast path setting.
 
void prewarm_sgr256_fg_cache (void)
 Prewarm 256-color foreground cache for benchmarks.
 
void prewarm_sgr256_cache (void)
 Prewarm 256-color foreground/background cache for benchmarks.
 
char * get_sgr256_fg_string (uint8_t fg, uint8_t *len_out)
 Get 256-color foreground ANSI sequence string.
 
char * get_sgr256_bg_string (uint8_t bg, uint8_t *len_out)
 Get 256-color background ANSI sequence string.
 
char * get_sgr256_fg_bg_string (uint8_t fg, uint8_t bg, uint8_t *len_out)
 Get 256-color foreground/background ANSI sequence string.
 
size_t write_row_rep_from_arrays_enhanced (const uint8_t *fg_r, const uint8_t *fg_g, const uint8_t *fg_b, const uint8_t *bg_r, const uint8_t *bg_g, const uint8_t *bg_b, const uint8_t *fg_idx, const uint8_t *bg_idx, const char *ascii_chars, int width, char *dst, size_t cap, bool is_truecolor)
 Enhanced REP compression writer.
 
void build_utf8_luminance_cache (const char *ascii_chars, utf8_char_t cache[256])
 Build UTF-8 luminance cache for all 256 levels.
 
void build_utf8_ramp64_cache (const char *ascii_chars, utf8_char_t cache64[64], uint8_t char_index_ramp[256])
 Build UTF-8 ramp64 cache and character index ramp.
 
utf8_palette_cache_t * get_utf8_palette_cache (const char *ascii_chars)
 Get UTF-8 palette cache for a character set.
 
void simd_caches_destroy_all (void)
 Destroy all SIMD caches (palette and other caches)
 
void ob_reserve (outbuf_t *ob, size_t need)
 Reserve buffer space for upcoming writes.
 
void ob_putc (outbuf_t *ob, char c)
 Append a character to buffer.
 
void ob_write (outbuf_t *ob, const char *s, size_t n)
 Append a string to buffer.
 
void ob_term (outbuf_t *ob)
 Append null terminator to buffer.
 
void ob_u8 (outbuf_t *ob, uint8_t v)
 Append unsigned 8-bit integer as decimal string.
 
void ob_u32 (outbuf_t *ob, uint32_t v)
 Append unsigned 32-bit integer as decimal string.
 
void emit_set_truecolor_fg (outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
 Emit truecolor foreground ANSI sequence.
 
void emit_set_truecolor_bg (outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
 Emit truecolor background ANSI sequence.
 
void emit_set_256_color_fg (outbuf_t *ob, uint8_t color_idx)
 Emit 256-color foreground ANSI sequence.
 
void emit_set_256_color_bg (outbuf_t *ob, uint8_t color_idx)
 Emit 256-color background ANSI sequence.
 
void emit_reset (outbuf_t *ob)
 Emit ANSI reset sequence.
 
bool rep_is_profitable (uint32_t runlen)
 Check if run-length encoding is profitable.
 
void emit_rep (outbuf_t *ob, uint32_t extra)
 Emit run-length encoded sequence.
 
void emit_set_fg (outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
 Emit foreground color sequence (auto-select mode)
 
void emit_set_bg (outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
 Emit background color sequence (auto-select mode)
 
char * ansi_expand_rle (const char *input, size_t input_len)
 Expand RLE escape sequences in a string.
 
char * ansi_compress_rle (const char *input, size_t input_len)
 Compress repeated characters using RLE escape sequences.
 
char * rgb_to_truecolor_halfblocks_scalar (const uint8_t *rgb, int width, int height, int stride_bytes)
 Convert RGB to truecolor half-blocks using scalar code.
 
char * rgb_to_halfblocks_scalar (const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
 Convert RGB to monochrome (grayscale) half-blocks using scalar code.
 
char * rgb_to_16color_halfblocks_scalar (const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
 Convert RGB to 16-color half-blocks using scalar code.
 
char * rgb_to_256color_halfblocks_scalar (const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
 Convert RGB to 256-color half-blocks using scalar code.
 
const color_filter_def_t * color_filter_get_metadata (color_filter_t filter)
 Get color filter metadata by enum value.
 
color_filter_t color_filter_from_cli_name (const char *cli_name)
 Convert color filter CLI name to enum value.
 
void color_filter_calculate_rainbow (float time, uint8_t *r, uint8_t *g, uint8_t *b)
 Calculate rainbow color from time (3.5s cycle)
 
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.
 
asciichat_error_t image_render_test_pattern (image_t *image, unsigned int *frame_counter)
 Render animated color bars and grid lines into an existing image.
 
image_t * image_new (size_t width, size_t height)
 Create a new image with standard allocation.
 
void image_destroy (image_t *p)
 Destroy an image allocated with image_new()
 
image_t * image_new_from_pool (size_t width, size_t height)
 Create a new image from buffer pool.
 
void image_destroy_to_pool (image_t *image)
 Destroy an image allocated from buffer pool.
 
void image_clear (image_t *p)
 Clear image (set all pixels to black)
 
image_t * image_new_copy (const image_t *source)
 Create a copy of an existing image.
 
char * image_print (const image_t *p, const char *palette)
 Print image as ASCII art (monochrome)
 
char * image_print_color (const image_t *p, const char *palette)
 Print image as ASCII art with color.
 
char * image_print_256color (const image_t *image, const char *palette)
 Print image using 256-color ANSI mode.
 
char * image_print_16color (const image_t *image, const char *palette)
 Print image using 16-color ANSI mode.
 
char * image_print_16color_dithered (const image_t *image, const char *palette)
 Print image using 16-color ANSI mode with dithering.
 
char * image_print_16color_dithered_with_background (const image_t *image, bool use_background, const char *palette)
 Print image using 16-color ANSI mode with dithering and background colors.
 
void quantize_color (int *r, int *g, int *b, int levels)
 Quantize color to specified number of levels.
 
void precalc_rgb_palettes (const float red, const float green, const float blue)
 Precalculate RGB palettes with color adjustment.
 
void image_resize (const image_t *source, image_t *dest)
 Resize image using nearest-neighbor interpolation.
 
void image_resize_interpolation (const image_t *source, image_t *dest)
 Resize image using bilinear interpolation.
 
char * rgb_to_ansi_fg (int r, int g, int b)
 Convert RGB to ANSI foreground color code.
 
char * rgb_to_ansi_bg (int r, int g, int b)
 Convert RGB to ANSI background color code.
 
void rgb_to_ansi_8bit (int r, int g, int b, int *fg_code, int *bg_code)
 Convert RGB to 8-bit ANSI color codes.
 

Variables

char ascii_palette []
 Default ASCII palette characters.
 
uint8_t dec3_t::len
 
char dec3_t::s [3]
 
dec3_t global_dec3_cache_t::dec3_table [256]
 
bool global_dec3_cache_t::dec3_initialized
 
global_dec3_cache_t g_dec3_cache
 Global decimal cache instance.
 
char g_default_luminance_palette [256]
 Default luminance palette (256 characters)
 
char * Str::data
 
size_t Str::len
 
size_t Str::cap
 
int RLEState::cFR
 
int RLEState::cFG
 
int RLEState::cFB
 
int RLEState::cBR
 
int RLEState::cBG
 
int RLEState::cBB
 
int RLEState::runLen
 
int RLEState::seeded
 
int ImageRGB::w
 
int ImageRGB::h
 
uint8_t * ImageRGB::pixels
 
double simd_benchmark_t::scalar_time
 
double simd_benchmark_t::sse2_time
 
double simd_benchmark_t::ssse3_time
 
double simd_benchmark_t::avx2_time
 
double simd_benchmark_t::neon_time
 
double simd_benchmark_t::sve_time
 
double simd_benchmark_t::speedup_best
 
const char * simd_benchmark_t::best_method
 
uint8_t utf8_char_t::width
 
uint8_t utf8_char_t::utf8_bytes [4]
 
uint8_t utf8_char_t::byte_len
 
uint8_t utf8_char_t::_pad
 
utf8_char_t utf8_palette_cache::cache [256]
 
utf8_char_t utf8_palette_cache::cache64 [64]
 
uint8_t utf8_palette_cache::char_index_ramp [256]
 
atomic_t utf8_palette_cache::last_access_time
 
atomic_t utf8_palette_cache::access_count
 
uint32_t utf8_palette_cache::total_age_seconds
 
uint64_t utf8_palette_cache::creation_time
 
double utf8_palette_cache::cached_score
 
size_t utf8_palette_cache::heap_index
 
uint32_t utf8_palette_cache::key
 
char utf8_palette_cache::palette_hash [256]
 
bool utf8_palette_cache::initialized
 
bool utf8_palette_cache::is_valid
 
UT_hash_handle utf8_palette_cache::hh
 

Luminance Calculation Constants

#define LUMA_RED   77
 Luminance red coefficient (0.299 * 256 = 77)
 
#define LUMA_GREEN   150
 Luminance green coefficient (0.587 * 256 = 150)
 
#define LUMA_BLUE   29
 Luminance blue coefficient (0.114 * 256 = 29)
 
#define LUMA_THRESHOLD   128
 Luminance threshold for rounding.
 

Detailed Description

🖼️ Everything necessary to turn pixels into text art

Implements a Matrix-inspired digital rain effect that can be applied to ASCII frames as a post-processing step. The effect creates the illusion of "falling" characters by modulating brightness in a sawtooth wave pattern.

ALGORITHM OVERVIEW:

The digital rain effect is based on the observation that Matrix code rain isn't actually falling characters - it's stationary characters whose brightness changes to create the illusion of rain. Key concepts:

  1. Sawtooth Wave: Each column has a brightness wave that repeats, creating multiple "raindrops" falling at different speeds.
  2. Cursor/Tracer: The bright leading character is detected where brightness increases (where the wave rises).
  3. Organic Variation: Random timing offsets and wobble functions prevent mechanical-looking patterns.

IMPLEMENTATION:

Note
This effect is applied AFTER ASCII conversion
Works with both color and monochrome ASCII frames
Minimal performance impact (O(frame_size) single pass)
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

This header provides comprehensive functions for converting images to ASCII art and outputting frames to the terminal. Supports multiple color modes, aspect ratio preservation, capability-aware conversion, and frame layout management.

CORE FEATURES:

CONVERSION MODES:

The system supports multiple conversion modes:

FRAME MANAGEMENT:

Frame management includes:

Note
All conversion functions return allocated strings that must be freed.
Terminal initialization functions must be matched with cleanup.
Capability-aware conversion is recommended for optimal results.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides AVX2 (Advanced Vector Extensions 2) optimized functions for converting images to ASCII art on modern x86-64 CPUs.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides SIMD-optimized functions for converting images to ASCII art. Supports multiple architectures including SSE2, SSSE3, AVX2 (x86), NEON (ARM), and SVE (ARM).

The interface provides:

Note
SIMD support is automatically detected and enabled based on CPU capabilities. CMake controls SIMD levels via ENABLE_SIMD_* options.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
October 2025

This header provides NEON (Advanced SIMD) optimized functions for converting images to ASCII art on ARM processors.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
August 2025

This header provides a dynamic output buffer system with automatic expansion and optimized ANSI escape sequence emission functions. The buffer is designed for efficient ASCII art frame construction and terminal output.

CORE FEATURES:

BUFFER MANAGEMENT:

The output buffer automatically expands when space is needed:

ANSI ESCAPE SEQUENCES:

The system emits ANSI escape sequences for:

COLOR MODE AUTO-SELECTION:

Color emission functions automatically select optimal mode:

Note
All buffer operations ensure null-terminated strings.
Buffer memory must be freed by caller using free() on buf pointer.
ANSI sequences are optimized for minimal terminal I/O.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This module provides functions for working with ANSI RLE (Run-Length Encoding) escape sequences. The REP sequence (CSI Ps b) repeats the preceding graphic character Ps times.

RLE FORMAT:

The ANSI REP sequence format is: ESC [ N b

Example: "A\x1b[3b" means "A" followed by 3 more "A"s = "AAAA"

USE CASES:

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
December 2025

This header provides scalar (non-SIMD) halfblock rendering functions for converting RGB images to ANSI-escaped halfblock characters with truecolor foreground and background colors.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

This header provides SSE2 (Streaming SIMD Extensions 2) optimized functions for converting images to ASCII art on x86-64 CPUs.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
August 2025

This header provides SSSE3 (Supplemental Streaming SIMD Extensions 3) optimized functions for converting images to ASCII art on x86-64 CPUs.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
August 2025

This header provides SVE (Scalable Vector Extension) optimized functions for converting images to ASCII art on ARM processors with SVE support.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
August 2025

This module provides color filtering capabilities to apply monochromatic color tints to grayscale video feeds. The server processes color filters; clients see each user in their chosen filter color.

FEATURES:

COLOR FILTERS:

0. none (no filtering)

  1. black (dark content on white background)
  2. white (white content on black background)
  3. green (#00FF41)
  4. magenta (#FF00FF)
  5. fuchsia (#FF00AA)
  6. orange (#FF8800)
  7. teal (#00DDDD)
  8. cyan (#00FFFF)
  9. pastel-pink (#FFB6C1)
  10. error-red (#FF3333)
  11. yellow (#FFEB99)

ARCHITECTURE:

USAGE:

// Convert image to grayscale and apply filter
if (filter != COLOR_FILTER_NONE) {
apply_color_filter(image, width, height, filter);
}
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.
@ COLOR_FILTER_NONE
No filtering (default)
Definition terminal.h:601

PROTOCOL INTEGRATION:

The color_filter field is included in the CLIENT_CAPABILITIES packet:

Note
All filter colors are applied using ITU-R BT.601 grayscale conversion
Filters automatically set –color-mode mono (monochromatic display)
"--color-filter foo --color-mode 256" is an error
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

This header provides comprehensive image data structures and operations for ascii-chat. Supports RGB images with standard and SIMD-optimized formats for efficient image processing and ASCII art conversion.

CORE FEATURES:

IMAGE FORMATS:

The system supports:

COLOR MODES:

ASCII conversion supports multiple color modes:

Note
All image allocation functions must be matched with corresponding destroy functions (image_destroy() or image_destroy_to_pool()).
ASCII conversion functions return allocated strings that must be freed by the caller.
Image structures are compatible with webcam capture (image_t).
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

video README

The video module converts RGB image data into ASCII art suitable for terminal display, with SIMD-optimized processing for real-time video conversion at 60+ FPS.

Overview

ascii-chat renders live video as ASCII art in the terminal by:

  1. Capturing RGB frames from webcam (1920x1080 or configured resolution)
  2. Scaling frames to terminal dimensions (typically 80x24 to 200x60)
  3. Converting RGB pixels to grayscale luminance
  4. Mapping luminance values to ASCII characters from a palette
  5. Optionally preserving color information via ANSI escape sequences
  6. Generating optimized ANSI output for terminal rendering
  7. Minimizing escape sequences to reduce bandwidth and rendering time

The module is highly optimized using SIMD instructions, achieving:

  • 60+ FPS for 1920x1080 → 160x45 conversion
  • <5% CPU usage on modern processors
  • Sub-millisecond conversion latency

Features

Rendering Modes:

  • Full-block mode: One character per pixel (standard resolution)
  • Half-block mode: Two pixels per character using ▀ and ▄ (2x vertical resolution)
  • Color mode: Preserves RGB color via ANSI 24-bit true color escape sequences
  • Monochrome mode: Grayscale ASCII art using luminance only

SIMD Acceleration:

  • SSE2: 16-byte parallel processing (x86_64 baseline)
  • SSSE3: Enhanced shuffle operations for pixel reordering
  • AVX2: 32-byte parallel processing (2x throughput vs SSE2)
  • NEON: 16-byte parallel processing (ARM/ARM64)
  • SVE: Scalable vector processing (ARM v8.2+)
  • Runtime CPU detection: Automatically selects best SIMD level

Grid Layouts:

  • Multi-client video grid (2x2, 3x3, up to 9 clients)
  • Automatic aspect ratio correction for each grid cell
  • Border characters between grid cells
  • Client ID labels for each video stream

Palette Support:

  • Customizable ASCII character sets
  • UTF-8 multi-byte character support
  • Built-in palettes: standard, dense, blocks, custom
  • Brightness-based character selection

Aspect Ratio Correction:

  • Terminal characters are ~2:1 height:width ratio
  • Automatic scaling to maintain proper aspect ratio
  • Prevents stretched/squished video

Rendering Pipeline

1. Image Scaling

Resize RGB image from webcam resolution to terminal dimensions:

  • Input: RGB24 image (e.g., 1920x1080 = 6,220,800 bytes)
  • Output: Scaled RGB24 image (e.g., 160x45 = 21,600 bytes)
  • Algorithm: Bilinear interpolation for smooth scaling
  • SIMD: Process 16 pixels at a time (SSE2) or 32 pixels (AVX2)
  • Performance: ~200 µs for 1920x1080 → 160x45 on modern CPU

2. Brightness Calculation

Convert RGB pixels to luminance (grayscale):

  • Formula: Y = 0.299*R + 0.587*G + 0.114*B (BT.601 standard)
  • SIMD: Process 16 RGB triplets in parallel
  • Input: RGB24 array (3 bytes per pixel)
  • Output: Y8 array (1 byte per pixel)
  • Performance: ~50 µs for 160x45 image

SIMD implementation (SSE2 example):

// Load 16 RGB pixels (48 bytes)
__m128i r = _mm_loadu_si128((__m128i*)(rgb + 0)); // R values
__m128i g = _mm_loadu_si128((__m128i*)(rgb + 16)); // G values
__m128i b = _mm_loadu_si128((__m128i*)(rgb + 32)); // B values
// Apply BT.601 coefficients (0.299, 0.587, 0.114)
__m128i y = _mm_add_epi16(
_mm_mullo_epi16(r, _mm_set1_epi16(77)), // 0.299 * 256 ≈ 77
_mm_add_epi16(
_mm_mullo_epi16(g, _mm_set1_epi16(150)), // 0.587 * 256 ≈ 150
_mm_mullo_epi16(b, _mm_set1_epi16(29)) // 0.114 * 256 ≈ 29
)
);
// Shift right by 8 to get final Y values
y = _mm_srli_epi16(y, 8);

3. Palette Mapping

Map brightness values to ASCII characters:

  • Input: Luminance value (0-255)
  • Output: ASCII character from palette
  • Palette example: " .:-=+*#%@" (10 characters, dark to bright)
  • Mapping: luminance / (256 / palette_length)
  • SIMD: Lookup table with SSSE3 shuffle instruction

Half-block rendering uses special logic:

  • Combines two vertically adjacent pixels into one character
  • Uses ▀ (upper half block), ▄ (lower half block), or █ (full block)
  • Effectively doubles vertical resolution
  • Example: 80x48 terminal displays 80x96 effective resolution

4. ANSI Escape Sequence Generation

Generate optimized ANSI sequences for terminal:

  • Color changes only when pixel color changes (stateful tracking)
  • Cursor movement optimized (relative vs absolute positioning)
  • Typical escape sequence: \x1b[38;2;R;G;Bm (24-bit color)
  • Output buffering to minimize write() syscalls
  • Pre-allocated buffer pool to avoid malloc overhead

Optimization techniques:

  • Delta encoding: Only emit changes from previous frame
  • Run-length encoding: Combine consecutive identical characters
  • Color caching: Remember last foreground/background color
  • Escape sequence caching: Pre-compute common sequences

Example output:

\x1b[38;2;255;128;64m###\x1b[38;2;200;100;50m***\x1b[38;2;150;75;37m+++

5. Terminal Output

Write ANSI output to terminal:

  • Single write() call per frame (buffered output)
  • Double-buffering: Compose frame while displaying previous
  • Cursor hiding during frame update (reduces flicker)
  • Cursor positioning to (1,1) for each frame
  • Terminal raw mode for direct output control

SIMD vs Scalar Implementations

ascii-chat provides both scalar (non-SIMD) and SIMD implementations of video conversion, with automatic runtime selection based on CPU capabilities.

Scalar (Non-SIMD) Implementation

Purpose: Fallback for processors without SIMD support, reference implementation

File: lib/video/scalar/halfblock.c

Algorithm:

  • Processes one source pixel at a time
  • No vector instructions - standard integer arithmetic
  • Suitable for embedded systems and older CPUs

Performance Characteristics:

  • Single-threaded throughput: ~15 FPS for 1920x1080 → 160x45
  • Latency: 66 ms per frame (16.7 ms target for 60 FPS)
  • CPU usage: ~25% on single core (limited by core speed)
  • Memory bandwidth: Not optimized (scalar loads dominate)
  • Cache efficiency: Moderate (sequential memory access)

When to Use:

  • Baseline architecture where SIMD unavailable
  • Testing and validation (reference implementation)
  • Debugging (easier to trace scalar logic)
  • Embedded systems with no vector support

Trade-offs:

  • Advantage: Maximal portability, simpler code
  • Disadvantage: ~4-8x slower than SIMD, unsuitable for real-time video

SIMD vs Scalar Implementations

SSE2 (x86_64 Baseline)

Capabilities:

  • 128-bit vector registers (XMM0-XMM15)
  • Process 16 bytes or 8 int16 or 4 int32 simultaneously
  • Integer arithmetic, logic, compare operations
  • Available on all x86_64 CPUs

Performance:

  • RGB to luminance: ~4x speedup vs scalar
  • Palette lookup: ~8x speedup vs scalar
  • Frame conversion: ~60 FPS for 1920x1080 → 160x45

SSSE3 (Supplemental SSE3)

Additional capabilities beyond SSE2:

  • PSHUFB: Arbitrary byte shuffle within 128-bit vector
  • Enables fast palette lookup via table shuffle
  • Horizontal operations for pixel combining

Performance improvement:

  • Palette lookup: ~12x speedup vs scalar (1.5x vs SSE2)
  • Reduced instruction count for complex permutations

AVX2 (Advanced Vector Extensions 2)

Capabilities:

  • 256-bit vector registers (YMM0-YMM15)
  • Process 32 bytes or 16 int16 or 8 int32 simultaneously
  • Double throughput vs SSE2 for same operations
  • Available on Intel Haswell+ (2013), AMD Excavator+ (2015)

Performance:

  • RGB to luminance: ~8x speedup vs scalar (2x vs SSE2)
  • Frame conversion: ~90 FPS for 1920x1080 → 160x45
  • Reduced loop overhead (process 2x data per iteration)

NEON (ARM)

Capabilities:

  • 128-bit vector registers (Q0-Q15 or D0-D31)
  • Process 16 bytes or 8 int16 or 4 int32 simultaneously
  • Similar performance to SSE2 on equivalent clock speeds
  • Available on ARM Cortex-A series, Apple Silicon

Performance on Apple M1:

  • RGB to luminance: ~6x speedup vs scalar
  • Frame conversion: ~120 FPS for 1920x1080 → 160x45 (higher clock)

SVE (ARM Scalable Vector Extension)

Capabilities:

  • Scalable vectors: 128-bit to 2048-bit (implementation defined)
  • Future-proof: Code runs on any SVE vector length
  • Predicate registers for advanced masking
  • Available on ARM v8.2+ (AWS Graviton3, Fujitsu A64FX)

Performance potential:

  • 512-bit vectors: ~4x SSE2 throughput
  • Automatically scales with future CPU generations

Runtime CPU Detection

ascii-chat automatically detects and uses the best SIMD level:

// At startup, detect CPU capabilities
if (cpu_has_avx2()) {
ascii_convert = ascii_convert_avx2; // Best performance
} else if (cpu_has_ssse3()) {
ascii_convert = ascii_convert_ssse3;
} else if (cpu_has_sse2()) {
ascii_convert = ascii_convert_sse2; // Baseline x86_64
} else if (cpu_has_neon()) {
ascii_convert = ascii_convert_neon; // ARM
} else {
ascii_convert = ascii_convert_scalar; // Fallback
}
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.
Definition ascii.c:111

Usage Examples

Basic Conversion

// Initialize ASCII converter
ascii_context_t ascii_ctx;
ascii_config_t config = {
.width = 160,
.height = 45,
.palette = " .:-=+*#%@",
.color = true,
.half_blocks = false
};
ascii_init(&ascii_ctx, &config);
// Convert RGB frame to ASCII
uint8_t *rgb_data = ...; // From webcam (1920x1080 RGB24)
char *ascii_output;
size_t output_len;
ascii_convert(&ascii_ctx, rgb_data, &ascii_output, &output_len);
// Write to terminal
write(STDOUT_FILENO, "\x1b[2J\x1b[H", 7); // Clear screen, home cursor
write(STDOUT_FILENO, ascii_output, output_len);
// Cleanup
ascii_destroy(&ascii_ctx);
unsigned char uint8_t
Definition common.h:56

Half-Block Mode

// Half-block mode for 2x vertical resolution
ascii_config_t config = {
.width = 80,
.height = 48, // Will show 80x96 effective resolution
.palette = " ▀▄█", // Special half-block characters
.color = true,
.half_blocks = true
};
ascii_init(&ascii_ctx, &config);
// Conversion combines vertical pixel pairs
ascii_convert(&ascii_ctx, rgb_data, &ascii_output, &output_len);

Multi-Client Grid

// 2x2 grid for 4 clients
ascii_grid_config_t grid_config = {
.grid_width = 2,
.grid_height = 2,
.cell_width = 80,
.cell_height = 24,
.border_char = '|',
.show_labels = true
};
ascii_grid_t grid;
ascii_grid_init(&grid, &grid_config);
// Render each client's frame to grid cell
for (int i = 0; i < 4; i++) {
ascii_grid_set_cell(&grid, i, client_frames[i], client_labels[i]);
}
// Composite final grid output
char *grid_output;
size_t grid_len;
ascii_grid_render(&grid, &grid_output, &grid_len);
write(STDOUT_FILENO, grid_output, grid_len);
ascii_grid_destroy(&grid);

Optimization Strategy

ascii-chat uses a multi-layered optimization approach to achieve real-time video conversion:

Architecture-Level Optimization

SIMD Instruction Selection:

  • Single implementation compiled per target (not runtime branches within loops)
  • Avoids branch prediction penalties in hot loops
  • CMake selects highest available SIMD level at build time

Build-Time vs Runtime Selection:

Build Time: CMake detects target CPU → Selects SIMD level → Compiles one variant
Runtime: CPU detection validates assumptions → Falls back if needed
Result: Optimal code without runtime dispatch overhead

Algorithm-Level Optimization

Parallel Data Processing:

  • SSE2/NEON: Process 16 bytes per instruction (16 RGB values or 4 RGBA pixels)
  • AVX2: Process 32 bytes per instruction (32 luminance values)
  • Reduces loop iterations by 4-8x vs scalar

Color Caching:

  • UTF-8 palette cache: 256 entries for monochrome, 64 for color
  • Eviction algorithm: LRU with frequency/recency scoring
  • ~5-10 MB cache to avoid repeated string lookups

ANSI Escape Sequence Optimization:

  • Stateful color tracking: Only emit color change when needed
  • Run-length encoding: Combine consecutive identical characters
  • Pre-allocated buffers avoid malloc per frame

Memory Hierarchy Optimization

Cache Line Awareness:

  • Process data in 64-byte chunks (typical L1 cache line)
  • Bilinear scaling: Process 2x2 pixel blocks with temporal locality
  • Luminance conversion: Sequential access patterns

Zero-Copy Techniques:

  • Webcam frames returned as direct memory pointers
  • Output buffers pre-allocated in buffer pool
  • Avoid malloc/memcpy in hot loops

SIMD Register Utilization:

  • Use all available vector registers (XMM0-15 for SSE2, YMM0-15 for AVX2)
  • Instruction-level parallelism: Multiple independent operations per cycle
  • Minimize register spills to stack

Spatial Locality

Bilinear Scaling: Process pixels in raster order

  • Sequential reads from source image
  • Sequential writes to scaled output
  • Prefetcher-friendly access patterns

Luminance Conversion: Stream processing

  • Read RGB triplets sequentially
  • Compute Y value immediately
  • Write Y value to output
  • Cache misses minimized by streaming pattern

Performance Benchmarks

Measured on Intel i7-10700K @ 3.8 GHz, 1920x1080 → 160x45 conversion:

Implementation FPS Latency CPU % Speedup vs Scalar
Scalar 15 66 ms 25% 1x (baseline)
SSE2 60 16 ms 6% 4x
SSSE3 75 13 ms 5% 5x
AVX2 90 11 ms 4% 6x

Measured on Apple M1 (ARM NEON):

  • NEON: 120 FPS, 8 ms latency, 5% CPU (8x speedup vs scalar)

Performance Analysis:

  • Scalar implementation bottlenecked by memory bandwidth (~25% CPU from single core)
  • SSE2 achieves 4x speedup with 16-byte parallel processing
  • AVX2 approaches 6x speedup limited by memory system (not ALU bound)
  • 60 FPS target achievable on all SIMD targets with <10% single-core CPU

Memory usage:

  • RGB frame buffer: 6 MB (1920x1080x3)
  • Scaled frame: 21 KB (160x45x3)
  • ASCII output: ~50 KB (with ANSI color codes)
  • Total: ~6.1 MB per frame

Performance Trade-offs Analysis

Scalar vs SIMD Trade-offs

Scalar Advantages:

  • Universal portability (any CPU)
  • Small binary size (<50 KB)
  • No CPU detection overhead
  • Easier to debug (serial execution model)

**Scalar Disadvantages**:

  • 4-8x slower than SIMD on typical CPUs
  • ~25% single-core CPU at 15 FPS (unsustainable for real-time)
  • Memory bandwidth underutilized
  • Not suitable for production video chat

**Decision Rationale**:

  • ascii-chat targets modern CPUs (post-2005) where SIMD is standard
  • Scalar kept as fallback for: testing, validation, exotic platforms
  • Production paths use SIMD to meet real-time requirements

SIMD Level Trade-offs

Level Year Speedup vs SSE2 Availability Recommendation
Scalar N/A 0.25x 100% Fallback only
SSE2 2001 1x (baseline) 99% x86_64 default
SSSE3 2006 1.25x 95% Nice-to-have
AVX2 2013 1.5x 80% Best for x86
NEON 2007 1.3-2x 100% ARM ARM default
SVE 2017 2-4x 5% Future use

Architecture Selection Logic:

At build time, CMake checks:
1. Target architecture (x86_64, ARM, ARM64, PowerPC)
2. Compiler support for intrinsics
3. Optional: Runtime detection preference
Best-fit selection:
- x86_64: Default SSE2 (baseline), enable AVX2 if compiler supports
- ARM: NEON (baseline for 32-bit), SVE if available
- Fallback: Always provide scalar for maximum compatibility

Compilation Strategy:

  • Single SIMD variant compiled per build (no runtime dispatch)
  • Avoids branch prediction penalty in hot loops
  • Smaller binary: Only one code path compiled
  • Cleaner CPU-specific code paths

Latency vs Throughput

64 FPS at 1920x1080:

  • Target latency: ~16 ms per frame (16.7 ms budget)
  • Scalar: 66 ms (4x over budget - unsuitable)
  • SSE2: 16 ms (meets target)
  • AVX2: 11 ms (headroom for other processing)

Strategy:

  • SIMD essential to stay within real-time deadline
  • Headroom (11 ms on AVX2) allows for:
    • Webcam capture (~1-2 ms)
    • Network transmission (~5-10 ms)
    • Terminal rendering (~2-3 ms)

Power Efficiency

Energy Per Frame:

Implementation Frames/sec Power Energy/Frame Relative
Scalar 15 FPS 25W 1.67 J 1.0x
SSE2 60 FPS 6W 0.10 J 0.06x
AVX2 90 FPS 4W 0.04 J 0.03x

Analysis:

  • SIMD dramatically reduces energy/frame (16-25x improvement)
  • Enables battery operation on mobile devices
  • Reduces thermal load and cooling requirements

Terminal Compatibility

ascii-chat supports various terminal features:

True Color (24-bit RGB):

  • Supported: iTerm2, Alacritty, Windows Terminal, GNOME Terminal 3.16+
  • Escape sequence: \x1b[38;2;R;G;Bm (foreground), \x1b[48;2;R;G;Bm (background)
  • Falls back to 256-color if true color unavailable

256-Color Mode:

  • Supported: Most modern terminals
  • Escape sequence: \x1b[38;5;Nm where N is 0-255
  • Color palette approximation for RGB values

UTF-8 Support:

  • Required for half-block characters (▀▄█)
  • Required for custom Unicode palettes
  • Automatic detection via $LANG and $LC_ALL

Terminal Size Detection:

  • Uses TIOCGWINSZ ioctl on POSIX (lib/platform/posix/terminal.c)
  • Uses GetConsoleScreenBufferInfo on Windows (lib/platform/windows/terminal.c)
  • Falls back to $COLUMNS and $LINES environment variables
  • Default: 80x24 if detection fails
See also
video/ascii.h
video/ansi_fast.h
video/simd/ascii_simd.h
video/output_buffer.h
palette.h

Macro Definition Documentation

◆ ANSI_BG_PREFIX

#define ANSI_BG_PREFIX   "\033[48;2;"

#include <ascii.h>

ANSI background color prefix (truecolor mode)

ANSI escape sequence prefix for 24-bit truecolor background colors. Format: ESC[48;2;r;g;bm (where r, g, b are RGB values).

Note
Use with ANSI_COLOR_SUFFIX to form complete escape sequence.
Example: ANSI_BG_PREFIX "255;0;0" ANSI_COLOR_SUFFIX (red background).

Definition at line 454 of file ascii.h.

◆ ANSI_COLOR_SUFFIX

#define ANSI_COLOR_SUFFIX   "m"

#include <ascii.h>

ANSI color suffix.

ANSI escape sequence suffix for color escape sequences. Used with ANSI_FG_PREFIX or ANSI_BG_PREFIX to form complete escape sequences.

Note
Format: prefix + RGB + suffix = complete ANSI sequence.
Example: "\033[38;2;255;0;0m" (red foreground).

Definition at line 468 of file ascii.h.

◆ ANSI_FG_PREFIX

#define ANSI_FG_PREFIX   "\033[38;2;"

#include <ascii.h>

ANSI foreground color prefix (truecolor mode)

ANSI escape sequence prefix for 24-bit truecolor foreground colors. Format: ESC[38;2;r;g;bm (where r, g, b are RGB values).

Note
Use with ANSI_COLOR_SUFFIX to form complete escape sequence.
Example: ANSI_FG_PREFIX "255;0;0" ANSI_COLOR_SUFFIX (red foreground).

Definition at line 441 of file ascii.h.

◆ ascii_clear_screen

#define ascii_clear_screen (   fd)    terminal_clear_screen()

#include <ascii.h>

Clear terminal screen.

Parameters
fdFile descriptor for terminal

Clears terminal screen without moving cursor. Equivalent to terminal_clear_screen().

Definition at line 529 of file ascii.h.

◆ ASCII_LUMINANCE_LEVELS

#define ASCII_LUMINANCE_LEVELS   256

#include <ascii.h>

Number of luminance levels supported (256)

Number of luminance levels used for character mapping. Each pixel's brightness value (0-255) maps to a character in the luminance palette.

Note
256 levels correspond to 8-bit pixel brightness values.
Luminance palette must have 256 elements.

Definition at line 410 of file ascii.h.

◆ ASCII_SLEEP_NS

#define ASCII_SLEEP_NS   50000L

#include <ascii.h>

Sleep duration in nanoseconds between frames (50000 ns = 50 μs)

Sleep duration between frame outputs for frame rate limiting. Used to prevent excessive terminal I/O and control frame rate.

Note
50000 nanoseconds = 50 microseconds = 0.05 milliseconds
Used for frame rate limiting in video playback.

Definition at line 423 of file ascii.h.

◆ ascii_zzz

#define ascii_zzz ( )    time_sleep_ns(ASCII_SLEEP_NS)

#include <ascii.h>

Sleep for frame rate limiting.

Sleeps for a short duration (50 microseconds) to limit frame rate and prevent excessive terminal I/O. Uses time_sleep_ns() for precise nanosecond-precision timing control with the unified timing API.

Note
Sleep duration: 50000 nanoseconds (50 microseconds)
Used between frame outputs for rate limiting.

Definition at line 548 of file ascii.h.

◆ CACHE_FREQUENCY_DECAY_TIME

#define CACHE_FREQUENCY_DECAY_TIME   10000

#include <common.h>

Definition at line 463 of file video/ascii/common.h.

◆ CACHE_MAX_LIFETIME

#define CACHE_MAX_LIFETIME   3600

#include <common.h>

Definition at line 465 of file video/ascii/common.h.

◆ CACHE_RECENCY_SCALE

#define CACHE_RECENCY_SCALE   100

#include <common.h>

Definition at line 464 of file video/ascii/common.h.

◆ console_clear

#define console_clear (   fd)    (terminal_clear_screen(), terminal_cursor_home(fd))

#include <ascii.h>

Clear console and move cursor to home position.

Parameters
fdFile descriptor for terminal

Clears terminal screen and moves cursor to home position (top-left). Equivalent to terminal_clear_screen() followed by terminal_cursor_home().

Definition at line 507 of file ascii.h.

◆ cursor_reset

#define cursor_reset (   fd)    terminal_cursor_home(fd)

#include <ascii.h>

Reset cursor to home position.

Parameters
fdFile descriptor for terminal

Moves cursor to home position (top-left, row 1, column 1). Equivalent to terminal_cursor_home().

Definition at line 518 of file ascii.h.

◆ DIGITAL_RAIN_DEFAULT_ANIMATION_SPEED

#define DIGITAL_RAIN_DEFAULT_ANIMATION_SPEED   1.0f

#include <digital_rain.h>

Default animation speed multiplier.

Overall animation speed. 1.0 = normal speed.

Definition at line 128 of file digital_rain.h.

◆ DIGITAL_RAIN_DEFAULT_BRIGHTNESS_DECAY

#define DIGITAL_RAIN_DEFAULT_BRIGHTNESS_DECAY   0.1f

#include <digital_rain.h>

Default brightness decay factor.

Controls how quickly brightness transitions occur (0-1). Higher values = more immediate transitions, lower = smoother.

Definition at line 121 of file digital_rain.h.

◆ DIGITAL_RAIN_DEFAULT_COLOR_B

#define DIGITAL_RAIN_DEFAULT_COLOR_B   80

#include <digital_rain.h>

Definition at line 137 of file digital_rain.h.

◆ DIGITAL_RAIN_DEFAULT_COLOR_G

#define DIGITAL_RAIN_DEFAULT_COLOR_G   255

#include <digital_rain.h>

Definition at line 136 of file digital_rain.h.

◆ DIGITAL_RAIN_DEFAULT_COLOR_R

#define DIGITAL_RAIN_DEFAULT_COLOR_R   0

#include <digital_rain.h>

Default Matrix green color (R, G, B)

Classic Matrix green: (0, 255, 80)

Definition at line 135 of file digital_rain.h.

◆ DIGITAL_RAIN_DEFAULT_CURSOR_BRIGHTNESS

#define DIGITAL_RAIN_DEFAULT_CURSOR_BRIGHTNESS   2.0f

#include <digital_rain.h>

Cursor brightness multiplier.

How much brighter the cursor should be compared to regular characters.

Definition at line 144 of file digital_rain.h.

◆ DIGITAL_RAIN_DEFAULT_FALL_SPEED

#define DIGITAL_RAIN_DEFAULT_FALL_SPEED   3.0f

#include <digital_rain.h>

Default fall speed for raindrops.

Controls how fast the brightness wave moves down each column. Higher values = faster falling rain.

Definition at line 105 of file digital_rain.h.

◆ DIGITAL_RAIN_DEFAULT_RAINDROP_LENGTH

#define DIGITAL_RAIN_DEFAULT_RAINDROP_LENGTH   12.0f

#include <digital_rain.h>

Default raindrop length.

Controls the length of each raindrop in grid cells. Smaller values = shorter, more frequent raindrops.

Definition at line 113 of file digital_rain.h.

◆ IMAGE_MAX_HEIGHT

#define IMAGE_MAX_HEIGHT   2160

#include <image.h>

Maximum image height (4K resolution)

Maximum supported image height in pixels. Set to 2160 pixels (4K UHD height) to support high-resolution video capture.

Note
Larger images may exceed memory limits.
Video pipeline may enforce lower limits for performance.

Definition at line 179 of file video/rgba/image.h.

◆ IMAGE_MAX_PIXELS_SIZE

#define IMAGE_MAX_PIXELS_SIZE   (IMAGE_MAX_WIDTH * IMAGE_MAX_HEIGHT * sizeof(rgb_pixel_t))

#include <image.h>

Maximum pixel data size in bytes.

Maximum size in bytes for pixel data array. Calculated as: IMAGE_MAX_WIDTH * IMAGE_MAX_HEIGHT * sizeof(rgb_pixel_t) Used for buffer allocation and validation.

Note
This is approximately 24.88 MB for 4K RGB images.
Actual memory usage may be higher due to alignment.

Definition at line 193 of file video/rgba/image.h.

◆ IMAGE_MAX_WIDTH

#define IMAGE_MAX_WIDTH   3840

#include <image.h>

Maximum image width (4K resolution)

Maximum supported image width in pixels. Set to 3840 pixels (4K UHD width) to support high-resolution video capture.

Note
Larger images may exceed memory limits.
Video pipeline may enforce lower limits for performance.

Definition at line 166 of file video/rgba/image.h.

◆ LUMA_BLUE

#define LUMA_BLUE   29

#include <common.h>

Luminance blue coefficient (0.114 * 256 = 29)

Definition at line 84 of file video/ascii/common.h.

◆ LUMA_GREEN

#define LUMA_GREEN   150

#include <common.h>

Luminance green coefficient (0.587 * 256 = 150)

Definition at line 82 of file video/ascii/common.h.

◆ LUMA_RED

#define LUMA_RED   77

#include <common.h>

Luminance red coefficient (0.299 * 256 = 77)

Definition at line 80 of file video/ascii/common.h.

◆ LUMA_THRESHOLD

#define LUMA_THRESHOLD   128

#include <common.h>

Luminance threshold for rounding.

Definition at line 86 of file video/ascii/common.h.

◆ print

#define print (   s)    fwrite(s, 1, sizeof(s) / sizeof((s)[0]), stdout)

#include <ascii.h>

Print string to stdout.

Parameters
sString literal to print

Utility macro for printing string literals to stdout. Uses fwrite() for efficient output without null terminator requirement.

Note
This is a macro that expands to fwrite() call.
Only works with string literals (compile-time known size).
Use fputs() or printf() for runtime strings.

Definition at line 488 of file ascii.h.

◆ RAMP64_SIZE

#define RAMP64_SIZE   64

#include <common.h>

UTF-8 character palette cache.

Definition at line 462 of file video/ascii/common.h.

Typedef Documentation

◆ utf8_palette_cache_t

Enumeration Type Documentation

◆ image_alloc_method_t

#include <image.h>

Pixel allocation method tracking.

Tracks which allocation method was used for image pixels to ensure correct deallocation. Must match the allocation method used when creating the image.

Enumerator
IMAGE_ALLOC_SIMD 

Pixels allocated with SAFE_MALLOC_SIMD()

IMAGE_ALLOC_POOL 

Pixels allocated with buffer_pool_alloc()

Definition at line 118 of file video/rgba/image.h.

118 {
119 IMAGE_ALLOC_SIMD = 0,
image_alloc_method_t
Pixel allocation method tracking.
@ IMAGE_ALLOC_POOL
Pixels allocated with buffer_pool_alloc()
@ IMAGE_ALLOC_SIMD
Pixels allocated with SAFE_MALLOC_SIMD()

Function Documentation

◆ alloc_image()

ImageRGB alloc_image ( int  w,
int  h 
)

#include <common.h>

Allocate a new ImageRGB (RGB8 format)

Parameters
wImage width
hImage height
Returns
Allocated ImageRGB structure
Note
Aborts on out-of-memory condition.

◆ ansi_compress_rle()

char * ansi_compress_rle ( const char *  input,
size_t  input_len 
)

#include <rle.h>

Compress repeated characters using RLE escape sequences.

Parameters
inputInput string (may contain ANSI escape codes)
input_lenLength of input string
Returns
Newly allocated string with RLE compression (caller must free)

Compresses runs of repeated printable characters using ANSI RLE sequences. Only compresses when profitable (run length > RLE overhead). Preserves all existing escape sequences.

Note
The returned string must be freed by the caller using free().
Returns NULL if input is NULL or allocation fails.
Only compresses runs where RLE saves bytes (typically 4+ chars).
Example
const char *frame = "AAAAAAA\x1b[31mBBBBB\n";
char *compressed = ansi_compress_rle(frame, strlen(frame));
// compressed = "A\x1b[6b\x1b[31mB\x1b[4b\n"
free(compressed);
char * ansi_compress_rle(const char *input, size_t input_len)
Compress repeated characters using RLE escape sequences.
Definition rle.c:98
int frame
Definition splash.c:99

Definition at line 98 of file rle.c.

98 {
99 if (!input || input_len == 0) {
100 return NULL;
101 }
102
103 outbuf_t ob = {0};
104 ob_reserve(&ob, input_len);
105
106 size_t i = 0;
107 while (i < input_len) {
108 // Check for ESC character (start of ANSI sequence)
109 if (input[i] == '\x1b' && i + 1 < input_len && input[i + 1] == '[') {
110 size_t seq_start = i;
111 i += 2; // Skip ESC[
112
113 // Skip parameter bytes
114 while (i < input_len && ((input[i] >= '0' && input[i] <= '9') || input[i] == ';')) {
115 i++;
116 }
117
118 // Skip final byte
119 if (i < input_len) {
120 i++;
121 }
122
123 // Copy entire escape sequence as-is
124 ob_write(&ob, input + seq_start, i - seq_start);
125 } else {
126 // Regular character - check for runs
127 char c = input[i];
128
129 // Only compress printable characters (not newlines, not control chars)
130 if (c >= 0x20 && c != 0x7F) {
131 // Count run length
132 size_t run_len = 1;
133 i++;
134
135 while (i < input_len && input[i] == c) {
136 run_len++;
137 i++;
138 }
139
140 // Emit first character
141 ob_putc(&ob, c);
142
143 // Use RLE if profitable (run > overhead of ESC[Nb)
144 if (run_len > 1 && rep_is_profitable((uint32_t)run_len)) {
145 emit_rep(&ob, (uint32_t)(run_len - 1));
146 } else {
147 // Emit remaining characters directly
148 for (size_t k = 1; k < run_len; k++) {
149 ob_putc(&ob, c);
150 }
151 }
152 } else {
153 // Non-compressible character (newline, etc.)
154 ob_putc(&ob, c);
155 i++;
156 }
157 }
158 }
159
160 ob_term(&ob);
161 return ob.buf;
162}
unsigned int uint32_t
Definition common.h:58
void ob_term(outbuf_t *ob)
Append null terminator to buffer.
void ob_putc(outbuf_t *ob, char c)
Append a character to buffer.
bool rep_is_profitable(uint32_t runlen)
Check if run-length encoding is profitable.
void ob_reserve(outbuf_t *ob, size_t need)
Reserve buffer space for upcoming writes.
void emit_rep(outbuf_t *ob, uint32_t extra)
Emit run-length encoded sequence.
void ob_write(outbuf_t *ob, const char *s, size_t n)
Append a string to buffer.
Dynamic output buffer (auto-expanding)
char * buf
Buffer pointer (allocated, owned by caller, must be freed)

References outbuf_t::buf, emit_rep(), ob_putc(), ob_reserve(), ob_term(), ob_write(), and rep_is_profitable().

◆ ansi_expand_rle()

char * ansi_expand_rle ( const char *  input,
size_t  input_len 
)

#include <rle.h>

Expand RLE escape sequences in a string.

Parameters
inputInput string containing ANSI escape codes
input_lenLength of input string
Returns
Newly allocated string with RLE expanded (caller must free)

Expands ANSI RLE (REP) sequences while preserving all other escape codes:

  • RLE sequences (ESC[Nb) are expanded: previous character repeated N times
  • All other escape sequences (colors, cursor, etc.) are preserved as-is

This is useful when outputting to files/pipes where terminals can't interpret RLE, but you still want to preserve color codes.

Note
The returned string must be freed by the caller using free().
Returns NULL if input is NULL or allocation fails.
Example
const char *frame = "A\x1b[3b\x1b[31mB\n"; // A + repeat 3 + red + B
char *expanded = ansi_expand_rle(frame, strlen(frame));
// expanded = "AAAA\x1b[31mB\n" (RLE expanded, color preserved)
free(expanded);
char * ansi_expand_rle(const char *input, size_t input_len)
Expand RLE escape sequences in a string.
Definition rle.c:13

Definition at line 13 of file rle.c.

13 {
14 if (!input || input_len == 0) {
15 return NULL;
16 }
17
18 // RLE expansion can make output larger than input, start with 2x capacity
19 outbuf_t ob = {0};
20 ob_reserve(&ob, input_len * 2);
21
22 // Track the last character/grapheme for RLE expansion
23 // UTF-8 characters can be up to 4 bytes
24 char last_char[5] = " ";
25 size_t last_char_len = 1;
26
27 size_t i = 0;
28 while (i < input_len) {
29 // Check for ESC character (start of ANSI sequence)
30 if (input[i] == '\x1b' && i + 1 < input_len && input[i + 1] == '[') {
31 size_t seq_start = i;
32 i += 2; // Skip ESC[
33
34 // Parse parameter bytes (digits and semicolons)
35 uint32_t param = 0;
36 while (i < input_len && ((input[i] >= '0' && input[i] <= '9') || input[i] == ';')) {
37 if (input[i] >= '0' && input[i] <= '9') {
38 param = param * 10 + (uint32_t)(input[i] - '0');
39 } else if (input[i] == ';') {
40 param = 0; // Reset for next parameter
41 }
42 i++;
43 }
44
45 // Check for final byte
46 if (i < input_len) {
47 char final_byte = input[i];
48 i++;
49
50 // Handle RLE: ESC[Nb repeats previous character N times
51 if (final_byte == 'b' && param > 0) {
52 for (uint32_t r = 0; r < param; r++) {
53 ob_write(&ob, last_char, last_char_len);
54 }
55 } else {
56 // Not RLE - copy the entire escape sequence as-is
57 ob_write(&ob, input + seq_start, i - seq_start);
58 }
59 }
60 } else {
61 // Regular character - copy to output and track for RLE
62 unsigned char c = (unsigned char)input[i];
63 size_t char_len = 1;
64
65 // Determine UTF-8 character length from first byte
66 if ((c & 0x80) == 0) {
67 char_len = 1; // ASCII
68 } else if ((c & 0xE0) == 0xC0) {
69 char_len = 2; // 2-byte UTF-8
70 } else if ((c & 0xF0) == 0xE0) {
71 char_len = 3; // 3-byte UTF-8
72 } else if ((c & 0xF8) == 0xF0) {
73 char_len = 4; // 4-byte UTF-8
74 }
75
76 // Make sure we don't read past end of input
77 if (i + char_len > input_len) {
78 char_len = input_len - i;
79 }
80
81 // Copy full UTF-8 character to output
82 ob_write(&ob, input + i, char_len);
83
84 // Track last printable character for RLE (skip control chars)
85 if (c >= 0x20 && c != 0x7F) {
86 SAFE_MEMCPY(last_char, sizeof(last_char), input + i, char_len);
87 last_char[char_len] = '\0';
88 last_char_len = char_len;
89 }
90 i += char_len;
91 }
92 }
93
94 ob_term(&ob);
95 return ob.buf;
96}
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:468

References outbuf_t::buf, ob_reserve(), ob_term(), ob_write(), and SAFE_MEMCPY.

◆ apply_color_filter()

int apply_color_filter ( uint8_t *  pixels,
uint32_t  width,
uint32_t  height,
uint32_t  stride,
color_filter_t  filter,
float  time 
)

#include <color_filter.h>

Apply color filter to entire image in-place.

Parameters
pixelsImage pixel buffer (modified in-place)
widthImage width in pixels
heightImage height in pixels
strideBytes per row (typically width * 3 for RGB24)
filterColor filter to apply (COLOR_FILTER_NONE = no-op)
timeCurrent time in seconds (used for COLOR_FILTER_RAINBOW animation)
Returns
0 on success, -1 on error (invalid parameters)

Applies the specified color filter to an RGB image. The operation:

  1. Converts each RGB pixel to grayscale (ITU-R BT.601)
  2. Scales grayscale by the filter's RGB color values
  3. Modifies pixels in-place

For "color on white" mode (black-on-white only): Dark pixels get full color, light pixels become white

For "white on color" mode (all other filters): Grayscale value scales the color intensity

For COLOR_FILTER_RAINBOW: Uses time parameter to cycle through RGB spectrum (3.5s period)

Note
This function modifies the image in-place
If filter == COLOR_FILTER_NONE, this is a no-op
Expected stride is typically width * 3 for RGB24 format

Definition at line 274 of file color_filter.c.

275 {
276 if (!pixels || width == 0 || height == 0 || stride == 0) {
277 return -1;
278 }
279
280 if (filter == COLOR_FILTER_NONE) {
281 return 0; // No-op for none filter
282 }
283
284 // Special handling for rainbow filter
285 if (filter == COLOR_FILTER_RAINBOW) {
286 uint8_t rainbow_r, rainbow_g, rainbow_b;
287 color_filter_calculate_rainbow(time, &rainbow_r, &rainbow_g, &rainbow_b);
288
289 // Create temporary filter def with current rainbow color
290 color_filter_def_t rainbow_filter = {
291 .name = "rainbow",
292 .cli_name = "rainbow",
293 .r = rainbow_r,
294 .g = rainbow_g,
295 .b = rainbow_b,
296 .foreground_on_bg = false,
297 };
298
299 // Process each pixel with minimum brightness to prevent black
300 // Minimum brightness is 70% to ensure rainbow colors stay vibrant even for dark input
301 const uint8_t min_brightness = 179; // 70% of 255
302
303 for (uint32_t y = 0; y < height; y++) {
304 uint8_t *row = pixels + y * stride;
305 for (uint32_t x = 0; x < width; x++) {
306 uint8_t *pixel = row + x * 3;
307 uint8_t r = pixel[0];
308 uint8_t g = pixel[1];
309 uint8_t b = pixel[2];
310 uint8_t gray = rgb_to_grayscale(r, g, b);
311
312 // Boost grayscale to maintain minimum brightness for rainbow
313 // Formula: adjusted = min + gray * (1 - min/255)
314 // This ensures: black pixels -> min_brightness, white pixels -> 255
315 uint8_t adjusted_gray = min_brightness + (uint8_t)((gray * (255U - min_brightness)) / 255U);
316
317 colorize_grayscale_pixel(adjusted_gray, &rainbow_filter, &pixel[0], &pixel[1], &pixel[2]);
318 }
319 }
320 return 0;
321 }
322
323 const color_filter_def_t *filter_def = color_filter_get_metadata(filter);
324 if (!filter_def) {
325 return -1; // Invalid filter
326 }
327
328 // Process each pixel in the image
329 for (uint32_t y = 0; y < height; y++) {
330 uint8_t *row = pixels + y * stride;
331 for (uint32_t x = 0; x < width; x++) {
332 uint8_t *pixel = row + x * 3; // Assume RGB24 (3 bytes per pixel)
333 uint8_t r = pixel[0];
334 uint8_t g = pixel[1];
335 uint8_t b = pixel[2];
336
337 // Convert RGB to grayscale using ITU-R BT.601
338 uint8_t gray = rgb_to_grayscale(r, g, b);
339
340 // Colorize the grayscale value
341 colorize_grayscale_pixel(gray, filter_def, &pixel[0], &pixel[1], &pixel[2]);
342 }
343 }
344
345 return 0;
346}
const color_filter_def_t * color_filter_get_metadata(color_filter_t filter)
Get color filter metadata by enum value.
void color_filter_calculate_rainbow(float time, uint8_t *r, uint8_t *g, uint8_t *b)
Calculate rainbow color from time (3.5s cycle)
Color filter metadata structure.
const char * name
Human-readable filter name (e.g., "matrix-green")
@ COLOR_FILTER_RAINBOW
Rainbow (cycles through spectrum over 3.5s)
Definition terminal.h:625

References color_filter_calculate_rainbow(), color_filter_get_metadata(), COLOR_FILTER_NONE, COLOR_FILTER_RAINBOW, and color_filter_def_t::name.

Referenced by ascii_convert_with_capabilities(), mirror_convert_frame(), and session_display_convert_to_ascii().

◆ ascii_convert()

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] 
)

#include <ascii.h>

Convert image to ASCII art.

Parameters
originalSource image (must not be NULL)
widthTarget width in characters (must be > 0)
heightTarget height in characters (must be > 0)
colorEnable color output (true for color, false for monochrome)
aspect_ratioPreserve aspect ratio (true to maintain proportions, false to fill dimensions)
stretchStretch to fit (if aspect_ratio is false, true to fill exactly, false to fit within)
palette_charsCharacter palette to use (or NULL for default ASCII palette)
luminance_paletteLuminance-to-character mapping palette (must not be NULL, 256 elements)
Returns
Allocated ASCII frame string (caller must free), or NULL on error

Converts an image to ASCII art with specified dimensions and color mode. Uses character palette to map pixel luminance to ASCII characters and optionally applies color output using ANSI escape sequences.

CONVERSION PROCESS:

  • Resizes image to target dimensions (with aspect ratio preservation if enabled)
  • Maps each pixel's luminance to ASCII character using palette
  • Optionally applies color output using ANSI escape sequences
  • Returns null-terminated string containing ASCII frame
Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
Aspect ratio preservation uses terminal character correction.
Color output requires terminal color support for proper display.

Definition at line 111 of file ascii.c.

113 {
114 if (original == NULL || !palette_chars || !luminance_palette) {
115 log_error("ascii_convert: invalid parameters");
116 return NULL;
117 }
118
119 // Check for empty strings
120 if (palette_chars[0] == '\0' || luminance_palette[0] == '\0') {
121 log_error("ascii_convert: empty palette strings");
122 return NULL;
123 }
124
125 // Start with the target dimensions requested by the user (or detected from
126 // the terminal). These can be modified by aspect_ratio() if stretching is
127 // disabled and one of the dimensions was left to be calculated
128 // automatically.
129 ssize_t resized_width = width;
130 ssize_t resized_height = height;
131
132 // If stretch is enabled, use full dimensions, otherwise calculate aspect ratio
133 if (_aspect_ratio) {
134 // The server now provides images at width*2 x height pixels
135 // The aspect_ratio function will handle terminal character aspect ratio
136 aspect_ratio(original->w, original->h, resized_width, resized_height, stretch, &resized_width, &resized_height);
137 }
138
139 // Calculate padding for centering
140 size_t pad_width = 0;
141 size_t pad_height = 0;
142
143 if (_aspect_ratio) {
144 // Only calculate padding when not stretching
145 ssize_t pad_width_ss = width > resized_width ? (width - resized_width) / 2 : 0;
146 pad_width = (size_t)pad_width_ss;
147
148 ssize_t pad_height_ss = height > resized_height ? (height - resized_height) / 2 : 0;
149 pad_height = (size_t)pad_height_ss;
150 }
151
152 // Resize the captured frame to the aspect-correct dimensions.
153 if (resized_width <= 0 || resized_height <= 0) {
154 log_error("Invalid dimensions for resize: width=%zd, height=%zd", resized_width, resized_height);
155 return NULL;
156 }
157
158 // Validate dimensions fit in image_t's int fields before casting
159 if (resized_width > INT_MAX || resized_height > INT_MAX) {
160 log_error("Dimensions exceed INT_MAX: width=%zd, height=%zd", resized_width, resized_height);
161 return NULL;
162 }
163
164 // Always resize to target dimensions
165 image_t *resized = image_new((size_t)resized_width, (size_t)resized_height);
166 if (!resized) {
167 log_error("Failed to allocate resized image");
168 return NULL;
169 }
170
171 image_clear(resized);
172 image_resize(original, resized);
173
174 char *ascii;
175 if (color) {
176 // Check for half-block mode first
177 if (GET_OPTION(render_mode) == RENDER_MODE_HALF_BLOCK) {
178 const uint8_t *rgb_data = (const uint8_t *)resized->pixels;
179#if SIMD_SUPPORT_NEON
180 // Use NEON half-block renderer (optimized SIMD path)
181 log_dev("Using NEON halfblock renderer");
182 ascii = rgb_to_truecolor_halfblocks_neon(rgb_data, resized->w, resized->h, 0);
183#else
184 // Fallback to scalar halfblock renderer (works on all platforms)
185 log_dev("Using scalar halfblock renderer (NEON not available)");
186 ascii = rgb_to_truecolor_halfblocks_scalar(rgb_data, resized->w, resized->h, 0);
187#endif
188 } else {
189#ifdef SIMD_SUPPORT
190 // Standard color modes (foreground/background)
191 bool use_background = (GET_OPTION(render_mode) == RENDER_MODE_BACKGROUND);
192 ascii = image_print_color_simd(resized, use_background, false, palette_chars);
193#else
194 if (GET_OPTION(render_mode) == RENDER_MODE_BACKGROUND) {
195 ascii = image_print_color_background(resized, palette_chars);
196 } else {
197 ascii = image_print_color_utf8(resized, palette_chars);
198 }
199#endif
200 }
201 } else {
202 // Use grayscale/monochrome conversion with client's palette
203 ascii = image_print(resized, palette_chars);
204 }
205
206 if (!ascii) {
207 log_error("Failed to convert image to ASCII");
208 image_destroy(resized);
209 return NULL;
210 }
211
212 size_t ascii_len = strlen(ascii);
213 if (ascii_len == 0) {
214 log_error("ASCII conversion returned empty string (resized dimensions: %dx%d)", resized->w, resized->h);
215 SAFE_FREE(ascii);
216 image_destroy(resized);
217 return NULL;
218 }
219
220 char *ascii_width_padded = ascii_pad_frame_width(ascii, pad_width);
221 SAFE_FREE(ascii);
222
223 char *ascii_padded = ascii_pad_frame_height(ascii_width_padded, pad_height);
224 SAFE_FREE(ascii_width_padded);
225
226 // Only destroy resized if we allocated it (not when using original directly)
227 image_destroy(resized);
228
229 return ascii_padded;
230}
char * image_print_color_background(const image_t *p, const char *palette)
Convert image to colored ASCII art with background colors.
Definition background.c:17
char * image_print_color_utf8(const image_t *p, const char *palette)
Definition foreground.c:310
#define SAFE_FREE(ptr)
Definition common.h:376
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#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 * rgb_to_truecolor_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes)
Scalar truecolor halfblock renderer.
char * image_print(const image_t *p, const char *palette)
Print image as ASCII art (monochrome)
Definition foreground.c:27
char * ascii_pad_frame_width(const char *frame, size_t pad_left)
Add leading spaces (left-padding) to each line of a frame.
Definition ascii.c:480
char * ascii_pad_frame_height(const char *frame, size_t pad_top)
Add blank lines (vertical padding) to center a frame vertically.
Definition ascii.c:925
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_color_simd(image_t *image, bool use_background_mode, bool use_256color, const char *ascii_chars)
Print image as ASCII with color using SIMD.
Definition sgr.c:413
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.
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)
@ RENDER_MODE_BACKGROUND
Background colors (block colors)
Definition terminal.h:666
@ RENDER_MODE_HALF_BLOCK
Unicode half-block characters (mixed foreground/background)
Definition terminal.h:668

References ascii_pad_frame_height(), ascii_pad_frame_width(), aspect_ratio(), GET_OPTION, image_t::h, image_clear(), image_destroy(), image_new(), image_print(), image_print_color_background(), image_print_color_simd(), image_print_color_utf8(), image_resize(), log_dev, log_error, image_t::pixels, RENDER_MODE_BACKGROUND, RENDER_MODE_HALF_BLOCK, rgb_to_truecolor_halfblocks_scalar(), SAFE_FREE, and image_t::w.

◆ ascii_convert_with_capabilities()

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 
)

#include <ascii.h>

Convert image to ASCII art with terminal capability awareness.

Parameters
originalSource image (must not be NULL)
widthTarget width in characters (must be > 0)
heightTarget height in characters (must be > 0)
capsTerminal capabilities structure (must not be NULL)
use_aspect_ratioPreserve aspect ratio (true to maintain proportions, false to fill dimensions)
stretchStretch to fit (if use_aspect_ratio is false, true to fill exactly, false to fit within)
palette_charsCharacter palette to use (or NULL for default ASCII palette)
luminance_paletteLuminance-to-character mapping palette (must not be NULL, 256 elements)
Returns
Allocated ASCII frame string (caller must free), or NULL on error

Converts an image to ASCII art with automatic color mode selection based on terminal capabilities. Automatically chooses the best color mode (16-color, 256-color, or truecolor) for optimal display.

CAPABILITY-AWARE CONVERSION:

  • Detects terminal color capabilities from structure
  • Selects optimal color mode (truecolor > 256-color > 16-color > monochrome)
  • Applies terminal character aspect ratio correction
  • Uses capability-specific conversion algorithms
Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
Color mode is selected automatically based on terminal capabilities.
This is the recommended function for capability-aware ASCII conversion.

Definition at line 233 of file ascii.c.

235 {
236
237 if (original == NULL || caps == NULL) {
238 log_error("Invalid parameters for ascii_convert_with_capabilities");
239 return NULL;
240 }
241
242 // Validate original image dimensions to detect corruption
243 if (original->w <= 0 || original->w > 10000 || original->h <= 0 || original->h > 10000) {
244 log_error("Invalid original image dimensions detected: w=%d, h=%d (likely corrupted)", original->w, original->h);
245 return NULL;
246 }
247
248 if (original->pixels == NULL) {
249 log_error("Original image pixels pointer is NULL");
250 return NULL;
251 }
252
253 // Start with the target dimensions requested by the user.
254 ssize_t resized_width = width;
255 ssize_t resized_height = height;
256
257 // Preserve aspect ratio by cropping the source to cover the viewport. The
258 // destination dimensions remain the full requested viewport dimensions.
259
260 // Half-block mode doubles height for 2x vertical resolution (AFTER aspect ratio)
261 if (caps->render_mode == RENDER_MODE_HALF_BLOCK) {
262 resized_height = resized_height * 2;
263 }
264
265 // Cover rendering fills the viewport, so no aspect-ratio padding is needed.
266 size_t pad_width = 0;
267 size_t pad_height = 0;
268
269 // Resize the captured frame to the aspect-correct dimensions
270 if (resized_width <= 0 || resized_height <= 0) {
271 log_error("Invalid dimensions for resize: width=%zd, height=%zd", resized_width, resized_height);
272 return NULL;
273 }
274
275 // Validate dimensions fit in image_t's int fields before casting
276 if (resized_width > INT_MAX || resized_height > INT_MAX) {
277 log_error("Dimensions exceed INT_MAX: width=%zd, height=%zd", resized_width, resized_height);
278 return NULL;
279 }
280
281 // PROFILING: Time image allocation and resize
282 START_TIMER("image_alloc");
283 uint64_t prof_alloc_start_ns = time_get_ns();
284
285 image_t *resized = image_new((size_t)resized_width, (size_t)resized_height);
286 if (!resized) {
287 log_error("Failed to allocate resized image");
288 return NULL;
289 }
290
291 image_clear(resized);
292
293 uint64_t prof_alloc_end_ns = time_get_ns();
294 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "image_alloc",
295 "IMAGE_ALLOC: Alloc+clear complete (%.2f ms)");
296
297 START_TIMER("image_resize");
298 uint64_t prof_resize_start_ns = prof_alloc_end_ns;
299
300 if (use_aspect_ratio && !stretch) {
301 image_resize_cover(original, resized, resized_width, resized_height);
302 } else {
303 image_resize(original, resized);
304 }
305
306 uint64_t prof_resize_end_ns = time_get_ns();
307 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "image_resize",
308 "IMAGE_RESIZE: Resize complete (%.2f ms)");
309
310 // Check original image before resize (validate dimensions to prevent integer overflow)
311 int orig_black = 0, orig_total = 0;
312 if (original->w > 0 && original->w <= 10000 && original->h > 0 && original->h <= 10000) {
313 int64_t total_pixels = (int64_t)original->w * original->h;
314 int sample_size = (total_pixels < 1000) ? (int)total_pixels : 1000;
315 for (int i = 0; i < sample_size; i++) {
316 rgb_pixel_t p = original->pixels[i];
317 orig_total++;
318 if (p.r == 0 && p.g == 0 && p.b == 0)
319 orig_black++;
320 }
322 ">>> ORIGINAL image: %d/%d pixels are black (%.1f%%), first pixel=RGB(%d,%d,%d)\n", orig_black,
323 orig_total, orig_total > 0 ? 100.0 * orig_black / orig_total : 0, original->pixels[0].r,
324 original->pixels[0].g, original->pixels[0].b);
325 } else {
326 log_warn("Skipping debug check: corrupted image dimensions (w=%d, h=%d)", original->w, original->h);
327 }
328
329 // PROFILING: Time ASCII print
330 uint64_t prof_print_start_ns = prof_resize_end_ns;
331
332 // DEBUG: Log dimensions going to renderer
334 "ascii_convert: original=%dx%d, requested=%zdx%zd, resized=%dx%d, pad=%zux%zu (mode=%d)", original->w,
335 original->h, width, height, resized->w, resized->h, pad_width, pad_height, caps->render_mode);
336
337 // Check if image is all black
338 int black_pixels = 0, total_pixels = 0;
339 for (int i = 0; i < resized->w * resized->h && i < 1000; i++) {
340 rgb_pixel_t p = resized->pixels[i];
341 total_pixels++;
342 if (p.r == 0 && p.g == 0 && p.b == 0)
343 black_pixels++;
344 }
346 ">>> Resized image check: %d/%d pixels are black (%.1f%%), first pixel=RGB(%d,%d,%d)\n", black_pixels,
347 total_pixels, total_pixels > 0 ? 100.0 * black_pixels / total_pixels : 0, resized->pixels[0].r,
348 resized->pixels[0].g, resized->pixels[0].b);
349
350 // Apply the target client's filter after resizing, so it is scoped to this
351 // client and never mutates the shared source frame.
352 if (caps->color_filter != COLOR_FILTER_NONE) {
353 const float time_seconds = (float)time_get_ns() / (float)NS_PER_SEC_INT;
354 if (apply_color_filter((uint8_t *)resized->pixels, (uint32_t)resized->w, (uint32_t)resized->h,
355 (uint32_t)(resized->w * sizeof(rgb_pixel_t)), caps->color_filter, time_seconds) != 0) {
356 log_warn("Failed to apply client color filter %d", caps->color_filter);
357 }
358 }
359
360 // Use the capability-aware image printing function with client's palette
361 START_TIMER("image_print_with_capabilities");
362 char *ascii = image_print_with_capabilities(resized, caps, palette_chars);
363
364 uint64_t prof_print_end_ns = time_get_ns();
365 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "image_print_with_capabilities",
366 "IMAGE_PRINT: Print complete (%.2f ms)");
367
368 uint64_t alloc_time_us = time_ns_to_us(time_elapsed_ns(prof_alloc_start_ns, prof_alloc_end_ns));
369 uint64_t resize_time_us = time_ns_to_us(time_elapsed_ns(prof_resize_start_ns, prof_resize_end_ns));
370 uint64_t print_time_us = time_ns_to_us(time_elapsed_ns(prof_print_start_ns, prof_print_end_ns));
371
372 // PROFILING: Time padding
373 START_TIMER("ascii_padding");
374 uint64_t prof_pad_start_ns = time_get_ns();
375
376 if (!ascii) {
377 log_error("Failed to convert image to ASCII using terminal capabilities");
378 image_destroy(resized);
379 return NULL;
380 }
381
382 size_t ascii_len = strlen(ascii);
383 if (ascii_len == 0) {
384 log_error("Capability-aware ASCII conversion returned empty string (resized dimensions: %dx%d)", resized->w,
385 resized->h);
386 SAFE_FREE(ascii);
387 image_destroy(resized);
388 return NULL;
389 }
390
391 char *ascii_width_padded = ascii_pad_frame_width(ascii, pad_width);
392 SAFE_FREE(ascii);
393
394 char *ascii_padded = ascii_pad_frame_height(ascii_width_padded, pad_height);
395 SAFE_FREE(ascii_width_padded);
396
397 uint64_t prof_pad_end_ns = time_get_ns();
398 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 2 * NS_PER_MS_INT, "ascii_padding",
399 "ASCII_PADDING: Padding complete (%.2f ms)");
400
401 uint64_t pad_time_ns = time_elapsed_ns(prof_pad_start_ns, prof_pad_end_ns);
402 char alloc_str[32], resize_str[32], print_str[32], pad_str[32], total_str[32];
403 time_pretty(alloc_time_us * 1000, -1, alloc_str, sizeof(alloc_str));
404 time_pretty(resize_time_us * 1000, -1, resize_str, sizeof(resize_str));
405 time_pretty(print_time_us * 1000, -1, print_str, sizeof(print_str));
406 time_pretty(pad_time_ns, -1, pad_str, sizeof(pad_str));
407 uint64_t total_time_ns = (alloc_time_us + resize_time_us + print_time_us) * 1000 + pad_time_ns;
408 time_pretty(total_time_ns, -1, total_str, sizeof(total_str));
409 log_dev("ASCII_BREAKDOWN: alloc=%s, resize=%s, print=%s, pad=%s (total=%s)", alloc_str, resize_str, print_str,
410 pad_str, total_str);
411
412 image_destroy(resized);
413
414 return ascii_padded;
415}
unsigned long long uint64_t
Definition common.h:59
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define STOP_TIMER_AND_LOG_EVERY(log_level, interval_ns, threshold_ns, timer_name, msg_fmt,...)
Stop a timer and log the result with rate limiting.
Definition time.h:647
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
#define START_TIMER(name_fmt,...)
Start a timer with formatted name.
Definition time.h:333
#define NS_PER_MS_INT
Definition time.h:156
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
#define US_PER_SEC_INT
Definition time.h:161
char * image_print_with_capabilities(const image_t *image, const terminal_capabilities_t *caps, const char *palette)
Convert image to ASCII art with terminal capabilities.
Definition ascii.c:978
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
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)
render_mode_t render_mode
Preferred rendering mode (render_mode_t)
Definition terminal.h:721
color_filter_t color_filter
Monochromatic color filter enum value (color_filter_t)
Definition terminal.h:735

References apply_color_filter(), ascii_pad_frame_height(), ascii_pad_frame_width(), rgb_pixel_t::b, terminal_capabilities_t::color_filter, COLOR_FILTER_NONE, rgb_pixel_t::g, image_t::h, image_clear(), image_destroy(), image_new(), image_print_with_capabilities(), image_resize(), log_debug_every, log_dev, log_error, log_warn, NS_PER_MS_INT, NS_PER_SEC_INT, image_t::pixels, rgb_pixel_t::r, terminal_capabilities_t::render_mode, RENDER_MODE_HALF_BLOCK, SAFE_FREE, START_TIMER, STOP_TIMER_AND_LOG_EVERY, time_elapsed_ns(), time_get_ns(), time_pretty(), US_PER_SEC_INT, and image_t::w.

Referenced by mirror_convert_frame(), and session_display_convert_to_ascii().

◆ ascii_create_grid()

char * ascii_create_grid ( ascii_frame_source_t *  sources,
int  source_count,
int  width,
int  height,
size_t *  out_size 
)

#include <ascii.h>

Create a grid layout from multiple ASCII frames.

Parameters
sourcesArray of frame sources (must not be NULL, must have source_count elements)
source_countNumber of frame sources (must be > 0)
widthGrid width in characters per frame (must be > 0)
heightGrid height in characters per frame (must be > 0)
out_sizePointer to store output size in bytes (can be NULL)
Returns
Allocated grid frame string (caller must free), or NULL on error

Combines multiple ASCII frames into a single grid layout. Arranges frames in a grid pattern suitable for multi-user display. Each frame is positioned in the grid according to its index in the sources array.

GRID LAYOUT:

  • Frames are arranged in grid pattern (rows and columns)
  • Each frame has specified width and height in characters
  • Grid dimensions are calculated based on source_count
  • Output frame contains all frames arranged in grid
Note
Returns NULL on error (invalid sources, memory allocation failure).
Grid frame string must be freed by caller using free().
Frame sources must have valid frame_data and frame_size.
Grid layout is useful for multi-user video display.
Example
// Initialize sources...
char *grid = ascii_create_grid(sources, 4, 80, 40, NULL);
if (grid) {
ascii_write(grid);
free(grid);
}
asciichat_error_t ascii_write(const char *frame)
Write ASCII frame to terminal.
Definition ascii.c:420
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:625
Frame source structure for grid layout.
Definition ascii.h:325

Creates a grid layout from multiple ASCII frame sources with | and _ separators.

Parameters: sources Array of ASCII frame sources to combine source_count Number of sources in the array width Target width of the output grid height Target height of the output grid out_size Output parameter for the size of the returned buffer

Returns: A newly allocated, null-terminated string containing the grid layout, or NULL on error. Caller must free the returned buffer.

Definition at line 625 of file ascii.c.

625 {
626 if (!sources || source_count <= 0 || width <= 0 || height <= 0 || !out_size) {
627 return NULL;
628 }
629
630 // If no sources, return empty frame
631
632 // If only one source, center it properly to maintain aspect ratio and look good
633 if (source_count == 1) {
634 // Create a frame of the target size filled with spaces
635 // Check for integer overflow before multiplication
636 size_t w = (size_t)width;
637 size_t h = (size_t)height;
638 size_t w_times_h;
639 if (checked_size_mul(w, h, &w_times_h) != ASCIICHAT_OK) {
640 SET_ERRNO(ERROR_INVALID_PARAM, "ascii_create_grid: dimensions would overflow: %dx%d", width, height);
641 return NULL;
642 }
643
644 size_t w_times_h_plus_h;
645 if (checked_size_add(w_times_h, h, &w_times_h_plus_h) != ASCIICHAT_OK) {
646 SET_ERRNO(ERROR_INVALID_PARAM, "ascii_create_grid: buffer size would overflow: %dx%d", width, height);
647 return NULL;
648 }
649
650 size_t target_size;
651 if (checked_size_add(w_times_h_plus_h, 1, &target_size) != ASCIICHAT_OK) {
652 SET_ERRNO(ERROR_INVALID_PARAM, "ascii_create_grid: buffer size would overflow: %dx%d", width, height);
653 return NULL;
654 }
655 char *result;
656 result = SAFE_MALLOC(target_size, char *);
657 SAFE_MEMSET(result, target_size, ' ', target_size - 1);
658 result[target_size - 1] = '\0';
659
660 // Add newlines at the end of each row
661 for (int row = 0; row < height; row++) {
662 result[row * (width + 1) + width] = '\n';
663 }
664
665 // Copy the source frame into the result, line by line, centering it
666 // Handle NULL frame_data gracefully
667 const char *src_data = sources[0].frame_data;
668 int src_pos = 0;
669 int src_size = (int)sources[0].frame_size;
670
671 // If source data is NULL or empty, just return the empty frame
672 if (!src_data || src_size <= 0) {
673 *out_size = target_size - 1; // Don't count null terminator
674 return result;
675 }
676
677 // Count lines in source to calculate vertical padding
678 int src_lines = 0;
679 for (int i = 0; i < src_size; i++) {
680 if (src_data[i] == '\n')
681 src_lines++;
682 }
683
684 int v_padding = (height - src_lines) / 2;
685 if (v_padding < 0)
686 v_padding = 0;
687
688 int dst_row = v_padding;
689 src_pos = 0;
690
691 while (src_pos < src_size && dst_row < height) {
692 // Find end of current line in source
693 int line_start = src_pos;
694 int line_len = 0;
695 while (src_pos < src_size && src_data[src_pos] != '\n') {
696 line_len++;
697 src_pos++;
698 }
699
700 // Calculate visual width of the line (excluding ANSI escape sequences)
701 int visual_line_width = ansi_visual_width(&src_data[line_start], line_len);
702
703 // Calculate horizontal padding to center the line based on visual width
704 int h_padding = (width - visual_line_width) / 2;
705 if (h_padding < 0)
706 h_padding = 0;
707
708 // Copy line to result with padding
709 // Use size_t for position calculation to prevent integer underflow
710 size_t row_offset = (size_t)dst_row * (size_t)(width + 1);
711 size_t dst_pos = row_offset + (size_t)h_padding;
712 int max_visual_width = width - h_padding;
713 // Truncate to visual width while preserving complete ANSI sequences
714 int copy_len = ansi_truncate_to_visual_width(&src_data[line_start], line_len, max_visual_width);
715
716 if (copy_len > 0 && dst_pos + (size_t)copy_len < target_size) {
717 SAFE_MEMCPY(&result[dst_pos], target_size - dst_pos, &src_data[line_start], (size_t)copy_len);
718 }
719
720 // Skip newline in source
721 if (src_pos < src_size && src_data[src_pos] == '\n') {
722 src_pos++;
723 }
724
725 dst_row++;
726 }
727
728 *out_size = target_size - 1; // Don't count null terminator
729 return result;
730 }
731
732 // Multiple sources: create grid layout
733 // Calculate grid dimensions that maximize the use of terminal space
734 // Character aspect ratio: terminal chars are typically ~2x taller than wide
735 float char_aspect = 2.0f;
736
737 int grid_cols, grid_rows;
738 float best_score = -1.0f;
739 int best_cols = 1;
740 int best_rows = source_count;
741
742 // Try all possible grid configurations
743 for (int test_cols = 1; test_cols <= source_count; test_cols++) {
744 int test_rows = (int)ceil((double)source_count / test_cols);
745
746 // Skip configurations with too many empty cells
747 int empty_cells = (test_cols * test_rows) - source_count;
748 if (empty_cells > source_count / 2)
749 continue; // Don't waste more than 50% space
750
751 // Calculate the size each cell would have
752 int cell_width = (width - (test_cols - 1)) / test_cols; // -1 per separator
753 int cell_height = (height - (test_rows - 1)) / test_rows; // -1 per separator
754
755 // Skip if cells would be too small
756 if (cell_width < 10 || cell_height < 3)
757 continue;
758
759 // Calculate the aspect ratio of each cell (accounting for char aspect)
760 float cell_aspect = ((float)cell_width / (float)cell_height) / char_aspect;
761
762 // Score based on how close to square (1:1) each video cell would be
763 // This naturally adapts to any terminal size
764 float aspect_score = 1.0f - fabsf(logf(cell_aspect)); // log makes it symmetric around 1
765 if (aspect_score < 0)
766 aspect_score = 0;
767
768 // Bonus for better space utilization
769 float utilization = (float)source_count / (float)(test_cols * test_rows);
770
771 // For 2 clients specifically, heavily weight the aspect score
772 // This makes 2 clients naturally go horizontal on wide terminals and vertical on tall ones
773 float total_score;
774 if (source_count == 2) {
775 // For 2 clients, we want the layout that gives the most square-ish cells
776 total_score = aspect_score * 0.9f + utilization * 0.1f;
777 } else {
778 // For 3+ clients, balance aspect ratio with space utilization
779 total_score = aspect_score * 0.7f + utilization * 0.3f;
780 }
781
782 // Small bonus for simpler grids (prefer 2x2 over 3x1, etc.)
783 if (test_cols == test_rows) {
784 total_score += 0.05f; // Slight preference for square grids
785 }
786
787 if (total_score > best_score) {
788 best_score = total_score;
789 best_cols = test_cols;
790 best_rows = test_rows;
791 }
792 }
793
794 grid_cols = best_cols;
795 grid_rows = best_rows;
796
797 // Calculate dimensions for each cell (leave 1 char for separators)
798 int cell_width = (width - (grid_cols - 1)) / grid_cols;
799 int cell_height = (height - (grid_rows - 1)) / grid_rows;
800
801 if (cell_width < 10 || cell_height < 3) {
802 // Too small for grid layout, just use first source
803 char *result;
804 result = SAFE_MALLOC(sources[0].frame_size + 1, char *);
805 if (sources[0].frame_data && sources[0].frame_size > 0) {
806 SAFE_MEMCPY(result, sources[0].frame_size + 1, sources[0].frame_data, sources[0].frame_size);
807 result[sources[0].frame_size] = '\0';
808 *out_size = sources[0].frame_size;
809 } else {
810 // Handle NULL or empty frame data
811 result[0] = '\0';
812 *out_size = 0;
813 }
814 return result;
815 }
816
817 // Allocate mixed frame buffer
818 // Check for integer overflow before multiplication
819 size_t w_sz = (size_t)width;
820 size_t h_sz = (size_t)height;
821 if (w_sz > SIZE_MAX / h_sz) {
822 SET_ERRNO(ERROR_INVALID_PARAM, "ascii_create_grid: dimensions would overflow: %dx%d", width, height);
823 return NULL;
824 }
825 size_t mixed_size = w_sz * h_sz + h_sz + 1; // +1 for null terminator, +height for newlines
826 char *mixed_frame;
827 mixed_frame = SAFE_MALLOC(mixed_size, char *);
828
829 // Initialize mixed frame with spaces
830 SAFE_MEMSET(mixed_frame, mixed_size, ' ', mixed_size - 1);
831 mixed_frame[mixed_size - 1] = '\0';
832
833 // Add newlines at the end of each row
834 for (int row = 0; row < height; row++) {
835 mixed_frame[row * (width + 1) + width] = '\n';
836 }
837
838 // Place each video source in the grid
839 for (int src = 0; src < source_count; src++) {
840 int grid_row = src / grid_cols;
841 int grid_col = src % grid_cols;
842
843 // Calculate position in mixed frame
844 int start_row = grid_row * (cell_height + 1); // +1 for separator
845 int start_col = grid_col * (cell_width + 1); // +1 for separator
846
847 // Parse source frame line by line and place in grid
848 const char *src_data = sources[src].frame_data;
849 int src_row = 0;
850 int src_pos = 0;
851
852 while (src_pos < (int)sources[src].frame_size && src_row < cell_height && start_row + src_row < height) {
853 // Find end of current line in source
854 int line_start = src_pos;
855 while (src_pos < (int)sources[src].frame_size && src_data[src_pos] != '\n') {
856 src_pos++;
857 }
858 int line_len = src_pos - line_start;
859
860 // Copy line to mixed frame (truncate if too long)
861 // Truncate to visual cell width while preserving complete ANSI sequences
862 int copy_len = ansi_truncate_to_visual_width(src_data + line_start, line_len, cell_width);
863 // Check that visual width of truncated line fits
864 int truncated_visual_width = ansi_visual_width(src_data + line_start, copy_len);
865 if (copy_len > 0 && start_col + truncated_visual_width <= width) {
866 int mixed_pos = (start_row + src_row) * (width + 1) + start_col;
867 SAFE_MEMCPY(mixed_frame + mixed_pos, mixed_size - (size_t)mixed_pos, src_data + line_start, (size_t)copy_len);
868 }
869
870 // Move to next line
871 if (src_pos < (int)sources[src].frame_size && src_data[src_pos] == '\n') {
872 src_pos++;
873 }
874 src_row++;
875 }
876
877 // Draw separators with bounds checking to prevent buffer overflow
878 if (grid_col < grid_cols - 1 && start_col + cell_width < width) {
879 // Vertical separator
880 for (int row = start_row; row < start_row + cell_height && row < height; row++) {
881 size_t idx = (size_t)row * (size_t)(width + 1) + (size_t)(start_col + cell_width);
882 if (idx < mixed_size - 1) { // -1 to preserve null terminator
883 mixed_frame[idx] = '|';
884 }
885 }
886 }
887
888 if (grid_row < grid_rows - 1 && start_row + cell_height < height) {
889 // Horizontal separator
890 for (int col = start_col; col < start_col + cell_width && col < width; col++) {
891 size_t idx = (size_t)(start_row + cell_height) * (size_t)(width + 1) + (size_t)col;
892 if (idx < mixed_size - 1) { // -1 to preserve null terminator
893 mixed_frame[idx] = '_';
894 }
895 }
896 // Corner character where separators meet
897 if (grid_col < grid_cols - 1 && start_col + cell_width < width) {
898 size_t idx = (size_t)(start_row + cell_height) * (size_t)(width + 1) + (size_t)(start_col + cell_width);
899 if (idx < mixed_size - 1) { // -1 to preserve null terminator
900 mixed_frame[idx] = '+';
901 }
902 }
903 }
904 }
905
906 *out_size = strlen(mixed_frame);
907 return mixed_frame;
908}
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:469
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
size_t frame_size
Frame data size in bytes (length of frame string)
Definition ascii.h:327
const char * frame_data
Frame data pointer (ASCII frame string, not owned)
Definition ascii.h:326

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ascii_frame_source_t::frame_data, ascii_frame_source_t::frame_size, SAFE_MALLOC, SAFE_MEMCPY, SAFE_MEMSET, and SET_ERRNO.

◆ ascii_pad_frame_height()

char * ascii_pad_frame_height ( const char *  frame,
size_t  pad_top 
)

#include <ascii.h>

Add blank lines (vertical padding) to center a frame vertically.

Parameters
frameASCII frame string (must not be NULL)
pad_topNumber of blank lines to add at top (must be >= 0)
Returns
Allocated padded frame string (caller must free with free()), or NULL on error

Adds blank lines (vertical padding) to the top of an ASCII frame. Useful for centering frames vertically or creating margins. Blank lines are added at the top of the frame.

Note
Returns NULL on error (invalid frame, memory allocation failure).
Padded frame string must be freed by caller using free().
Frame width is preserved (characters per line unchanged).
Frame height is increased by 'pad_top' lines.

Adds vertical padding (blank lines) to center a frame vertically.

Parameters: frame The input ASCII frame to pad vertically. pad_top Number of blank lines to add at the top.

Returns: A newly allocated, null-terminated string with vertical padding, or NULL if frame is NULL.

NOTE: Uses plain newlines instead of ANSI escape sequences to support both TTY and piped/redirected output. TTY flicker prevention is handled by the display layer (e.g., display.c) when appropriate.

Definition at line 925 of file ascii.c.

925 {
926 if (!frame) {
927 return NULL;
928 }
929
930 if (pad_top == 0) {
931 // Nothing to do; return a copy because the caller knows to free() the value.
932 size_t orig_len = strlen(frame);
933 char *copy;
934 copy = SAFE_MALLOC(orig_len + 1, char *);
935 SAFE_MEMCPY(copy, orig_len + 1, frame, orig_len + 1);
936 return copy;
937 }
938
939 // Calculate buffer size needed
940 // Each padding row needs: 1 newline character per padding row
941 size_t frame_len = strlen(frame);
942 size_t top_padding_len = pad_top; // 1 newline per padding row
943 size_t total_len = top_padding_len + frame_len;
944
945 char *buffer;
946 buffer = SAFE_MALLOC(total_len + 1, char *);
947
948 char *position = buffer;
949
950 // Add top padding with plain newlines
951 // Use plain newlines instead of ANSI escape sequences so the output works
952 // when redirected to pipes or files, not just TTY
953 for (size_t i = 0; i < pad_top; i++) {
954 *position++ = '\n';
955 }
956
957 // Copy the original frame
958 size_t remaining = frame_len + 1;
959 SAFE_MEMCPY(position, remaining, frame, frame_len);
960 position += frame_len;
961 *position = '\0';
962
963 return buffer;
964}

References frame, SAFE_MALLOC, and SAFE_MEMCPY.

Referenced by ascii_convert(), and ascii_convert_with_capabilities().

◆ ascii_pad_frame_width()

char * ascii_pad_frame_width ( const char *  frame,
size_t  pad 
)

#include <ascii.h>

Add leading spaces (left-padding) to each line of a frame.

Parameters
frameASCII frame string (must not be NULL)
padNumber of spaces to pad on left (must be >= 0)
Returns
Allocated padded frame string (caller must free with free()), or NULL on error

Adds leading spaces (left-padding) to each line of an ASCII frame. Useful for centering frames horizontally or creating margins. Each line in the frame is padded with the specified number of spaces.

Note
Returns NULL on error (invalid frame, memory allocation failure).
Padded frame string must be freed by caller using free().
Frame height is preserved (number of lines unchanged).
Frame width is increased by 'pad' characters.

Definition at line 480 of file ascii.c.

480 {
481 if (!frame) {
482 SET_ERRNO(ERROR_INVALID_PARAM, "ascii_pad_frame_width: frame is NULL");
483 return NULL;
484 }
485
486 if (pad_left == 0) {
487 // Nothing to do; return a copy so the caller can free it safely without
488 // worrying about the original allocation strategy.
489 size_t orig_len = strlen(frame);
490 char *copy;
491 copy = SAFE_MALLOC(orig_len + 1, char *);
492 SAFE_MEMCPY(copy, orig_len + 1, frame, orig_len + 1);
493 return copy;
494 }
495
496 // Count how many visual rows we have (lines terminated by '\n') to determine
497 // the final buffer size.
498 size_t line_count = 1; // There is always at least the first line
499 const char *char_in_frame = frame;
500 while (*char_in_frame) {
501 if (*char_in_frame == '\n') {
502 line_count++;
503 }
504 char_in_frame++;
505 }
506
507 // Total length of the source plus padding.
508 const size_t frame_len = strlen(frame);
509 const size_t left_padding_len = line_count * pad_left;
510 const size_t total_len = frame_len + left_padding_len;
511
512 char *buffer;
513 buffer = SAFE_MALLOC(total_len + 1, char *);
514
515 // Build the padded frame.
516 bool at_line_start = true;
517 const char *src = frame;
518 char *position = buffer;
519
520 while (*src) {
521 if (at_line_start) {
522 // Insert the requested amount of spaces in front of every visual row.
523 size_t remaining = (size_t)((ptrdiff_t)(buffer + total_len + 1) - (ptrdiff_t)position);
524 SAFE_MEMSET(position, remaining, ' ', (size_t)pad_left);
525 position += pad_left;
526 at_line_start = false;
527 }
528
529 *position++ = *src;
530
531 if (*src == '\n') {
532 at_line_start = true;
533 }
534
535 src++;
536 }
537
538 *position = '\0';
539 return buffer;
540}

References ERROR_INVALID_PARAM, frame, SAFE_MALLOC, SAFE_MEMCPY, SAFE_MEMSET, and SET_ERRNO.

Referenced by ascii_convert(), and ascii_convert_with_capabilities().

◆ ascii_simd_init()

void ascii_simd_init ( void  )

#include <common.h>

Initialize SIMD subsystem.

Initializes all SIMD subsystems and caches. Must be called once at startup.

Definition at line 601 of file video/ascii/common.c.

601 {
602 init_dec3();
604}
void init_dec3(void)
Initialize decimal-to-ASCII cache (0-255 → "0" to "255")
void init_default_luminance_palette(void)
Initialize default luminance palette.

References init_dec3(), and init_default_luminance_palette().

Referenced by ansi_fast_init(), server_main(), and session_host_start_render().

◆ ascii_write()

asciichat_error_t ascii_write ( const char *  frame)

#include <ascii.h>

Write ASCII frame to terminal.

Parameters
frameASCII frame string to write (must not be NULL)
Returns
ASCIICHAT_OK on success, error code on failure

Writes an ASCII frame string to the terminal output. Frame string may contain ANSI escape sequences for color output and cursor positioning. Output is written to the file descriptor configured during ascii_write_init().

Note
Frame string should be null-terminated.
ANSI escape sequences in frame string are processed by terminal.
Output is written immediately (no buffering).

Definition at line 420 of file ascii.c.

420 {
421 if (frame == NULL) {
422 log_warn("Attempted to write NULL frame");
423 return ERROR_INVALID_PARAM;
424 }
425
426 // Only reset cursor if output is connected to a TTY (not piped/redirected)
427 if (terminal_should_use_control_sequences(STDOUT_FILENO)) {
428 cursor_reset(STDOUT_FILENO);
429 }
430
431 size_t frame_len = strlen(frame);
432 // Write all frame data with automatic retry on transient errors
433 platform_write_all(STDOUT_FILENO, frame, frame_len);
434
435 // Flush C stdio buffer and terminal to ensure piped output is written immediately
436 (void)fflush(stdout);
437 terminal_flush(STDOUT_FILENO);
438
439 return ASCIICHAT_OK;
440}
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all bytes to a file descriptor, handling partial writes.
Definition system.c:224
asciichat_error_t terminal_flush(int fd)
Flush terminal output.
#define cursor_reset(fd)
Reset cursor to home position.
Definition ascii.h:518
bool terminal_should_use_control_sequences(int fd)
Check if terminal control sequences should be used for the given fd.

References ASCIICHAT_OK, cursor_reset, ERROR_INVALID_PARAM, frame, log_warn, platform_write_all(), terminal_flush(), and terminal_should_use_control_sequences().

◆ ascii_write_destroy()

void ascii_write_destroy ( int  fd,
bool  reset_terminal 
)

#include <ascii.h>

Destroy ASCII write subsystem.

Parameters
fdFile descriptor that was used for writing (must be valid)
reset_terminalWhether to reset terminal on cleanup (true to restore state, false to preserve)

Cleans up the ASCII write subsystem and restores terminal state. Restores cursor visibility, clears screen, and resets terminal attributes if reset_terminal is true.

TERMINAL CLEANUP:

  • Restores cursor visibility (if reset_terminal is true)
  • Resets terminal attributes (if reset_terminal is true)
  • Clears screen (if reset_terminal is true)
  • Restores terminal to default state
Note
Safe to call multiple times (no-op after first call).
Reset terminal restores terminal to default state.
After cleanup, terminal output functions may fail until re-initialized.

Definition at line 442 of file ascii.c.

442 {
443#if PLATFORM_WINDOWS
444 (void)fd; // Unused on Windows - terminal operations use stdout directly
445#endif
446 // console_clear(fd);
447 // cursor_reset(fd);
448 // Only restore terminal state if:
449 // 1. reset_terminal is true (caller wants terminal restore)
450 // 2. terminal_should_use_control_sequences() confirms it's safe (TTY, not snapshot, not testing)
451 if (reset_terminal && terminal_should_use_control_sequences(fd)) {
452 // Show cursor using platform abstraction
453 if (terminal_cursor_show() != 0) {
454 log_warn("Failed to show cursor");
455 }
456
457 // Re-enable echo using platform abstraction
458 if (terminal_set_echo(true) != 0) {
459 log_warn("Failed to re-enable echo");
460 }
461 }
462 log_debug("ASCII writer destroyed");
463}
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
asciichat_error_t terminal_set_echo(bool enabled)
Set terminal echo mode.
asciichat_error_t terminal_cursor_show(void)
Show terminal cursor.

References log_debug, log_warn, terminal_cursor_show(), terminal_set_echo(), and terminal_should_use_control_sequences().

Referenced by session_display_destroy().

◆ ascii_write_init()

asciichat_error_t ascii_write_init ( int  fd,
bool  reset_terminal 
)

#include <ascii.h>

Initialize ASCII write subsystem.

Parameters
fdFile descriptor to write to (must be valid file descriptor)
reset_terminalWhether to reset terminal on initialization (true to reset, false to preserve state)
Returns
ASCIICHAT_OK on success, error code on failure

Initializes the ASCII write subsystem for terminal output. Configures terminal for ASCII art display including cursor hiding, screen clearing, and terminal state management.

TERMINAL INITIALIZATION:

  • Hides cursor (if reset_terminal is true)
  • Clears screen (if reset_terminal is true)
  • Configures terminal for raw output
  • Prepares terminal for ASCII art display
Note
Must call ascii_write_destroy() when done.
Reset terminal clears screen and hides cursor for clean display.
File descriptor is used for terminal output (stdout, stderr, etc.).

Definition at line 83 of file ascii.c.

83 {
84 // Validate file descriptor
85 if (fd < 0) {
86 log_error("Invalid file descriptor %d", fd);
88 }
89
90 // Only apply terminal control sequences if:
91 // 1. reset_terminal is true (caller wants terminal reset)
92 // 2. terminal_should_use_control_sequences() confirms it's safe (TTY, not snapshot, not testing)
93 if (reset_terminal && terminal_should_use_control_sequences(fd)) {
94 console_clear(fd);
95 cursor_reset(fd);
96
97 // Disable echo using platform abstraction
98 if (terminal_set_echo(false) != 0) {
99 log_error("Failed to disable echo for fd %d", fd);
100 return ERROR_TERMINAL;
101 }
102 // Hide cursor using platform abstraction
103 if (terminal_cursor_hide() != 0) {
104 log_warn("Failed to hide cursor");
105 }
106 }
107 log_dev("ASCII writer initialized");
108 return ASCIICHAT_OK;
109}
@ ERROR_TERMINAL
Definition error_codes.h:69
asciichat_error_t terminal_cursor_hide(void)
Hide terminal cursor.
#define console_clear(fd)
Clear console and move cursor to home position.
Definition ascii.h:507

References ASCIICHAT_OK, console_clear, cursor_reset, ERROR_INVALID_PARAM, ERROR_TERMINAL, log_dev, log_error, log_warn, terminal_cursor_hide(), terminal_set_echo(), and terminal_should_use_control_sequences().

Referenced by session_display_create().

◆ benchmark_simd_color_conversion()

simd_benchmark_t benchmark_simd_color_conversion ( int  width,
int  height,
int  iterations,
bool  background_mode 
)

#include <common.h>

Benchmark SIMD color conversion methods.

Parameters
widthImage width in pixels
heightImage height in pixels
iterationsNumber of benchmark iterations
background_modeUse background colors
Returns
Benchmark results structure

◆ benchmark_simd_color_conversion_with_source()

simd_benchmark_t benchmark_simd_color_conversion_with_source ( int  width,
int  height,
int  iterations,
bool  background_mode,
const image_t *  source_image,
bool  use_256color 
)

#include <common.h>

Benchmark SIMD color conversion with source image.

Parameters
widthImage width in pixels
heightImage height in pixels
iterationsNumber of benchmark iterations
background_modeUse background colors
source_imageSource image for conversion
use_256colorUse 256-color mode
Returns
Benchmark results structure

◆ benchmark_simd_conversion()

simd_benchmark_t benchmark_simd_conversion ( int  width,
int  height,
int  iterations 
)

#include <common.h>

Benchmark SIMD conversion methods (monochrome)

Parameters
widthImage width in pixels
heightImage height in pixels
iterationsNumber of benchmark iterations
Returns
Benchmark results structure

◆ benchmark_simd_conversion_with_source()

simd_benchmark_t benchmark_simd_conversion_with_source ( int  width,
int  height,
int  iterations,
bool  background_mode,
const image_t *  source_image,
bool  use_256color 
)

#include <common.h>

Benchmark SIMD conversion with source image.

Parameters
widthImage width in pixels
heightImage height in pixels
iterationsNumber of benchmark iterations
background_modeUse background colors
source_imageSource image for conversion
use_256colorUse 256-color mode
Returns
Benchmark results structure

◆ build_utf8_luminance_cache()

void build_utf8_luminance_cache ( const char *  ascii_chars,
utf8_char_t  cache[256] 
)

#include <common.h>

Build UTF-8 luminance cache for all 256 levels.

Definition at line 380 of file video/ascii/common.c.

380 {
381 if (!ascii_chars || !cache)
382 return;
383
384 // Parse characters
385 typedef struct {
386 const char *start;
387 int byte_len;
388 } char_info_t;
389
390 char_info_t char_infos[256];
391 int char_count = 0;
392 const char *p = ascii_chars;
393
394 while (*p && char_count < 255) {
395 char_infos[char_count].start = p;
396
397 if ((*p & 0xE0) == 0xC0) {
398 char_infos[char_count].byte_len = 2;
399 p += 2;
400 } else if ((*p & 0xF0) == 0xE0) {
401 char_infos[char_count].byte_len = 3;
402 p += 3;
403 } else if ((*p & 0xF8) == 0xF0) {
404 char_infos[char_count].byte_len = 4;
405 p += 4;
406 } else {
407 // ASCII characters (0x00-0x7F) and invalid sequences: treat as single byte
408 char_infos[char_count].byte_len = 1;
409 p++;
410 }
411 char_count++;
412 }
413
414 // Handle empty string case
415 if (char_count == 0)
416 return;
417
418 // Build 256-entry cache
419 for (int i = 0; i < 256; i++) {
420 int char_idx = char_count > 1 ? (i * (char_count - 1) + 127) / 255 : 0;
421 if (char_idx >= char_count)
422 char_idx = char_count - 1;
423
424 cache[i].byte_len = char_infos[char_idx].byte_len;
425 memcpy(cache[i].utf8_bytes, char_infos[char_idx].start, char_infos[char_idx].byte_len);
426 if (cache[i].byte_len < 4) {
427 cache[i].utf8_bytes[cache[i].byte_len] = '\0';
428 }
429 }
430}
uint8_t utf8_bytes[4]

References utf8_char_t::byte_len, and utf8_char_t::utf8_bytes.

Referenced by get_utf8_palette_cache().

◆ build_utf8_ramp64_cache()

void build_utf8_ramp64_cache ( const char *  ascii_chars,
utf8_char_t  cache64[64],
uint8_t  char_index_ramp[256] 
)

#include <common.h>

Build UTF-8 ramp64 cache and character index ramp.

Definition at line 433 of file video/ascii/common.c.

433 {
434 if (!ascii_chars || !cache64 || !char_index_ramp)
435 return;
436
437 // Reuse the luminance cache building logic but for 64 entries
438 // (Same UTF-8 parsing as above, but map to 64 entries instead of 256)
439
440 // Parse characters (same as above)
441 typedef struct {
442 const char *start;
443 int byte_len;
444 } char_info_t;
445
446 char_info_t char_infos[256];
447 int char_count = 0;
448 const char *p = ascii_chars;
449
450 while (*p && char_count < 255) {
451 char_infos[char_count].start = p;
452
453 if ((*p & 0xE0) == 0xC0) {
454 char_infos[char_count].byte_len = 2;
455 p += 2;
456 } else if ((*p & 0xF0) == 0xE0) {
457 char_infos[char_count].byte_len = 3;
458 p += 3;
459 } else if ((*p & 0xF8) == 0xF0) {
460 char_infos[char_count].byte_len = 4;
461 p += 4;
462 } else {
463 // ASCII characters (0x00-0x7F) and invalid sequences: treat as single byte
464 char_infos[char_count].byte_len = 1;
465 p++;
466 }
467 char_count++;
468 }
469
470 // Handle empty string case
471 if (char_count == 0)
472 return;
473
474 // Build 64-entry cache and index ramp
475 for (int i = 0; i < 64; i++) {
476 int char_idx = char_count > 1 ? (i * (char_count - 1) + 31) / 63 : 0;
477 if (char_idx >= char_count)
478 char_idx = char_count - 1;
479
480 // Store character index for SIMD lookup
481 char_index_ramp[i] = (uint8_t)char_idx;
482
483 // Cache UTF-8 character
484 cache64[i].byte_len = char_infos[char_idx].byte_len;
485 memcpy(cache64[i].utf8_bytes, char_infos[char_idx].start, char_infos[char_idx].byte_len);
486 if (cache64[i].byte_len < 4) {
487 cache64[i].utf8_bytes[cache64[i].byte_len] = '\0';
488 }
489 }
490}

References utf8_char_t::byte_len, and utf8_char_t::utf8_bytes.

Referenced by get_utf8_palette_cache().

◆ color_filter_calculate_rainbow()

void color_filter_calculate_rainbow ( float  time,
uint8_t *  r,
uint8_t *  g,
uint8_t *  b 
)

#include <color_filter.h>

Calculate rainbow color from time (3.5s cycle)

Parameters
timeCurrent time in seconds
rOutput red component (0-255)
gOutput green component (0-255)
bOutput blue component (0-255)

Centralized rainbow color calculation used by both –matrix and –color-filter rainbow. Cycles through full RGB spectrum over 3.5 seconds.

Calculate rainbow color from time Cycles through full spectrum over 3.5 seconds This is the centralized rainbow implementation used by both matrix and color filter

Definition at line 169 of file color_filter.c.

169 {
170 // Cycle period: 3.5 seconds
171 const float cycle_period = 3.5f;
172
173 // Normalize time to [0, 1] range within cycle
174 float phase = fmodf(time, cycle_period) / cycle_period;
175
176 // Convert to hue angle [0, 360)
177 float hue = phase * 360.0f;
178
179 // HSV to RGB conversion (S=1, V=1 for full saturation and brightness)
180 // Hue divided into 6 segments
181 float h = hue / 60.0f;
182 int i = (int)floorf(h);
183 float f = h - (float)i;
184
185 // Calculate RGB based on hue segment
186 float q = 1.0f - f;
187 float t = f;
188
189 switch (i % 6) {
190 case 0:
191 *r = 255;
192 *g = (uint8_t)(t * 255.0f + 0.5f);
193 *b = 0;
194 break; // Red to Yellow
195 case 1:
196 *r = (uint8_t)(q * 255.0f + 0.5f);
197 *g = 255;
198 *b = 0;
199 break; // Yellow to Green
200 case 2:
201 *r = 0;
202 *g = 255;
203 *b = (uint8_t)(t * 255.0f + 0.5f);
204 break; // Green to Cyan
205 case 3:
206 *r = 0;
207 *g = (uint8_t)(q * 255.0f + 0.5f);
208 *b = 255;
209 break; // Cyan to Blue
210 case 4:
211 *r = (uint8_t)(t * 255.0f + 0.5f);
212 *g = 0;
213 *b = 255;
214 break; // Blue to Magenta
215 case 5:
216 *r = 255;
217 *g = 0;
218 *b = (uint8_t)(q * 255.0f + 0.5f);
219 break; // Magenta to Red
220 default:
221 *r = 255;
222 *g = 0;
223 *b = 0;
224 break; // Fallback to red
225 }
226
227 // Ensure minimum perceived brightness by boosting luminance
228 // Pure blue (0,0,255) has luminance ~18, which appears very dark
229 // We need minimum luminance of ~120 for bright, vibrant colors
230 const float min_luminance = 120.0f;
231
232 // Calculate current luminance using ITU-R BT.709 coefficients
233 float luminance = 0.2126f * *r + 0.7152f * *g + 0.0722f * *b;
234
235 if (luminance < min_luminance) {
236 // Boost luminance by adding white (increasing all channels proportionally)
237 // This desaturates the color but makes it much brighter
238 float boost = (min_luminance - luminance) / 3.0f;
239 *r = (uint8_t)fminf(255.0f, *r + boost);
240 *g = (uint8_t)fminf(255.0f, *g + boost);
241 *b = (uint8_t)fminf(255.0f, *b + boost);
242 }
243}

Referenced by apply_color_filter(), digital_rain_apply(), and rainbow_replace_ansi_colors().

◆ color_filter_from_cli_name()

color_filter_t color_filter_from_cli_name ( const char *  cli_name)

#include <color_filter.h>

Convert color filter CLI name to enum value.

Parameters
cli_nameCLI argument name (e.g., "matrix-green")
Returns
Filter enum value, or COLOR_FILTER_NONE if not found

Converts a CLI argument name (like "matrix-green") to the corresponding color_filter_t enum value.

Note
CLI names are case-insensitive
Returns COLOR_FILTER_NONE for unknown filter names

Definition at line 150 of file color_filter.c.

150 {
151 if (!cli_name) {
152 return COLOR_FILTER_NONE;
153 }
154
155 for (int i = 0; i < COLOR_FILTER_COUNT; i++) {
156 if (platform_strcasecmp(color_filter_registry[i].cli_name, cli_name) == 0) {
157 return (color_filter_t)i;
158 }
159 }
160
161 return COLOR_FILTER_NONE;
162}
int platform_strcasecmp(const char *s1, const char *s2)
Case-insensitive string comparison.
color_filter_t
Monochromatic color filter enumeration.
Definition terminal.h:599
@ COLOR_FILTER_COUNT
Total count of filters (not a valid filter)
Definition terminal.h:627

References COLOR_FILTER_COUNT, COLOR_FILTER_NONE, and platform_strcasecmp().

Referenced by parse_color_filter().

◆ color_filter_get_metadata()

const color_filter_def_t * color_filter_get_metadata ( color_filter_t  filter)

#include <color_filter.h>

Get color filter metadata by enum value.

Parameters
filterFilter enum value (color_filter_t)
Returns
Pointer to filter metadata, or NULL if invalid filter

Returns the metadata definition for a color filter including its name, RGB values, and rendering mode.

Note
The returned pointer is statically allocated and valid for program lifetime
Returns NULL for COLOR_FILTER_NONE

Definition at line 143 of file color_filter.c.

143 {
144 if (filter <= COLOR_FILTER_NONE || filter >= COLOR_FILTER_COUNT) {
145 return NULL;
146 }
147 return &color_filter_registry[filter];
148}

References COLOR_FILTER_COUNT.

Referenced by apply_color_filter(), and digital_rain_set_color_from_filter().

◆ convert_pixels_scalar()

void convert_pixels_scalar ( const rgb_pixel_t *  pixels,
char *  ascii_chars,
int  count,
const char  luminance_palette[256] 
)

#include <common.h>

Convert pixels to ASCII (scalar fallback)

Parameters
pixelsRGB pixel array
ascii_charsOutput buffer for ASCII characters
countNumber of pixels to convert
luminance_paletteLuminance-to-character mapping palette

◆ convert_pixels_scalar_with_newlines()

char * convert_pixels_scalar_with_newlines ( image_t *  image,
const char  luminance_palette[256] 
)

#include <common.h>

Convert image to ASCII with newlines (scalar fallback)

Parameters
imageSource image
luminance_paletteLuminance-to-character mapping palette
Returns
Allocated ASCII string (caller must free), or NULL on error

◆ digital_rain_apply()

char * digital_rain_apply ( digital_rain_t *  rain,
const char *  frame,
float  delta_time 
)

#include <digital_rain.h>

Apply digital rain effect to ASCII frame.

Parameters
rainDigital rain context (must not be NULL)
frameInput ASCII frame (must not be NULL, must be null-terminated)
delta_timeTime elapsed since last frame in seconds
Returns
Allocated frame with digital rain effect applied, or NULL on error

Applies the digital rain effect to an ASCII frame by analyzing character positions and injecting ANSI color codes to modulate brightness. The effect:

  1. Parses the frame to identify grid positions
  2. Calculates per-cell brightness using sawtooth wave pattern
  3. Detects cursor positions (brightness increases)
  4. Injects ANSI color codes to darken/brighten characters
  5. Returns new frame with effect applied

FRAME FORMAT:

  • Input can be plain ASCII or contain existing ANSI codes
  • Output contains ANSI color codes for brightness modulation
  • Frame dimensions must match num_columns x num_rows
  • Newlines are preserved in output
Note
Caller must free returned frame string
Input frame is not modified
Works with both color and monochrome frames
Preserves existing ANSI codes (may override colors)

Definition at line 369 of file digital_rain.c.

369 {
370 if (!rain || !frame) {
371 log_error("digital_rain_apply: NULL parameter");
372 return NULL;
373 }
374
375 // Update time
376 rain->time += delta_time * rain->animation_speed;
377 float sim_time = rain->time;
378
379 // Update rainbow color if rainbow mode is enabled
380 if (rain->rainbow_mode) {
381 color_filter_calculate_rainbow(sim_time, &rain->color_r, &rain->color_g, &rain->color_b);
382 }
383
384 // Allocate output buffer (input size + overhead for ANSI codes)
385 // Estimate: each character might need up to 20 bytes for ANSI codes
386 size_t input_len = strlen(frame);
387 size_t output_capacity = input_len * 20 + 1024;
388 char *output = SAFE_MALLOC(output_capacity, char *);
389 if (!output) {
390 log_error("digital_rain_apply: failed to allocate output buffer");
391 return NULL;
392 }
393
394 const char *src = frame;
395 char *dst = output;
396 size_t remaining = output_capacity;
397
398 int col = 0;
399 int row = 0;
400
401 while (*src && remaining > 100) {
402 // Try to parse and modify ANSI color sequences
403 if (*src == '\033') {
404 int r, g, b;
405 bool is_foreground;
406 const char *after = parse_ansi_color(src, &r, &g, &b, &is_foreground);
407
408 if (after) {
409 // Found a color sequence - calculate brightness for this position
410 float brightness = get_rain_brightness(rain, col, row, sim_time);
411 float brightness_below = get_rain_brightness(rain, col, row + 1, sim_time);
412 bool is_cursor = brightness > brightness_below;
413
414 // Blend with previous brightness for smooth transitions
415 if (!rain->first_frame && row < rain->num_rows && col < rain->num_columns) {
416 size_t idx = (size_t)row * (size_t)rain->num_columns + (size_t)col;
417 float prev_brightness = rain->previous_brightness[idx];
418 brightness = prev_brightness + (brightness - prev_brightness) * rain->brightness_decay;
419 rain->previous_brightness[idx] = brightness;
420 } else if (row < rain->num_rows && col < rain->num_columns) {
421 size_t idx = (size_t)row * (size_t)rain->num_columns + (size_t)col;
422 rain->previous_brightness[idx] = brightness;
423 }
424
425 // Generate modulated color
426 char ansi_buf[32];
427 int ansi_len =
428 generate_modulated_color(ansi_buf, sizeof(ansi_buf), r, g, b, brightness, is_foreground, is_cursor);
429
430 if ((size_t)ansi_len < remaining) {
431 memcpy(dst, ansi_buf, (size_t)ansi_len);
432 dst += ansi_len;
433 remaining -= (size_t)ansi_len;
434 }
435
436 src = after;
437 continue;
438 } else {
439 // Not a color sequence - copy as-is
440 const char *after_skip = skip_ansi_sequence(src);
441 size_t ansi_len = (size_t)(after_skip - src);
442 if (ansi_len < remaining) {
443 memcpy(dst, src, ansi_len);
444 dst += ansi_len;
445 remaining -= ansi_len;
446 src = after_skip;
447 } else {
448 break;
449 }
450 continue;
451 }
452 }
453
454 // Handle newline
455 if (*src == '\n') {
456 *dst++ = *src++;
457 remaining--;
458 row++;
459 col = 0;
460 continue;
461 }
462
463 // Copy regular characters - inject color for non-colored characters
464 if (remaining > 0) {
465 // Calculate brightness for this position
466 float brightness = get_rain_brightness(rain, col, row, sim_time);
467 float brightness_below = get_rain_brightness(rain, col, row + 1, sim_time);
468 bool is_cursor = brightness > brightness_below;
469
470 // Blend with previous brightness
471 if (!rain->first_frame && row < rain->num_rows && col < rain->num_columns) {
472 size_t idx = (size_t)row * (size_t)rain->num_columns + (size_t)col;
473 float prev_brightness = rain->previous_brightness[idx];
474 brightness = prev_brightness + (brightness - prev_brightness) * rain->brightness_decay;
475 rain->previous_brightness[idx] = brightness;
476 } else if (row < rain->num_rows && col < rain->num_columns) {
477 size_t idx = (size_t)row * (size_t)rain->num_columns + (size_t)col;
478 rain->previous_brightness[idx] = brightness;
479 }
480
481 // For characters without explicit color codes, use the rain's default color
482 char ansi_buf[32];
483 int ansi_len = generate_modulated_color(ansi_buf, sizeof(ansi_buf), rain->color_r, rain->color_g, rain->color_b,
484 brightness, true, is_cursor);
485
486 if ((size_t)ansi_len < remaining) {
487 memcpy(dst, ansi_buf, (size_t)ansi_len);
488 dst += ansi_len;
489 remaining -= (size_t)ansi_len;
490 }
491
492 // Decode UTF-8 character to get byte length
493 uint32_t codepoint;
494 int utf8_len = utf8_decode((const uint8_t *)src, &codepoint);
495 if (utf8_len < 0) {
496 // Invalid UTF-8, treat as single byte
497 utf8_len = 1;
498 }
499
500 // Copy all bytes of the UTF-8 character
501 for (int i = 0; i < utf8_len && *src && remaining > 0; i++) {
502 *dst++ = *src++;
503 remaining--;
504 }
505
506 // Only increment column once per character (not per byte)
507 col++;
508 } else {
509 break;
510 }
511 }
512
513 // Null-terminate
514 if (remaining > 0) {
515 *dst = '\0';
516 } else {
517 output[output_capacity - 1] = '\0';
518 }
519
520 rain->first_frame = false;
521 return output;
522}
int utf8_decode(const uint8_t *s, uint32_t *codepoint)
Decode a UTF-8 sequence to a Unicode codepoint.
Definition utf8.c:18
float time
Current simulation time (accumulated)
float animation_speed
Overall animation speed multiplier.
uint8_t color_b
Matrix green B component.
float brightness_decay
Brightness smoothing factor (0-1)
int num_columns
Number of columns in grid.
bool first_frame
True if this is the first frame.
float * previous_brightness
Previous frame brightness (for smoothing)
bool rainbow_mode
True if rainbow color cycling is enabled.
uint8_t color_g
Matrix green G component.
uint8_t color_r
Matrix green R component.

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

Referenced by mirror_convert_frame(), session_display_convert_to_ascii(), and session_display_write_ascii().

◆ digital_rain_destroy()

void digital_rain_destroy ( digital_rain_t *  rain)

#include <digital_rain.h>

Destroy digital rain effect context.

Parameters
rainDigital rain context to destroy (can be NULL)

Frees all resources associated with the digital rain context. Safe to call with NULL pointer.

Definition at line 154 of file digital_rain.c.

154 {
155 if (!rain) {
156 return;
157 }
158
159 SAFE_FREE(rain->columns);
161 SAFE_FREE(rain);
162}
digital_rain_column_t * columns
Per-column state array.

References digital_rain_t::columns, digital_rain_t::previous_brightness, and SAFE_FREE.

Referenced by cleanup_client_media_buffers(), mirror_cleanup(), mirror_convert_frame(), session_display_convert_to_ascii(), and session_display_destroy().

◆ digital_rain_init()

digital_rain_t * digital_rain_init ( int  num_columns,
int  num_rows 
)

#include <digital_rain.h>

Initialize digital rain effect context.

Parameters
num_columnsNumber of columns in the ASCII grid
num_rowsNumber of rows in the ASCII grid
Returns
Allocated digital rain context, or NULL on error

Creates and initializes a digital rain effect context for the specified grid dimensions. Allocates per-column state and initializes random offsets.

Note
Caller must call digital_rain_destroy() to free resources
Returns NULL on memory allocation failure

Definition at line 96 of file digital_rain.c.

96 {
97 if (num_columns <= 0 || num_rows <= 0) {
98 log_error("digital_rain_init: invalid dimensions %dx%d", num_columns, num_rows);
99 return NULL;
100 }
101
103 if (!rain) {
104 log_error("digital_rain_init: failed to allocate context");
105 return NULL;
106 }
107
108 rain->num_columns = num_columns;
109 rain->num_rows = num_rows;
110
111 // Allocate column state array
112 rain->columns = SAFE_CALLOC((size_t)num_columns, sizeof(digital_rain_column_t), digital_rain_column_t *);
113 if (!rain->columns) {
114 log_error("digital_rain_init: failed to allocate column array");
115 SAFE_FREE(rain);
116 return NULL;
117 }
118
119 // Allocate previous brightness array
120 size_t grid_size = (size_t)num_columns * (size_t)num_rows;
121 rain->previous_brightness = SAFE_CALLOC(grid_size, sizeof(float), float *);
122 if (!rain->previous_brightness) {
123 log_error("digital_rain_init: failed to allocate brightness array");
124 SAFE_FREE(rain->columns);
125 SAFE_FREE(rain);
126 return NULL;
127 }
128
129 // Initialize per-column random offsets
130 for (int col = 0; col < num_columns; col++) {
131 digital_rain_column_t *column = &rain->columns[col];
132 column->time_offset = random_float((float)col, 0.0f) * 1000.0f;
133 column->speed_multiplier = random_float((float)col + 0.1f, 0.0f) * 0.5f + 0.5f;
134 column->phase_offset = random_float((float)col + 0.2f, 0.0f) * (float)M_PI * 2.0f;
135 }
136
137 // Set default parameters
146 rain->rainbow_mode = false;
147 rain->first_frame = true;
148 rain->time = 0.0f;
149
150 log_info("Digital rain initialized: %dx%d grid", num_columns, num_rows);
151 return rain;
152}
#define M_PI
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define DIGITAL_RAIN_DEFAULT_CURSOR_BRIGHTNESS
Cursor brightness multiplier.
#define DIGITAL_RAIN_DEFAULT_BRIGHTNESS_DECAY
Default brightness decay factor.
#define DIGITAL_RAIN_DEFAULT_COLOR_B
#define DIGITAL_RAIN_DEFAULT_COLOR_R
Default Matrix green color (R, G, B)
#define DIGITAL_RAIN_DEFAULT_ANIMATION_SPEED
Default animation speed multiplier.
#define DIGITAL_RAIN_DEFAULT_FALL_SPEED
Default fall speed for raindrops.
#define DIGITAL_RAIN_DEFAULT_RAINDROP_LENGTH
Default raindrop length.
#define DIGITAL_RAIN_DEFAULT_COLOR_G
Digital rain column state.
float phase_offset
Phase offset for wobble variation.
float time_offset
Random time offset for this column (prevents synchronization)
float speed_multiplier
Speed variation multiplier (0.5 to 1.0)
Digital rain effect context.
int num_rows
Number of rows in grid.
float cursor_brightness
Cursor brightness multiplier.
float fall_speed
Base fall speed multiplier.
float raindrop_length
Length of each raindrop (in cells)

References digital_rain_t::animation_speed, digital_rain_t::brightness_decay, digital_rain_t::color_b, digital_rain_t::color_g, digital_rain_t::color_r, digital_rain_t::columns, digital_rain_t::cursor_brightness, DIGITAL_RAIN_DEFAULT_ANIMATION_SPEED, DIGITAL_RAIN_DEFAULT_BRIGHTNESS_DECAY, DIGITAL_RAIN_DEFAULT_COLOR_B, DIGITAL_RAIN_DEFAULT_COLOR_G, DIGITAL_RAIN_DEFAULT_COLOR_R, DIGITAL_RAIN_DEFAULT_CURSOR_BRIGHTNESS, DIGITAL_RAIN_DEFAULT_FALL_SPEED, DIGITAL_RAIN_DEFAULT_RAINDROP_LENGTH, digital_rain_t::fall_speed, digital_rain_t::first_frame, log_error, log_info, M_PI, digital_rain_t::num_columns, digital_rain_t::num_rows, digital_rain_column_t::phase_offset, digital_rain_t::previous_brightness, digital_rain_t::rainbow_mode, digital_rain_t::raindrop_length, SAFE_CALLOC, SAFE_FREE, digital_rain_column_t::speed_multiplier, digital_rain_t::time, and digital_rain_column_t::time_offset.

Referenced by mirror_convert_frame(), session_display_convert_to_ascii(), and session_display_create().

◆ digital_rain_reset()

void digital_rain_reset ( digital_rain_t *  rain)

#include <digital_rain.h>

Reset digital rain effect state.

Parameters
rainDigital rain context (must not be NULL)

Resets the digital rain effect to its initial state, clearing all timing and brightness history. Useful when switching frames or restarting the effect.

Definition at line 164 of file digital_rain.c.

164 {
165 if (!rain) {
166 return;
167 }
168
169 rain->time = 0.0f;
170 rain->first_frame = true;
171
172 // Clear previous brightness
173 size_t grid_size = (size_t)rain->num_columns * (size_t)rain->num_rows;
174 memset(rain->previous_brightness, 0, grid_size * sizeof(float));
175}

References digital_rain_t::first_frame, digital_rain_t::num_columns, digital_rain_t::num_rows, digital_rain_t::previous_brightness, and digital_rain_t::time.

◆ digital_rain_set_color()

void digital_rain_set_color ( digital_rain_t *  rain,
uint8_t  r,
uint8_t  g,
uint8_t  b 
)

#include <digital_rain.h>

Set digital rain color.

Parameters
rainDigital rain context (must not be NULL)
rRed component (0-255)
gGreen component (0-255)
bBlue component (0-255)

Definition at line 193 of file digital_rain.c.

193 {
194 if (rain) {
195 rain->color_r = r;
196 rain->color_g = g;
197 rain->color_b = b;
198 }
199}

References digital_rain_t::color_b, digital_rain_t::color_g, and digital_rain_t::color_r.

Referenced by digital_rain_set_color_from_filter().

◆ digital_rain_set_color_from_filter()

void digital_rain_set_color_from_filter ( digital_rain_t *  rain,
color_filter_t  filter 
)

#include <digital_rain.h>

Set digital rain color from color filter.

Parameters
rainDigital rain context (must not be NULL)
filterColor filter to use for rain color

Automatically sets the rain color based on the active color filter. If filter is COLOR_FILTER_NONE, uses default Matrix green.

Definition at line 201 of file digital_rain.c.

201 {
202 if (!rain) {
203 return;
204 }
205
206 // If no filter is set, use default Matrix green
207 if (filter == COLOR_FILTER_NONE) {
208 rain->rainbow_mode = false;
211 return;
212 }
213
214 // Rainbow mode: enable dynamic color cycling
215 if (filter == COLOR_FILTER_RAINBOW) {
216 rain->rainbow_mode = true;
217 // Set initial color to red (will be overridden during rendering)
218 digital_rain_set_color(rain, 255, 0, 0);
219 return;
220 }
221
222 // Get color from filter metadata
223 rain->rainbow_mode = false;
224 const color_filter_def_t *filter_def = color_filter_get_metadata(filter);
225 if (filter_def) {
226 digital_rain_set_color(rain, filter_def->r, filter_def->g, filter_def->b);
227 }
228}
void digital_rain_set_color(digital_rain_t *rain, uint8_t r, uint8_t g, uint8_t b)
Set digital rain color.
uint8_t b
RGB color values for this filter.

References color_filter_def_t::b, color_filter_get_metadata(), COLOR_FILTER_NONE, COLOR_FILTER_RAINBOW, DIGITAL_RAIN_DEFAULT_COLOR_B, DIGITAL_RAIN_DEFAULT_COLOR_G, DIGITAL_RAIN_DEFAULT_COLOR_R, digital_rain_set_color(), color_filter_def_t::g, color_filter_def_t::r, and digital_rain_t::rainbow_mode.

Referenced by mirror_convert_frame(), session_display_convert_to_ascii(), session_display_create(), and session_display_write_ascii().

◆ digital_rain_set_fall_speed()

void digital_rain_set_fall_speed ( digital_rain_t *  rain,
float  speed 
)

#include <digital_rain.h>

Set digital rain fall speed.

Parameters
rainDigital rain context (must not be NULL)
speedFall speed multiplier (suggested range: 0.1 to 5.0)

Definition at line 181 of file digital_rain.c.

181 {
182 if (rain) {
183 rain->fall_speed = speed;
184 }
185}

References digital_rain_t::fall_speed.

◆ digital_rain_set_raindrop_length()

void digital_rain_set_raindrop_length ( digital_rain_t *  rain,
float  length 
)

#include <digital_rain.h>

Set digital rain raindrop length.

Parameters
rainDigital rain context (must not be NULL)
lengthRaindrop length in cells (suggested range: 5.0 to 50.0)

Definition at line 187 of file digital_rain.c.

187 {
188 if (rain) {
189 rain->raindrop_length = length;
190 }
191}

References digital_rain_t::raindrop_length.

◆ emit_rep()

void emit_rep ( outbuf_t *  ob,
uint32_t  extra 
)

#include <output_buffer.h>

Emit run-length encoded sequence.

Parameters
obOutput buffer structure (must not be NULL)
extraAdditional characters to repeat (must be > 0)

Emits an ANSI run-length encoding sequence that repeats the last character in the buffer 'extra' times. Format: ESC[extram (where 'extra' is the repeat count and the last character is repeated).

Note
This function repeats the character at buffer[len-1].
Useful for compressing repeated characters (spaces, blocks, etc.).
Only profitable for runs exceeding threshold (see rep_is_profitable()).
Example
ob_putc(ob, ' '); // Append space
emit_rep(ob, 49); // Repeat space 49 more times (total 50)
}

Definition at line 156 of file output_buffer.c.

156 {
157 if (!ob)
158 return;
159 // ESC [ extra b
160 ob_putc(ob, 0x1b);
161 ob_putc(ob, '[');
162 ob_u32(ob, extra);
163 ob_putc(ob, 'b');
164}
void ob_u32(outbuf_t *ob, uint32_t v)
Append unsigned 32-bit integer as decimal string.

References ob_putc(), and ob_u32().

Referenced by ansi_compress_rle(), image_print(), rgb_to_16color_halfblocks_scalar(), rgb_to_256color_halfblocks_scalar(), rgb_to_halfblocks_scalar(), and rgb_to_truecolor_halfblocks_scalar().

◆ emit_reset()

void emit_reset ( outbuf_t *  ob)

#include <output_buffer.h>

Emit ANSI reset sequence.

Parameters
obOutput buffer structure (must not be NULL)

Emits ANSI escape sequence to reset all terminal attributes to defaults. Format: ESC[0m or ESC[m. Resets colors, bold, underline, and other formatting attributes.

Note
This resets both foreground and background colors to default.
Sequence length: 3 bytes (ESC[0m).
Useful for resetting terminal state between frames.

Definition at line 138 of file output_buffer.c.

138 {
139 if (!ob)
140 return;
141 ob_putc(ob, 0x1b);
142 ob_putc(ob, '[');
143 ob_putc(ob, '0');
144 ob_putc(ob, 'm');
145}

References ob_putc().

Referenced by rgb_to_16color_halfblocks_scalar(), rgb_to_256color_halfblocks_scalar(), and rgb_to_truecolor_halfblocks_scalar().

◆ emit_set_256_color_bg()

void emit_set_256_color_bg ( outbuf_t *  ob,
uint8_t  color_idx 
)

#include <output_buffer.h>

Emit 256-color background ANSI sequence.

Parameters
obOutput buffer structure (must not be NULL)
color_idx256-color palette index (0-255)

Emits ANSI escape sequence for 256-color background color. Format: ESC[48;5;idxm (where idx is palette index 0-255). Uses extended ANSI color palette for 256-color mode.

Note
Requires terminal 256-color support for proper display.
Sequence length: ~9 bytes (ESC[48;5;255m).
Palette indices 0-15 are standard ANSI colors.
Palette indices 16-255 are extended palette.

Definition at line 177 of file output_buffer.c.

177 {
178 if (!ob)
179 return;
180 // Use pre-cached color sequence instead of building on-demand
181 uint8_t seq_len;
182 const char *seq = get_sgr256_bg_string(color_idx, &seq_len);
183 ob_write(ob, seq, seq_len);
184}
char * get_sgr256_bg_string(uint8_t bg, uint8_t *len_out)
Get 256-color background ANSI sequence string.
Definition sgr.c:142

References get_sgr256_bg_string(), and ob_write().

◆ emit_set_256_color_fg()

void emit_set_256_color_fg ( outbuf_t *  ob,
uint8_t  color_idx 
)

#include <output_buffer.h>

Emit 256-color foreground ANSI sequence.

Parameters
obOutput buffer structure (must not be NULL)
color_idx256-color palette index (0-255)

Emits ANSI escape sequence for 256-color foreground color. Format: ESC[38;5;idxm (where idx is palette index 0-255). Uses extended ANSI color palette for 256-color mode.

Note
Requires terminal 256-color support for proper display.
Sequence length: ~9 bytes (ESC[38;5;255m).
Palette indices 0-15 are standard ANSI colors.
Palette indices 16-255 are extended palette.

Definition at line 167 of file output_buffer.c.

167 {
168 if (!ob)
169 return;
170 // Use pre-cached color sequence instead of building on-demand
171 uint8_t seq_len;
172 const char *seq = get_sgr256_fg_string(color_idx, &seq_len);
173 ob_write(ob, seq, seq_len);
174}
char * get_sgr256_fg_string(uint8_t fg, uint8_t *len_out)
Get 256-color foreground ANSI sequence string.
Definition sgr.c:134

References get_sgr256_fg_string(), and ob_write().

◆ emit_set_bg()

void emit_set_bg ( outbuf_t *  ob,
uint8_t  r,
uint8_t  g,
uint8_t  b 
)

#include <output_buffer.h>

Emit background color sequence (auto-select mode)

Parameters
obOutput buffer structure (must not be NULL)
rRed component (0-255)
gGreen component (0-255)
bBlue component (0-255)

Emits background color ANSI sequence using the optimal color mode based on terminal capabilities. Automatically selects:

  • Truecolor mode (if supported): 24-bit RGB
  • 256-color mode (if supported): Quantized to 256-color palette
  • 16-color mode (fallback): Quantized to standard ANSI colors
Note
Color mode is selected based on terminal_capabilities_t configuration.
This function uses the best available color mode for the terminal.
Color quantization is applied when truecolor is not available.

Definition at line 201 of file output_buffer.c.

201 {
202 if (!ob)
203 return;
204 // ESC[48;2;R;G;Bm
205 ob_putc(ob, 0x1b);
206 ob_putc(ob, '[');
207 ob_write(ob, "48;2;", 5);
208 ob_u8(ob, r);
209 ob_putc(ob, ';');
210 ob_u8(ob, g);
211 ob_putc(ob, ';');
212 ob_u8(ob, b);
213 ob_putc(ob, 'm');
214}
void ob_u8(outbuf_t *ob, uint8_t v)
Append unsigned 8-bit integer as decimal string.

References ob_putc(), ob_u8(), and ob_write().

Referenced by rgb_to_truecolor_halfblocks_scalar().

◆ emit_set_fg()

void emit_set_fg ( outbuf_t *  ob,
uint8_t  r,
uint8_t  g,
uint8_t  b 
)

#include <output_buffer.h>

Emit foreground color sequence (auto-select mode)

Parameters
obOutput buffer structure (must not be NULL)
rRed component (0-255)
gGreen component (0-255)
bBlue component (0-255)

Emits foreground color ANSI sequence using the optimal color mode based on terminal capabilities. Automatically selects:

  • Truecolor mode (if supported): 24-bit RGB
  • 256-color mode (if supported): Quantized to 256-color palette
  • 16-color mode (fallback): Quantized to standard ANSI colors
Note
Color mode is selected based on terminal_capabilities_t configuration.
This function uses the best available color mode for the terminal.
Color quantization is applied when truecolor is not available.

Definition at line 186 of file output_buffer.c.

186 {
187 if (!ob)
188 return;
189 // ESC[38;2;R;G;Bm
190 ob_putc(ob, 0x1b);
191 ob_putc(ob, '[');
192 ob_write(ob, "38;2;", 5);
193 ob_u8(ob, r);
194 ob_putc(ob, ';');
195 ob_u8(ob, g);
196 ob_putc(ob, ';');
197 ob_u8(ob, b);
198 ob_putc(ob, 'm');
199}

References ob_putc(), ob_u8(), and ob_write().

Referenced by rgb_to_truecolor_halfblocks_scalar().

◆ emit_set_truecolor_bg()

void emit_set_truecolor_bg ( outbuf_t *  ob,
uint8_t  r,
uint8_t  g,
uint8_t  b 
)

#include <output_buffer.h>

Emit truecolor background ANSI sequence.

Parameters
obOutput buffer structure (must not be NULL)
rRed component (0-255)
gGreen component (0-255)
bBlue component (0-255)

Emits ANSI escape sequence for 24-bit truecolor background color. Format: ESC[48;2;r;g;bm (where r, g, b are RGB values). Uses 24-bit RGB color values for optimal color accuracy.

Note
Requires terminal truecolor support for proper display.
Sequence length: ~15 bytes (ESC[48;2;255;255;255m).

Definition at line 123 of file output_buffer.c.

123 {
124 if (!ob)
125 return;
126 // ESC[48;2;R;G;Bm
127 ob_putc(ob, 0x1b);
128 ob_putc(ob, '[');
129 ob_write(ob, "48;2;", 5);
130 ob_u8(ob, r);
131 ob_putc(ob, ';');
132 ob_u8(ob, g);
133 ob_putc(ob, ';');
134 ob_u8(ob, b);
135 ob_putc(ob, 'm');
136}

References ob_putc(), ob_u8(), and ob_write().

◆ emit_set_truecolor_fg()

void emit_set_truecolor_fg ( outbuf_t *  ob,
uint8_t  r,
uint8_t  g,
uint8_t  b 
)

#include <output_buffer.h>

Emit truecolor foreground ANSI sequence.

Parameters
obOutput buffer structure (must not be NULL)
rRed component (0-255)
gGreen component (0-255)
bBlue component (0-255)

Emits ANSI escape sequence for 24-bit truecolor foreground color. Format: ESC[38;2;r;g;bm (where r, g, b are RGB values). Uses 24-bit RGB color values for optimal color accuracy.

Note
Requires terminal truecolor support for proper display.
Sequence length: ~15 bytes (ESC[38;2;255;255;255m).

Definition at line 107 of file output_buffer.c.

107 {
108 if (!ob)
109 return;
110 // ESC[38;2;R;G;Bm
111 ob_putc(ob, 0x1b);
112 ob_putc(ob, '[');
113 ob_write(ob, "38;2;", 5);
114 ob_u8(ob, r);
115 ob_putc(ob, ';');
116 ob_u8(ob, g);
117 ob_putc(ob, ';');
118 ob_u8(ob, b);
119 ob_putc(ob, 'm');
120}

References ob_putc(), ob_u8(), and ob_write().

◆ get_256_color_fast_path()

bool get_256_color_fast_path ( void  )

#include <common.h>

Get 256-color fast path setting.

Returns
true if 256-color mode is enabled, false for truecolor

◆ get_lum_palette()

char * get_lum_palette ( void  )

#include <ascii.h>

Get luminance palette for character mapping.

Returns
Pointer to luminance palette array (256 elements, do not free)

Returns the default luminance-to-character mapping palette. The palette contains 256 characters ordered by luminance (dark to light) for mapping pixel brightness values (0-255) to ASCII characters.

Note
Returns pointer to static array (do not free).
Palette has 256 elements (one for each luminance level).
Palette is used for monochrome ASCII conversion.

◆ get_sgr256_bg_string()

char * get_sgr256_bg_string ( uint8_t  bg,
uint8_t *  len_out 
)

#include <common.h>

Get 256-color background ANSI sequence string.

Parameters
bgBackground palette index (0-255)
len_outOutput parameter for sequence length
Returns
Pointer to ANSI sequence string (cached)

Definition at line 142 of file sgr.c.

142 {
143 if (!sgr256_cache_initialized) {
144 init_sgr256_cache();
145 }
146 *len_out = sgr256_bg_cache[bg].len;
147 return sgr256_bg_cache[bg].seq;
148}
char seq[12]
ANSI sequence string (max 11 bytes for "\e[38;5;NNNm")
Definition sgr.c:55
uint8_t len
Length of sequence string.
Definition sgr.c:56

◆ get_sgr256_fg_bg_string()

char * get_sgr256_fg_bg_string ( uint8_t  fg,
uint8_t  bg,
uint8_t *  len_out 
)

#include <common.h>

Get 256-color foreground/background ANSI sequence string.

Parameters
fgForeground palette index (0-255)
bgBackground palette index (0-255)
len_outOutput parameter for sequence length
Returns
Pointer to ANSI sequence string (cached)

Definition at line 150 of file sgr.c.

150 {
151 // For FG+BG, still build on-demand since we'd need 256*256 cache
152 static __thread char buf[32];
153 return build_sgr256_fgbg(buf, fg, bg, len_out);
154}

◆ get_sgr256_fg_string()

char * get_sgr256_fg_string ( uint8_t  fg,
uint8_t *  len_out 
)

#include <common.h>

Get 256-color foreground ANSI sequence string.

Parameters
fg256-color palette index (0-255)
len_outOutput parameter for sequence length
Returns
Pointer to ANSI sequence string (cached)

Definition at line 134 of file sgr.c.

134 {
135 if (!sgr256_cache_initialized) {
136 init_sgr256_cache();
137 }
138 *len_out = sgr256_fg_cache[fg].len;
139 return sgr256_fg_cache[fg].seq;
140}

◆ get_utf8_palette_cache()

utf8_palette_cache_t * get_utf8_palette_cache ( const char *  ascii_chars)

#include <common.h>

Get UTF-8 palette cache for a character set.

Definition at line 270 of file video/ascii/common.c.

270 {
271 if (!ascii_chars)
272 return NULL;
273
274 // Check for empty string
275 if (ascii_chars[0] == '\0')
276 return NULL;
277
278 // Create hash of palette for cache key
279 uint32_t palette_hash = hash_palette_string(ascii_chars);
280
281 // Ensure the cache system is initialized
282 init_utf8_cache_system();
283
284 // First try: read lock for cache lookup (allows multiple concurrent readers)
285 rwlock_rdlock(&g_utf8_cache_rwlock);
286
287 // Check if cache exists
288 utf8_palette_cache_t *cache = NULL;
289 HASH_FIND_INT(g_utf8_cache_table, &palette_hash, cache);
290 if (cache) {
291 // Cache hit: Update access tracking (atomics are thread-safe under rdlock)
292 uint64_t current_time = time_get_ns();
293 atomic_store_u64(&cache->last_access_time, current_time);
294 uint32_t new_access_count = atomic_fetch_add_u64(&cache->access_count, 1) + 1;
295
296 // Every 10th access: Update heap position (requires write lock)
297 if (new_access_count % 10 == 0) {
298 // Save the key before releasing read lock
299 uint32_t saved_key = cache->key;
300
301 // Release read lock and upgrade to write lock
302 rwlock_rdunlock(&g_utf8_cache_rwlock);
303 rwlock_wrlock(&g_utf8_cache_rwlock);
304
305 // Re-lookup cache entry after lock upgrade
306 // Another thread could have evicted this entry while we were upgrading locks
307 utf8_palette_cache_t *cache_relookup = NULL;
308 HASH_FIND_INT(g_utf8_cache_table, &saved_key, cache_relookup);
309 if (cache_relookup) {
310 // Cache entry still exists - safe to update heap position
311 uint64_t last_access = atomic_load_u64(&cache_relookup->last_access_time);
312 uint32_t access_count = atomic_load_u64(&cache_relookup->access_count);
313 double new_score =
314 calculate_cache_eviction_score(last_access, access_count, cache_relookup->creation_time, current_time);
315 utf8_heap_update_score(cache_relookup, new_score);
316 }
317 // If cache_relookup is NULL, entry was evicted - skip update (not an error)
318
319 rwlock_wrunlock(&g_utf8_cache_rwlock);
320 } else {
321 rwlock_rdunlock(&g_utf8_cache_rwlock);
322 }
323
324 return cache;
325 }
326
327 // Cache miss: Need to create new entry
328 // Release read lock and acquire write lock
329 rwlock_rdunlock(&g_utf8_cache_rwlock);
330 rwlock_wrlock(&g_utf8_cache_rwlock);
331
332 // Double-check: another thread might have created it while we upgraded locks
333 cache = NULL;
334 HASH_FIND_INT(g_utf8_cache_table, &palette_hash, cache);
335 if (cache) {
336 // Found it! Just update access tracking and return
337 uint64_t current_time = time_get_ns();
338 atomic_store_u64(&cache->last_access_time, current_time);
340 rwlock_wrunlock(&g_utf8_cache_rwlock);
341 return cache;
342 }
343
344 // Create new cache entry (holding write lock)
346 if (!cache) {
347 rwlock_wrunlock(&g_utf8_cache_rwlock);
348 return NULL;
349 }
350 memset(cache, 0, sizeof(utf8_palette_cache_t));
351
352 // Set the key for uthash
353 cache->key = palette_hash;
354
355 // Build both cache types
356 build_utf8_luminance_cache(ascii_chars, cache->cache);
357 build_utf8_ramp64_cache(ascii_chars, cache->cache64, cache->char_index_ramp);
358
359 // Store palette hash for validation
360 SAFE_STRNCPY(cache->palette_hash, ascii_chars, sizeof(cache->palette_hash));
361 cache->is_valid = true;
362
363 // Initialize eviction tracking
364 uint64_t current_time = time_get_ns();
365 atomic_store_u64(&cache->last_access_time, current_time);
366 atomic_store_u64(&cache->access_count, 1); // First access
367 cache->creation_time = current_time;
368
369 // Store in hash table with guaranteed eviction support
370 try_insert_with_eviction_utf8(palette_hash, cache);
371
372 // WASM: Skip debug log to avoid blocking render loop on main thread
373 // log_debug("UTF8_CACHE: Created new cache for palette='%s' (hash=0x%x)", ascii_chars, palette_hash);
374
375 rwlock_wrunlock(&g_utf8_cache_rwlock);
376 return cache;
377}
uint64_t atomic_fetch_add_u64(atomic_t *a, uint64_t delta)
Atomically add to a uint64_t and return the previous value.
Definition atomic.c:248
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:349
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:294
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:313
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:331
void build_utf8_luminance_cache(const char *ascii_chars, utf8_char_t cache[256])
Build UTF-8 luminance cache for all 256 levels.
utf8_char_t cache64[64]
void build_utf8_ramp64_cache(const char *ascii_chars, utf8_char_t cache64[64], uint8_t char_index_ramp[256])
Build UTF-8 ramp64 cache and character index ramp.
utf8_char_t cache[256]
double calculate_cache_eviction_score(uint64_t last_access_time, uint32_t access_count, uint64_t creation_time, uint64_t current_time)

References utf8_palette_cache::access_count, atomic_fetch_add_u64(), atomic_load_u64(), atomic_store_u64(), build_utf8_luminance_cache(), build_utf8_ramp64_cache(), utf8_palette_cache::cache, utf8_palette_cache::cache64, calculate_cache_eviction_score(), utf8_palette_cache::char_index_ramp, utf8_palette_cache::creation_time, utf8_palette_cache::is_valid, utf8_palette_cache::key, utf8_palette_cache::last_access_time, utf8_palette_cache::palette_hash, rwlock_rdlock, rwlock_rdunlock, rwlock_wrlock, rwlock_wrunlock, SAFE_MALLOC, SAFE_STRNCPY, and time_get_ns().

Referenced by image_print(), image_print_16color(), image_print_16color_dithered(), image_print_16color_dithered_with_background(), image_print_256color(), image_print_color(), image_print_color_background(), image_print_color_utf8(), and session_display_create().

◆ image_clear()

void image_clear ( image_t *  p)

#include <image.h>

Clear image (set all pixels to black)

Parameters
pImage to clear (must not be NULL)

Sets all pixels in the image to black (RGB = 0, 0, 0). Useful for resetting image state before filling with new data.

Note
Image dimensions are not modified (only pixel data is cleared).
This is equivalent to memset() of pixels array to zero.

Definition at line 258 of file video/rgba/image.c.

258 {
259 if (!p || !p->pixels) {
260 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: p or p->pixels is NULL");
261 return;
262 }
263 // Check for integer overflow before multiplication.
264 unsigned long w_ul = (unsigned long)p->w;
265 unsigned long h_ul = (unsigned long)p->h;
266 if (w_ul > 0 && h_ul > ULONG_MAX / w_ul) {
267 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: dimensions overflow: %d x %d", p->w, p->h);
268 return;
269 }
270 unsigned long pixel_count = w_ul * h_ul;
271 if (pixel_count > ULONG_MAX / sizeof(rgb_pixel_t)) {
272 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: buffer size overflow");
273 return;
274 }
275 size_t clear_size = pixel_count * sizeof(rgb_pixel_t);
276 SAFE_MEMSET(p->pixels, clear_size, 0, clear_size);
277}

References ERROR_INVALID_PARAM, image_t::h, image_t::pixels, SAFE_MEMSET, SET_ERRNO, and image_t::w.

Referenced by ascii_convert(), and ascii_convert_with_capabilities().

◆ image_destroy()

void image_destroy ( image_t *  p)

#include <image.h>

Destroy an image allocated with image_new()

Parameters
pImage to destroy (can be NULL, no-op if NULL)

Frees an image structure and its associated pixel data array allocated by image_new(). Frees both the image structure and the pixels array.

Note
Safe to call with NULL pointer (no-op).
Must NOT be used for images allocated with image_new_from_pool().
Use image_destroy_to_pool() for pool-allocated images.

Definition at line 140 of file video/rgba/image.c.

140 {
141 if (!p) {
142 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy: p is NULL");
143 return;
144 }
145
146 // Check allocation method and deallocate appropriately
147 if (p->alloc_method == IMAGE_ALLOC_POOL) {
148 // Pool-allocated images: free entire contiguous buffer (image + pixels)
149 // Validate dimensions before calculating size (guard against corruption)
150 if (p->w <= 0 || p->h <= 0) {
151 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy: invalid dimensions %dx%d (pool-allocated)", p->w, p->h);
152 return;
153 }
154
155 // Calculate original allocation size for proper buffer pool free
156 size_t w = (size_t)p->w;
157 size_t h = (size_t)p->h;
158 size_t pixels_size;
159 if (checked_size_mul3(w, h, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
160 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy: dimensions would overflow: %dx%d", p->w, p->h);
161 return;
162 }
163 size_t total_size;
164 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
165 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy: total size would overflow: %dx%d", p->w, p->h);
166 return;
167 }
168
169 // Free the entire contiguous buffer back to pool
170 buffer_pool_free(NULL, p, total_size);
171 } else {
172 // SIMD-allocated images: free pixels and structure separately
173 // SAFE_MALLOC_SIMD allocates with aligned allocation (posix_memalign on macOS, aligned_alloc on Linux)
174 // These can be freed with standard free() / SAFE_FREE()
175 SAFE_FREE(p->pixels);
176 SAFE_FREE(p);
177 }
178}
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
@ ERROR_INVALID_STATE
uint8_t alloc_method
Allocation method (image_alloc_method_t) for correct deallocation.

References image_t::alloc_method, ASCIICHAT_OK, buffer_pool_free(), ERROR_INVALID_PARAM, ERROR_INVALID_STATE, image_t::h, IMAGE_ALLOC_POOL, image_t::pixels, SAFE_FREE, SET_ERRNO, and image_t::w.

Referenced by ascii_convert(), ascii_convert_with_capabilities(), create_mixed_ascii_frame_for_client(), ffmpeg_decoder_destroy(), media_source_destroy(), session_display_convert_to_ascii(), session_host_add_client(), session_host_add_memory_participant(), session_host_destroy(), session_host_inject_frame(), and session_host_remove_client().

◆ image_destroy_to_pool()

void image_destroy_to_pool ( image_t *  image)

#include <image.h>

Destroy an image allocated from buffer pool.

Parameters
imageImage to destroy (can be NULL, no-op if NULL)

Returns an image structure and its pixel data to the buffer pool for reuse. This is faster than standard free() because buffers are reused instead of being deallocated.

Note
Safe to call with NULL pointer (no-op).
Must NOT be used for images allocated with image_new().
Use image_destroy() for standard-allocated images.

Definition at line 226 of file video/rgba/image.c.

226 {
227 if (!image) {
228 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy_to_pool: image is NULL");
229 return;
230 }
231
232 // Validate dimensions before calculating size (guard against corruption)
233 if (image->w <= 0 || image->h <= 0) {
234 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy_to_pool: invalid dimensions %dx%d", image->w, image->h);
235 return;
236 }
237
238 // Calculate original allocation size for proper buffer pool free
239 // Check for overflow (defensive - should match original allocation)
240 size_t w = (size_t)image->w;
241 size_t h = (size_t)image->h;
242 size_t pixels_size;
243 if (checked_size_mul3(w, h, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
244 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy_to_pool: dimensions would overflow: %dx%d", image->w, image->h);
245 return;
246 }
247 size_t total_size;
248 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
249 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy_to_pool: total size would overflow: %dx%d", image->w, image->h);
250 return;
251 }
252
253 // Free the entire contiguous buffer back to pool
254 buffer_pool_free(NULL, image, total_size);
255 // Note: Don't set image = NULL here since caller owns the pointer
256}

References ASCIICHAT_OK, buffer_pool_free(), ERROR_INVALID_PARAM, ERROR_INVALID_STATE, image_t::h, SET_ERRNO, and image_t::w.

Referenced by create_mixed_ascii_frame_for_client().

◆ image_new()

image_t * image_new ( size_t  width,
size_t  height 
)

#include <image.h>

Create a new image with standard allocation.

Parameters
widthImage width in pixels (must be > 0 and <= IMAGE_MAX_WIDTH)
heightImage height in pixels (must be > 0 and <= IMAGE_MAX_HEIGHT)
Returns
Allocated image structure, or NULL on error

Allocates a new image structure and pixel data array using standard malloc() allocation. Image is initialized with specified dimensions and all pixels set to black (0, 0, 0).

Note
Caller must free with image_destroy() when done.
Returns NULL on error (invalid dimensions, memory allocation failure).
Pixel data is allocated separately and freed automatically on destroy.
Example
image_t *img = image_new(1920, 1080);
if (img) {
// Use image...
}

Definition at line 91 of file video/rgba/image.c.

91 {
92 image_t *p;
93
94 p = SAFE_MALLOC(sizeof(image_t), image_t *);
95
96 // Validate dimensions are non-zero and within bounds
97 if (image_validate_dimensions(width, height) != ASCIICHAT_OK) {
98 SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions invalid or too large: %zu x %zu", width, height);
99 SAFE_FREE(p);
100 return NULL;
101 }
102
103 // Calculate pixel count with overflow checking
104 size_t total_pixels;
105 if (checked_size_mul(width, height, &total_pixels) != ASCIICHAT_OK) {
106 SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions would cause overflow: %zu x %zu", width, height);
107 SAFE_FREE(p);
108 return NULL;
109 }
110
111 // Calculate total pixel buffer size with overflow checking
112 size_t pixels_size;
113 if (checked_size_mul(total_pixels, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
114 SET_ERRNO(ERROR_INVALID_PARAM, "Image pixel buffer size would cause overflow");
115 SAFE_FREE(p);
116 return NULL;
117 }
118
119 if (pixels_size > IMAGE_MAX_PIXELS_SIZE) {
120 SET_ERRNO(ERROR_INVALID_PARAM, "Image size exceeds maximum allowed: %zu x %zu (%zu bytes)", width, height,
121 pixels_size);
122 SAFE_FREE(p);
123 return NULL;
124 }
125
126 // Use SIMD-aligned allocation for optimal NEON/AVX performance with vld3q_u8
127 p->pixels = SAFE_MALLOC_SIMD(pixels_size, rgb_pixel_t *);
128 if (!p->pixels) {
129 SET_ERRNO(ERROR_MEMORY, "Failed to allocate image pixels: %zu bytes", pixels_size);
130 SAFE_FREE(p);
131 return NULL;
132 }
133
134 p->w = (int)width;
135 p->h = (int)height;
136 p->alloc_method = IMAGE_ALLOC_SIMD; // Track allocation method for correct deallocation
137 return p;
138}
#define SAFE_MALLOC_SIMD(size, cast)
Definition common.h:364
@ ERROR_MEMORY
Definition error_codes.h:56
#define IMAGE_MAX_PIXELS_SIZE
Maximum pixel data size in bytes.
asciichat_error_t image_validate_dimensions(size_t width, size_t height)
Validate image dimensions (non-zero, within limits)
Definition video.c:12

References image_t::alloc_method, ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_MEMORY, image_t::h, IMAGE_ALLOC_SIMD, IMAGE_MAX_PIXELS_SIZE, image_validate_dimensions(), image_t::pixels, SAFE_FREE, SAFE_MALLOC, SAFE_MALLOC_SIMD, SET_ERRNO, and image_t::w.

Referenced by ascii_convert(), ascii_convert_with_capabilities(), ffmpeg_decoder_create(), ffmpeg_decoder_create_stdin(), image_new_copy(), session_capture_process_for_transmission(), session_display_convert_to_ascii(), session_host_add_client(), session_host_add_memory_participant(), and session_host_inject_frame().

◆ image_new_copy()

image_t * image_new_copy ( const image_t *  source)

#include <image.h>

Create a copy of an existing image.

Parameters
sourceSource image to copy (must not be NULL)
Returns
Newly allocated copy of the image, or NULL on error

Creates a deep copy of an image, allocating new memory for the copy. The source and destination images are completely independent - modifying one does not affect the other. Useful for creating snapshots to prevent concurrent modification issues.

Note
Returns NULL on error (invalid image, memory allocation failure).
Caller must free the returned image with image_destroy().
Copy is allocated with standard malloc() allocation.
Pixels are fully copied (not shared with source).

Definition at line 279 of file video/rgba/image.c.

279 {
280 if (!source) {
281 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_copy: source is NULL");
282 return NULL;
283 }
284
285 // Create new image with same dimensions
286 image_t *copy = image_new((size_t)source->w, (size_t)source->h);
287 if (!copy) {
288 return NULL;
289 }
290
291 // Copy pixel data from source to copy
292 if (source->pixels && copy->pixels) {
293 size_t pixel_count = (size_t)source->w * (size_t)source->h;
294 size_t pixels_size = pixel_count * sizeof(rgb_pixel_t);
295 memcpy(copy->pixels, source->pixels, pixels_size);
296 }
297
298 return copy;
299}

References ERROR_INVALID_PARAM, image_t::h, image_new(), image_t::pixels, SET_ERRNO, and image_t::w.

Referenced by create_mixed_ascii_frame_for_client().

◆ image_new_from_pool()

image_t * image_new_from_pool ( size_t  width,
size_t  height 
)

#include <image.h>

Create a new image from buffer pool.

Parameters
widthImage width in pixels (must be > 0 and <= IMAGE_MAX_WIDTH)
heightImage height in pixels (must be > 0 and <= IMAGE_MAX_HEIGHT)
Returns
Allocated image structure from pool, or NULL on error

Allocates a new image structure and pixel data from the buffer pool for efficient memory management in video pipeline. Buffer pool reduces allocation overhead and improves performance for high-frequency operations (video frame capture/processing).

Note
Caller must free with image_destroy_to_pool() when done.
Returns NULL on error (invalid dimensions, pool exhaustion).
Buffer pool allocation is faster than standard malloc().
Example
image_t *img = image_new_from_pool(1920, 1080);
if (img) {
// Process video frame...
}
void image_destroy_to_pool(image_t *image)
Destroy an image allocated from buffer pool.
image_t * image_new_from_pool(size_t width, size_t height)
Create a new image from buffer pool.

Definition at line 181 of file video/rgba/image.c.

181 {
182 if (width == 0 || height == 0) {
183 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: invalid dimensions %zux%zu", width, height);
184 return NULL;
185 }
186
187 if (width > IMAGE_MAX_WIDTH || height > IMAGE_MAX_HEIGHT) {
188 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: dimensions %zux%zu exceed maximum %ux%u", width, height,
190 return NULL;
191 }
192
193 // Calculate total allocation size (structure + pixel data in single buffer)
194 // Check for integer overflow before multiplication
195 size_t pixels_size;
196 if (checked_size_mul3(width, height, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
197 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: dimensions would overflow: %zux%zu", width, height);
198 return NULL;
199 }
200
201 size_t total_size;
202 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
203 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: total size would overflow");
204 return NULL;
205 }
206
207 // Allocate from buffer pool as single contiguous block
208 void *buffer = buffer_pool_alloc(NULL, total_size);
209 if (!buffer) {
210 SET_ERRNO(ERROR_MEMORY, "image_new_from_pool: buffer pool allocation failed for %zu bytes (%zux%zu)", total_size,
211 width, height);
212 return NULL;
213 }
214
215 // Set up image structure at start of buffer
216 image_t *image = (image_t *)buffer;
217 image->w = (int)width;
218 image->h = (int)height;
219 // Pixel data immediately follows the image structure
220 image->pixels = (rgb_pixel_t *)((uint8_t *)buffer + sizeof(image_t));
221 image->alloc_method = IMAGE_ALLOC_POOL; // Track allocation method for correct deallocation
222
223 return image;
224}
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
#define IMAGE_MAX_WIDTH
Maximum image width (4K resolution)
#define IMAGE_MAX_HEIGHT
Maximum image height (4K resolution)

References image_t::alloc_method, ASCIICHAT_OK, buffer_pool_alloc(), ERROR_INVALID_PARAM, ERROR_MEMORY, image_t::h, IMAGE_ALLOC_POOL, IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, image_t::pixels, SET_ERRNO, and image_t::w.

◆ image_print()

char * image_print ( const image_t *  p,
const char *  palette 
)

#include <image.h>

Print image as ASCII art (monochrome)

Parameters
pImage to print (must not be NULL)
paletteCharacter palette to use (or NULL for default ASCII palette)
Returns
Allocated ASCII string (caller must free), or NULL on error

Converts an image to ASCII art without color output. Uses character palette to map pixel luminance to ASCII characters. Returns a null-terminated string containing the ASCII art frame.

Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
Character palette is used to map luminance to characters.
ASCII output has width*height characters (one character per pixel).

Definition at line 27 of file foreground.c.

27 {
28 if (!p || !palette) {
29 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: p=%p or palette=%p is NULL", p, palette);
30 return NULL;
31 }
32 if (!p->pixels) {
33 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: p->pixels is NULL");
34 return NULL;
35 }
36
37 const int h = p->h;
38 const int w = p->w;
39
40 if (h <= 0 || w <= 0) {
41 SET_ERRNO(ERROR_INVALID_PARAM, "image_print: invalid dimensions h=%d, w=%d", h, w);
42 return NULL;
43 }
44
45 // Get UTF-8 character cache for proper multi-byte character support
46 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
47 if (!utf8_cache) {
48 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 palette cache for scalar rendering");
49 return NULL;
50 }
51
52 // Character index ramp is now part of UTF-8 cache - no separate cache needed
53
54 // Need space for h rows with UTF-8 characters, plus h-1 newlines, plus null terminator
55 const size_t max_char_bytes = 4; // Max UTF-8 character size
56
57 const rgb_pixel_t *pix = p->pixels;
58
59 // Use outbuf_t for efficient UTF-8 RLE emission (same as SIMD renderers)
60 outbuf_t ob = {0};
61
62 // Calculate buffer size with overflow checking
63 size_t w_times_bytes;
64 if (checked_size_mul((size_t)w, max_char_bytes, &w_times_bytes) != ASCIICHAT_OK) {
65 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: width too large for UTF-8 encoding");
66 return NULL;
67 }
68
69 size_t w_times_bytes_plus_one;
70 if (checked_size_add(w_times_bytes, 1, &w_times_bytes_plus_one) != ASCIICHAT_OK) {
71 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: width * bytes + 1 overflow");
72 return NULL;
73 }
74
75 if (checked_size_mul((size_t)h, w_times_bytes_plus_one, &ob.cap) != ASCIICHAT_OK) {
76 SET_ERRNO(ERROR_INVALID_PARAM, "Buffer size overflow: height * (width * bytes + 1) overflow");
77 return NULL;
78 }
79
80 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
81 if (!ob.buf) {
82 SET_ERRNO(ERROR_MEMORY, "Failed to allocate output buffer for scalar rendering");
83 return NULL;
84 }
85
86 // Process pixels with UTF-8 RLE emission (same approach as SIMD)
87 for (int y = 0; y < h; y++) {
88 const int row_offset = y * w;
89
90 for (int x = 0; x < w;) {
91 const rgb_pixel_t pixel = pix[row_offset + x];
92 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
93 const int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
94
95 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
96 uint8_t safe_luminance = clamp_rgb(luminance);
97 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
98 uint8_t char_idx = utf8_cache->char_index_ramp[luma_idx]; // Map to character index (same as SIMD)
99
100 // cache64 is indexed by luminance bucket; char_idx is only for run matching.
101 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
102
103 // Find run length for same character (RLE optimization)
104 int j = x + 1;
105 while (j < w) {
106 const rgb_pixel_t next_pixel = pix[row_offset + j];
107 const int next_luminance = (77 * next_pixel.r + 150 * next_pixel.g + 29 * next_pixel.b + 128) >> 8;
108 uint8_t next_safe_luminance = clamp_rgb(next_luminance);
109 uint8_t next_luma_idx = (uint8_t)(next_safe_luminance >> 2); // 0-63 index (same as SIMD)
110 uint8_t next_char_idx = utf8_cache->char_index_ramp[next_luma_idx]; // Map to character index (same as SIMD)
111 if (next_char_idx != char_idx)
112 break;
113 j++;
114 }
115 uint32_t run = (uint32_t)(j - x);
116
117 // Emit UTF-8 character with RLE (same as SIMD)
118 ob_write(&ob, (const char *)char_info->utf8_bytes, char_info->byte_len);
119 if (rep_is_profitable(run)) {
120 emit_rep(&ob, run - 1);
121 } else {
122 for (uint32_t k = 1; k < run; k++) {
123 ob_write(&ob, (const char *)char_info->utf8_bytes, char_info->byte_len);
124 }
125 }
126 x = j;
127 }
128
129 // Add newline between rows (except last row)
130 if (y != h - 1) {
131 ob_putc(&ob, '\n');
132 }
133 }
134
135 ob_term(&ob);
136 return ob.buf;
137}
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)
Get UTF-8 palette cache for a character set.
size_t cap
Buffer capacity in bytes (maximum length before reallocation)

References ASCIICHAT_OK, rgb_pixel_t::b, outbuf_t::buf, utf8_char_t::byte_len, utf8_palette_cache::cache64, outbuf_t::cap, utf8_palette_cache::char_index_ramp, emit_rep(), ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, rgb_pixel_t::g, get_utf8_palette_cache(), image_t::h, ob_putc(), ob_term(), ob_write(), image_t::pixels, rgb_pixel_t::r, rep_is_profitable(), SAFE_MALLOC, SET_ERRNO, utf8_char_t::utf8_bytes, and image_t::w.

Referenced by ascii_convert(), and image_print_with_capabilities().

◆ image_print_16color()

char * image_print_16color ( const image_t *  image,
const char *  palette 
)

#include <image.h>

Print image using 16-color ANSI mode.

Parameters
imageImage to print (must not be NULL)
paletteCharacter palette to use (or NULL for default ASCII palette)
Returns
Allocated ASCII string with 16-color ANSI codes (caller must free), or NULL on error

Converts an image to ASCII art using 16-color ANSI mode. Uses standard ANSI color palette (0-15) for color output. Colors are quantized to 16-color palette.

Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
Requires terminal 16-color support for proper display.
Colors are quantized to 16-color palette automatically.

Definition at line 533 of file foreground.c.

533 {
534 if (!image || !image->pixels || !palette) {
535 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
536 return NULL;
537 }
538
539 int h = image->h;
540 int w = image->w;
541
542 if (h <= 0 || w <= 0) {
543 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color: invalid dimensions h=%d, w=%d", h, w);
544 return NULL;
545 }
546
547 // Initialize 16-color lookup table
549
550 // Calculate buffer size (smaller than 256-color due to shorter ANSI sequences)
551 // Space for ANSI codes + newlines
552 // Calculate with overflow checking
553 size_t h_times_w;
554 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w) != ASCIICHAT_OK) {
555 return NULL;
556 }
557
558 size_t h_times_w_times_12;
559 if (checked_size_mul(h_times_w, 12u, &h_times_w_times_12) != ASCIICHAT_OK) {
560 return NULL;
561 }
562
563 size_t buffer_size;
564 if (checked_size_add(h_times_w_times_12, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
565 return NULL;
566 }
567
568 char *buffer;
569 buffer = SAFE_MALLOC(buffer_size, char *);
570
571 char *ptr = buffer;
572 const char *reset_code = "\033[0m";
573
574 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
575 if (!utf8_cache) {
576 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache for 16-color rendering");
577 SAFE_FREE(buffer);
578 return NULL;
579 }
580
581 for (int y = 0; y < h; y++) {
582 for (int x = 0; x < w; x++) {
583 rgb_pixel_t pixel = image->pixels[y * w + x];
584
585 // Convert RGB to 16-color index and generate ANSI sequence
586 uint8_t color_index = rgb_to_16color(pixel.r, pixel.g, pixel.b);
587 ptr = append_16color_fg(ptr, color_index);
588
589 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
590 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
591
592 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
593 uint8_t safe_luminance = clamp_rgb(luminance);
594 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
595 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
596
597 if (char_info) {
598 // Copy UTF-8 character bytes
599 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
600 *ptr++ = char_info->utf8_bytes[byte_idx];
601 }
602 } else {
603 // Fallback to simple ASCII if UTF-8 cache fails
604 size_t palette_len = strlen(palette);
605 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
606 *ptr++ = palette[palette_index];
607 }
608 }
609
610 // Add reset and newline at end of each row
611 SAFE_STRNCPY(ptr, reset_code, 5);
612 ptr += strlen(reset_code);
613 if (y < h - 1) {
614 *ptr++ = '\n';
615 }
616 }
617
618 *ptr = '\0';
619 return buffer;
620}
int buffer_size
Size of circular buffer.
Definition grep.c:90
char * append_16color_fg(char *dst, uint8_t color_index)
Definition ansi.c:405
uint8_t rgb_to_16color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:437
void ansi_fast_init_16color(void)
Definition ansi.c:398

References ansi_fast_init_16color(), append_16color_fg(), ASCIICHAT_OK, rgb_pixel_t::b, buffer_size, utf8_char_t::byte_len, utf8_palette_cache::cache64, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, rgb_pixel_t::g, get_utf8_palette_cache(), image_t::h, image_t::pixels, rgb_pixel_t::r, rgb_to_16color(), SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, SET_ERRNO, utf8_char_t::utf8_bytes, and image_t::w.

Referenced by image_print_with_capabilities().

◆ image_print_16color_dithered()

char * image_print_16color_dithered ( const image_t *  image,
const char *  palette 
)

#include <image.h>

Print image using 16-color ANSI mode with dithering.

Parameters
imageImage to print (must not be NULL)
paletteCharacter palette to use (or NULL for default ASCII palette)
Returns
Allocated ASCII string with dithered 16-color ANSI codes (caller must free), or NULL on error

Converts an image to ASCII art using 16-color ANSI mode with Floyd-Steinberg dithering. Dithering improves color accuracy by distributing quantization errors across neighboring pixels.

DITHERING:

  • Floyd-Steinberg error diffusion algorithm
  • Distributes quantization errors to adjacent pixels
  • Improves perceived color accuracy in 16-color mode
  • Slight performance overhead compared to non-dithered mode
Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
Dithering improves color accuracy but may add noise.
Requires terminal 16-color support for proper display.

Definition at line 646 of file foreground.c.

646 {
647 if (!image || !image->pixels || !palette) {
648 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
649 return NULL;
650 }
651
652 int h = image->h;
653 int w = image->w;
654
655 if (h <= 0 || w <= 0) {
656 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color_dithered: invalid dimensions h=%d, w=%d", h, w);
657 return NULL;
658 }
659
660 // Initialize 16-color lookup table
662
663 // Allocate error buffer for Floyd-Steinberg dithering
664 size_t pixel_count = (size_t)h * (size_t)w;
665 rgb_error_t *error_buffer;
666 error_buffer = SAFE_CALLOC(pixel_count, sizeof(rgb_error_t), rgb_error_t *);
667
668 // Calculate buffer size (same as non-dithered version)
669 // Space for ANSI codes + newlines
670 // Calculate with overflow checking
671 size_t h_times_w2;
672 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w2) != ASCIICHAT_OK) {
673 SAFE_FREE(error_buffer);
674 return NULL;
675 }
676
677 size_t h_times_w2_times_12;
678 if (checked_size_mul(h_times_w2, 12u, &h_times_w2_times_12) != ASCIICHAT_OK) {
679 SAFE_FREE(error_buffer);
680 return NULL;
681 }
682
683 size_t buffer_size;
684 if (checked_size_add(h_times_w2_times_12, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
685 SAFE_FREE(error_buffer);
686 return NULL;
687 }
688
689 char *buffer;
690 buffer = SAFE_MALLOC(buffer_size, char *);
691
692 char *ptr = buffer;
693 const char *reset_code = "\033[0m";
694
695 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
696 if (!utf8_cache) {
697 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache for 16-color dithered rendering");
698 SAFE_FREE(buffer);
699 SAFE_FREE(error_buffer);
700 return NULL;
701 }
702
703 for (int y = 0; y < h; y++) {
704 for (int x = 0; x < w; x++) {
705 rgb_pixel_t pixel = image->pixels[y * w + x];
706
707 // Convert RGB to 16-color index using dithering
708 uint8_t color_index = rgb_to_16color_dithered(pixel.r, pixel.g, pixel.b, x, y, w, h, error_buffer);
709 ptr = append_16color_fg(ptr, color_index);
710
711 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
712 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
713
714 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
715 uint8_t safe_luminance = clamp_rgb(luminance);
716 uint8_t luma_idx = (uint8_t)(safe_luminance >> 2); // 0-63 index (same as SIMD)
717 const utf8_char_t *char_info = &utf8_cache->cache64[luma_idx];
718
719 if (char_info) {
720 // Copy UTF-8 character bytes
721 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
722 *ptr++ = char_info->utf8_bytes[byte_idx];
723 }
724 } else {
725 // Fallback to simple ASCII if UTF-8 cache fails
726 size_t palette_len = strlen(palette);
727 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
728 *ptr++ = palette[palette_index];
729 }
730 }
731
732 // Add reset and newline at end of each row
733 SAFE_STRNCPY(ptr, reset_code, 5);
734 ptr += strlen(reset_code);
735 if (y < h - 1) {
736 *ptr++ = '\n';
737 }
738 }
739
740 *ptr = '\0';
741 SAFE_FREE(error_buffer); // Clean up error buffer
742 return buffer;
743}
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)
Definition ansi.c:511

References ansi_fast_init_16color(), append_16color_fg(), ASCIICHAT_OK, rgb_pixel_t::b, buffer_size, utf8_char_t::byte_len, utf8_palette_cache::cache64, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, rgb_pixel_t::g, get_utf8_palette_cache(), image_t::h, image_t::pixels, rgb_pixel_t::r, rgb_to_16color_dithered(), SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, SET_ERRNO, utf8_char_t::utf8_bytes, and image_t::w.

◆ image_print_16color_dithered_with_background()

char * image_print_16color_dithered_with_background ( const image_t *  image,
bool  use_background,
const char *  palette 
)

#include <image.h>

Print image using 16-color ANSI mode with dithering and background colors.

Parameters
imageImage to print (must not be NULL)
use_backgroundWhether to use background colors (true) or foreground colors only (false)
paletteCharacter palette to use (or NULL for default ASCII palette)
Returns
Allocated ASCII string with dithered 16-color ANSI codes (caller must free), or NULL on error

Converts an image to ASCII art using 16-color ANSI mode with Floyd-Steinberg dithering and optional background color support. Background colors provide better color coverage for block-style rendering.

RENDERING MODES:

  • Foreground colors: Character color is set (text color)
  • Background colors: Character background is set (block color)
  • Background mode provides better color coverage for block characters
Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
Background colors require terminal background color support.
Dithering improves color accuracy but may add noise.

Definition at line 746 of file foreground.c.

746 {
747 if (!image || !image->pixels || !palette) {
748 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
749 return NULL;
750 }
751
752 int h = image->h;
753 int w = image->w;
754
755 if (h <= 0 || w <= 0) {
756 SET_ERRNO(ERROR_INVALID_STATE, "image_print_16color_dithered_with_background: invalid dimensions h=%d, w=%d", h, w);
757 return NULL;
758 }
759
760 // Initialize 16-color lookup table
762
763 // Allocate error buffer for Floyd-Steinberg dithering
764 size_t pixel_count = (size_t)h * (size_t)w;
765 rgb_error_t *error_buffer;
766 error_buffer = SAFE_CALLOC(pixel_count, sizeof(rgb_error_t), rgb_error_t *);
767
768 // Calculate buffer size (larger for background mode due to more ANSI sequences)
769 size_t buffer_size =
770 (size_t)h * (size_t)w * (use_background ? 24 : 12) + (size_t)h; // Space for ANSI codes + newlines
771 char *buffer;
772 buffer = SAFE_MALLOC(buffer_size, char *);
773
774 char *ptr = buffer;
775 const char *reset_code = "\033[0m";
776
777 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
778 if (!utf8_cache) {
779 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache for 16-color dithered bg rendering");
780 SAFE_FREE(buffer);
781 SAFE_FREE(error_buffer);
782 return NULL;
783 }
784
785 for (int y = 0; y < h; y++) {
786 for (int x = 0; x < w; x++) {
787 rgb_pixel_t pixel = image->pixels[y * w + x];
788
789 // Convert RGB to 16-color index using dithering
790 uint8_t color_index = rgb_to_16color_dithered(pixel.r, pixel.g, pixel.b, x, y, w, h, error_buffer);
791
792 if (use_background) {
793 // Background mode: use contrasting foreground on background color
794 uint8_t bg_r, bg_g, bg_b;
795 get_16color_rgb(color_index, &bg_r, &bg_g, &bg_b);
796
797 // Calculate luminance to choose contrasting foreground
798 int bg_luminance = (bg_r * 77 + bg_g * 150 + bg_b * 29) / 256;
799 uint8_t fg_color = (bg_luminance < 127) ? 15 : 0; // White on dark, black on bright
800
801 ptr = append_16color_bg(ptr, color_index); // Set background to pixel color
802 ptr = append_16color_fg(ptr, fg_color); // Set contrasting foreground
803 } else {
804 // Foreground mode: set foreground to pixel color
805 ptr = append_16color_fg(ptr, color_index);
806 }
807
808 // Use same luminance formula as SIMD: ITU-R BT.601 with rounding
809 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
810
811 // Use same 6-bit precision as SIMD: map luminance (0-255) to bucket (0-63) then to character
812 uint8_t safe_luminance = clamp_rgb(luminance);
813
814 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
815
816 if (char_info) {
817 // Copy UTF-8 character bytes
818 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
819 *ptr++ = char_info->utf8_bytes[byte_idx];
820 }
821 } else {
822 // Fallback to simple ASCII if UTF-8 cache fails
823 size_t palette_len = strlen(palette);
824 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
825 *ptr++ = palette[palette_index];
826 }
827 }
828
829 // Add reset and newline at end of each row
830 SAFE_STRNCPY(ptr, reset_code, 5);
831 ptr += strlen(reset_code);
832 if (y < h - 1) {
833 *ptr++ = '\n';
834 }
835 }
836
837 *ptr = '\0';
838 SAFE_FREE(error_buffer); // Clean up error buffer
839 return buffer;
840}
char * append_16color_bg(char *dst, uint8_t color_index)
Definition ansi.c:421
void get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b)
Definition ansi.c:480

References ansi_fast_init_16color(), append_16color_bg(), append_16color_fg(), rgb_pixel_t::b, buffer_size, utf8_char_t::byte_len, utf8_palette_cache::cache, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, rgb_pixel_t::g, get_16color_rgb(), get_utf8_palette_cache(), image_t::h, image_t::pixels, rgb_pixel_t::r, rgb_to_16color_dithered(), SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, SET_ERRNO, utf8_char_t::utf8_bytes, and image_t::w.

Referenced by image_print_color_simd().

◆ image_print_256color()

char * image_print_256color ( const image_t *  image,
const char *  palette 
)

#include <image.h>

Print image using 256-color ANSI mode.

Parameters
imageImage to print (must not be NULL)
paletteCharacter palette to use (or NULL for default ASCII palette)
Returns
Allocated ASCII string with 256-color ANSI codes (caller must free), or NULL on error

Converts an image to ASCII art using 256-color ANSI mode. Uses extended ANSI color palette (0-255) for color output. Colors are quantized to 256-color palette.

Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
Requires terminal 256-color support for proper display.
Colors are quantized to 256-color palette automatically.

Definition at line 431 of file foreground.c.

431 {
432 if (!image || !image->pixels || !palette) {
433 SET_ERRNO(ERROR_INVALID_PARAM, "image=%p or image->pixels=%p or palette=%p is NULL", image, image->pixels, palette);
434 return NULL;
435 }
436
437 int h = image->h;
438 int w = image->w;
439
440 if (h <= 0 || w <= 0) {
441 SET_ERRNO(ERROR_INVALID_STATE, "image_print_256color: invalid dimensions h=%d, w=%d", h, w);
442 return NULL;
443 }
444
446
447 // Buffer size: 256-color ANSI codes are "\033[38;5;NNNm" (max 13 bytes) + char + reset + newline
448 size_t h_times_w;
449 if (checked_size_mul((size_t)h, (size_t)w, &h_times_w) != ASCIICHAT_OK) {
450 return NULL;
451 }
452
453 size_t h_times_w_times_18;
454 if (checked_size_mul(h_times_w, 18u, &h_times_w_times_18) != ASCIICHAT_OK) {
455 return NULL;
456 }
457
458 size_t buffer_size;
459 if (checked_size_add(h_times_w_times_18, (size_t)h, &buffer_size) != ASCIICHAT_OK) {
460 return NULL;
461 }
462
463 char *buffer = SAFE_MALLOC(buffer_size, char *);
464 char *ptr = buffer;
465 const char *reset_code = "\033[0m";
466
467 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
468 if (!utf8_cache) {
469 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 cache for 256-color rendering");
470 SAFE_FREE(buffer);
471 return NULL;
472 }
473
474 for (int y = 0; y < h; y++) {
475 for (int x = 0; x < w; x++) {
476 rgb_pixel_t pixel = image->pixels[y * w + x];
477
478 uint8_t color_index = rgb_to_256color(pixel.r, pixel.g, pixel.b);
479 ptr = append_256color_fg(ptr, color_index);
480
481 int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
482
483 uint8_t safe_luminance = clamp_rgb(luminance);
484
485 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
486
487 if (char_info && char_info->byte_len > 0) {
488 for (int byte_idx = 0; byte_idx < char_info->byte_len; byte_idx++) {
489 *ptr++ = char_info->utf8_bytes[byte_idx];
490 }
491 } else {
492 size_t palette_len = strlen(palette);
493 int palette_index = (luminance * ((int)palette_len - 1) + 127) / 255;
494 *ptr++ = palette[palette_index];
495 }
496 }
497
498 SAFE_STRNCPY(ptr, reset_code, 5);
499 ptr += strlen(reset_code);
500 if (y < h - 1) {
501 *ptr++ = '\n';
502 }
503 }
504
505 *ptr = '\0';
506 return buffer;
507}
void ansi_fast_init_256color(void)
Definition ansi.c:334
char * append_256color_fg(char *dst, uint8_t color_index)
Definition ansi.c:344
uint8_t rgb_to_256color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:360

References ansi_fast_init_256color(), append_256color_fg(), ASCIICHAT_OK, rgb_pixel_t::b, buffer_size, utf8_char_t::byte_len, utf8_palette_cache::cache, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, rgb_pixel_t::g, get_utf8_palette_cache(), image_t::h, image_t::pixels, rgb_pixel_t::r, rgb_to_256color(), SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, SET_ERRNO, utf8_char_t::utf8_bytes, and image_t::w.

Referenced by image_print_color_simd(), and image_print_with_capabilities().

◆ image_print_color()

char * image_print_color ( const image_t *  p,
const char *  palette 
)

#include <image.h>

Print image as ASCII art with color.

Parameters
pImage to print (must not be NULL)
paletteCharacter palette to use (or NULL for default ASCII palette)
Returns
Allocated ASCII string with ANSI color codes (caller must free), or NULL on error

Converts an image to ASCII art with color output. Uses character palette for character mapping and ANSI color codes for color output. Returns a null-terminated string containing the ASCII art frame with embedded ANSI escape sequences.

Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
ANSI color codes are embedded in the output string.
Color output requires terminal color support for proper display.

Converts an image to colored ASCII art with ANSI escape codes.

This function generates a string representation of an image where each pixel is converted to an ASCII character with ANSI color codes. The character is chosen based on luminance, and colors are applied using 24-bit RGB ANSI escape sequences.

Buffer allocation is precisely calculated to avoid waste and prevent overflows:

  • Each pixel: 1 ASCII char + foreground ANSI code (19 bytes max)
  • Background mode: adds background ANSI code (19 bytes max per pixel)
  • Each row: reset sequence (\033[0m = 4 bytes) + newline (except last row)
  • At the end: null terminator (1 byte)

Color modes:

  • Foreground only (default): ASCII characters with colored foreground
  • Background mode (opt_background_color): colored background with contrasting foreground (black on bright backgrounds, white on dark backgrounds)

ANSI escape code format:

  • Foreground: \033[38;2;R;G;Bm (11-19 bytes depending on RGB values)
  • Background: \033[48;2;R;G;Bm (11-19 bytes depending on RGB values)
  • Reset: \033[0m (4 bytes)
Parameters
pPointer to image_t structure containing pixel data
Returns
Dynamically allocated string containing colored ASCII art, or NULL on error. Caller is responsible for freeing the returned string.
Note
The function performs overflow checks to prevent integer overflow when calculating buffer sizes for very large images.
Uses global opt_background_color to determine color mode.
Exits with ERROR_BUFFER if buffer overflow is detected during string construction (should never happen with correct calculation).

Definition at line 194 of file foreground.c.

194 {
195 if (!p || !palette) {
196 SET_ERRNO(ERROR_INVALID_PARAM, "p=%p or palette=%p is NULL", p, palette);
197 return NULL;
198 }
199 if (!p->pixels) {
200 SET_ERRNO(ERROR_INVALID_PARAM, "p->pixels is NULL");
201 return NULL;
202 }
203
204 // Get UTF-8 character cache for proper multi-byte character support
205 utf8_palette_cache_t *utf8_cache = get_utf8_palette_cache(palette);
206 if (!utf8_cache) {
207 SET_ERRNO(ERROR_INVALID_STATE, "Failed to get UTF-8 palette cache for scalar color rendering");
208 return NULL;
209 }
210
211 const int h = p->h;
212 const int w = p->w;
213
214 // Constants for ANSI escape codes (using exact sizes from ansi_fast.c)
215 const size_t max_fg_ansi = 19; // \033[38;2;255;255;255m
216 const size_t max_bg_ansi = 19; // \033[48;2;255;255;255m
217 const size_t reset_len = 4; // \033[0m
218
219 const size_t h_sz = (size_t)h;
220 const size_t w_sz = (size_t)w;
221
222 // Ensure h * w won't overflow
223 if (h_sz > 0 && w_sz > SIZE_MAX / h_sz) {
224 SET_ERRNO(ERROR_INVALID_STATE, "Image dimensions too large: %d x %d", h, w);
225 return NULL;
226 }
227
228 const size_t total_pixels = h_sz * w_sz;
229 const size_t bytes_per_pixel = 1 + max_fg_ansi + max_bg_ansi; // Conservative estimate for max possible
230
231 // Ensure total_pixels * bytes_per_pixel won't overflow
232 if (total_pixels > SIZE_MAX / bytes_per_pixel) {
233 SET_ERRNO(ERROR_INVALID_STATE, "Pixel data too large for buffer: %d x %d", h, w);
234 return NULL;
235 }
236
237 const size_t pixel_bytes = total_pixels * bytes_per_pixel;
238
239 // Per row: newline (except last row)
240 // Final reset added once by ansi_rle_finish()
241 const size_t total_newlines = (h_sz > 0) ? (h_sz - 1) : 0;
242 const size_t final_reset = reset_len; // One reset at end (from ansi_rle_finish)
243
244 // Final buffer size: pixel bytes + per-row newlines + final reset + null terminator
245 const size_t extra_bytes = total_newlines + final_reset + 1;
246
247 if (pixel_bytes > SIZE_MAX - extra_bytes) {
248 SET_ERRNO(ERROR_INVALID_STATE, "Final buffer size would overflow: %d x %d", h, w);
249 return NULL;
250 }
251
252 const size_t lines_size = pixel_bytes + extra_bytes;
253 char *lines;
254 lines = SAFE_MALLOC(lines_size, char *);
255
256 const rgb_pixel_t *pix = p->pixels;
257 // char *current_pos = lines;
258 // const char *buffer_end = lines + lines_size - 1; // reserve space for '\0'
259
260 // Initialize the optimized RLE context for color sequence caching
261 // Note: This function should be called via image_print_with_capabilities() for proper per-client rendering
262 ansi_rle_context_t rle_ctx;
263 ansi_color_mode_t color_mode = ANSI_MODE_FOREGROUND; // Default to foreground-only for legacy usage
264 ansi_rle_init(&rle_ctx, lines, lines_size, color_mode);
265
266 // Process each pixel using the optimized RLE context
267 for (int y = 0; y < h; y++) {
268 const int row_offset = y * w;
269
270 for (int x = 0; x < w; x++) {
271 const rgb_pixel_t pixel = pix[row_offset + x];
272 int r = pixel.r, g = pixel.g, b = pixel.b;
273 // Standard ITU-R BT.601 luminance calculation (uses original values for accuracy)
274 const int luminance = (77 * pixel.r + 150 * pixel.g + 29 * pixel.b + 128) >> 8;
275
276 // Use UTF-8 character cache for proper character selection
277 uint8_t safe_luminance = clamp_rgb(luminance);
278 const utf8_char_t *char_info = &utf8_cache->cache[safe_luminance];
279
280 // For single-byte ASCII, use RLE optimization; for multi-byte UTF-8, write directly
281 if (char_info->byte_len == 1 && (unsigned char)char_info->utf8_bytes[0] < 128) {
282 // ASCII character - use RLE optimization
283 const char ascii_char = char_info->utf8_bytes[0];
284 ansi_rle_add_pixel(&rle_ctx, (uint8_t)r, (uint8_t)g, (uint8_t)b, ascii_char);
285 } else {
286 // Multi-byte UTF-8 character - write color code and full UTF-8 bytes directly
287 int written = SAFE_SNPRINTF((char *)&rle_ctx.buffer[rle_ctx.length], rle_ctx.capacity - rle_ctx.length,
288 "\033[38;2;%d;%d;%dm", r, g, b);
289 rle_ctx.length += written;
290 // Write UTF-8 bytes
291 for (int i = 0; i < char_info->byte_len && rle_ctx.length < rle_ctx.capacity; i++) {
292 rle_ctx.buffer[rle_ctx.length++] = char_info->utf8_bytes[i];
293 }
294 }
295 }
296
297 // Add newline after each row (except the last row)
298 if (y != h - 1 && rle_ctx.length < rle_ctx.capacity - 1) {
299 rle_ctx.buffer[rle_ctx.length++] = '\n';
300 }
301 }
302
303 ansi_rle_finish(&rle_ctx);
304
305 return lines;
306}
ansi_color_mode_t
Definition ansi.h:38
void ansi_rle_init(ansi_rle_context_t *ctx, char *buffer, size_t capacity, ansi_color_mode_t mode)
Definition ansi.c:248
void ansi_rle_finish(ansi_rle_context_t *ctx)
Definition ansi.c:303
void ansi_rle_add_pixel(ansi_rle_context_t *ctx, uint8_t r, uint8_t g, uint8_t b, char ascii_char)
Definition ansi.c:261
@ ANSI_MODE_FOREGROUND
Definition ansi.h:39
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
size_t capacity
Definition ansi.h:47
char * buffer
Definition ansi.h:46
size_t length
Definition ansi.h:48

References ANSI_MODE_FOREGROUND, ansi_rle_add_pixel(), ansi_rle_finish(), ansi_rle_init(), rgb_pixel_t::b, ansi_rle_context_t::buffer, utf8_char_t::byte_len, utf8_palette_cache::cache, ansi_rle_context_t::capacity, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, rgb_pixel_t::g, get_utf8_palette_cache(), image_t::h, ansi_rle_context_t::length, image_t::pixels, rgb_pixel_t::r, SAFE_MALLOC, SAFE_SNPRINTF, SET_ERRNO, utf8_char_t::utf8_bytes, and image_t::w.

Referenced by image_print_color_simd().

◆ image_print_color_simd()

char * image_print_color_simd ( image_t *  image,
bool  use_background_mode,
bool  use_256color,
const char *  ascii_chars 
)

#include <common.h>

Print image as ASCII with color using SIMD.

Parameters
imageSource image
use_background_modeUse background colors
use_256colorUse 256-color mode (vs truecolor)
ascii_charsCharacter palette
Returns
Allocated ASCII string with ANSI codes (caller must free), or NULL on error

Definition at line 413 of file sgr.c.

413 {
414 if (!image || !ascii_chars) {
415 SET_ERRNO(ERROR_INVALID_PARAM, "image_print_color_simd: image or ascii_chars is NULL");
416 return NULL;
417 }
418
419 log_dev_every(4500 * US_PER_MS_INT, "image_print_color_simd called: width=%d, height=%d, use_256color=%d", image->w,
420 image->h, use_256color);
421
422#if SIMD_SUPPORT_AVX2
423 log_debug_every(10 * US_PER_SEC_INT, "Taking AVX2 path: width=%d, height=%d", image->w, image->h);
424 // AVX2 implementation would go here
425 // For now, fall through to scalar implementation
426#endif
427
428 // Fallback to scalar implementation
429 if (use_background_mode) {
430 return image_print_16color_dithered_with_background(image, use_background_mode, ascii_chars);
431 } else if (use_256color) {
432 return image_print_256color(image, ascii_chars);
433 } else {
434 return image_print_color(image, ascii_chars);
435 }
436}
#define US_PER_MS_INT
Definition time.h:160
char * image_print_color(const image_t *p, const char *palette)
Print image as ASCII art with color.
Definition foreground.c:194
char * image_print_16color_dithered_with_background(const image_t *image, bool use_background, const char *palette)
Print image using 16-color ANSI mode with dithering and background colors.
Definition foreground.c:746
char * image_print_256color(const image_t *image, const char *palette)
Print image using 256-color ANSI mode.
Definition foreground.c:431
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:699

References ERROR_INVALID_PARAM, image_t::h, image_print_16color_dithered_with_background(), image_print_256color(), image_print_color(), log_debug_every, log_dev_every, SET_ERRNO, US_PER_MS_INT, US_PER_SEC_INT, and image_t::w.

Referenced by ascii_convert(), and image_print_with_capabilities().

◆ image_print_with_capabilities()

char * image_print_with_capabilities ( const image_t *  image,
const terminal_capabilities_t *  caps,
const char *  palette 
)

#include <ascii.h>

Convert image to ASCII art with terminal capabilities.

Print image with terminal capability awareness.

Parameters
imageImage to convert (must not be NULL)
capsTerminal capabilities structure
palettePalette characters to use for conversion
Returns
ASCII art string, or NULL on error

Converts an image to ASCII art using the provided terminal capabilities. Automatically selects the best rendering method based on terminal capabilities.

Note
Returns dynamically allocated string that must be freed by caller.
Parameters
imageImage to print (must not be NULL)
capsTerminal capabilities structure (must not be NULL)
paletteCharacter palette to use (or NULL for default ASCII palette)
luminance_paletteLuminance-to-character mapping palette (must not be NULL, 256 elements)
Returns
Allocated ASCII string (caller must free), or NULL on error

Converts an image to ASCII art with automatic color mode selection based on terminal capabilities. Automatically chooses the best color mode (16-color, 256-color, or truecolor) for the terminal.

COLOR MODE SELECTION:

  • Truecolor: If terminal supports 24-bit RGB colors
  • 256-color: If terminal supports extended ANSI palette
  • 16-color: Fallback to standard ANSI colors
  • Monochrome: If terminal has no color support
Note
Returns NULL on error (invalid image, memory allocation failure).
ASCII string must be freed by caller using free().
Color mode is selected automatically based on terminal capabilities.
This is the recommended function for capability-aware ASCII conversion.

Definition at line 978 of file ascii.c.

978 {
979 if (!image || !caps || !palette) {
980 return NULL;
981 }
982
983 terminal_color_mode_t color_level = caps->color_level;
984 render_mode_t render_mode = caps->render_mode;
985
986 // Half-block mode: dispatch to halfblock renderers by color depth
987 if (render_mode == RENDER_MODE_HALF_BLOCK) {
988 const uint8_t *rgb_data = (const uint8_t *)image->pixels;
989 switch (color_level) {
991 return rgb_to_truecolor_halfblocks_scalar(rgb_data, image->w, image->h, 0);
992 case TERM_COLOR_256:
993 return rgb_to_256color_halfblocks_scalar(rgb_data, image->w, image->h, 0, palette);
994 case TERM_COLOR_16:
995 return rgb_to_16color_halfblocks_scalar(rgb_data, image->w, image->h, 0, palette);
996 case TERM_COLOR_NONE:
997 case TERM_COLOR_AUTO:
998 default:
999 return rgb_to_halfblocks_scalar(rgb_data, image->w, image->h, 0, palette);
1000 }
1001 }
1002
1003 // Foreground/background modes: dispatch by color depth
1004 switch (color_level) {
1005 case TERM_COLOR_TRUECOLOR: {
1006 bool use_background = (render_mode == RENDER_MODE_BACKGROUND);
1007#ifdef SIMD_SUPPORT
1008 return image_print_color_simd((image_t *)image, use_background, false, palette);
1009#else
1010 if (use_background) {
1011 return image_print_color_background(image, palette);
1012 }
1013 return image_print_color_utf8(image, palette);
1014#endif
1015 }
1016 case TERM_COLOR_256:
1017 return image_print_256color(image, palette);
1018 case TERM_COLOR_16:
1019 return image_print_16color(image, palette);
1020 case TERM_COLOR_NONE:
1021 case TERM_COLOR_AUTO:
1022 default:
1023 return image_print(image, palette);
1024 }
1025}
char * rgb_to_16color_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
Scalar 16-color halfblock renderer Uses 16-color ANSI codes for foreground (top) and implicit backgro...
char * rgb_to_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
Scalar monochrome halfblock renderer (no color codes)
char * image_print_16color(const image_t *image, const char *palette)
Print image using 16-color ANSI mode.
Definition foreground.c:533
char * rgb_to_256color_halfblocks_scalar(const uint8_t *rgb, int width, int height, int stride_bytes, const char *palette)
Scalar 256-color halfblock renderer Uses 256-color ANSI codes for foreground (top) and implicit backg...
terminal_color_mode_t color_level
Detected color support level (terminal_color_mode_t)
Definition terminal.h:711
render_mode_t
Render mode preferences.
Definition terminal.h:662
terminal_color_mode_t
Terminal color support levels.
Definition terminal.h:578
@ TERM_COLOR_NONE
No color support (monochrome terminal)
Definition terminal.h:582
@ TERM_COLOR_16
16-color support (standard ANSI colors)
Definition terminal.h:584
@ TERM_COLOR_256
256-color support (extended ANSI palette)
Definition terminal.h:586
@ TERM_COLOR_AUTO
Auto-detect color support from terminal capabilities.
Definition terminal.h:580
@ TERM_COLOR_TRUECOLOR
24-bit truecolor support (RGB colors)
Definition terminal.h:588

References terminal_capabilities_t::color_level, image_t::h, image_print(), image_print_16color(), image_print_256color(), image_print_color_background(), image_print_color_simd(), image_print_color_utf8(), image_t::pixels, terminal_capabilities_t::render_mode, RENDER_MODE_BACKGROUND, RENDER_MODE_HALF_BLOCK, rgb_to_16color_halfblocks_scalar(), rgb_to_256color_halfblocks_scalar(), rgb_to_halfblocks_scalar(), rgb_to_truecolor_halfblocks_scalar(), TERM_COLOR_16, TERM_COLOR_256, TERM_COLOR_AUTO, TERM_COLOR_NONE, TERM_COLOR_TRUECOLOR, and image_t::w.

Referenced by ascii_convert_with_capabilities().

◆ image_render_test_pattern()

asciichat_error_t image_render_test_pattern ( image_t *  image,
unsigned int *  frame_counter 
)

#include <image.h>

Render animated color bars and grid lines into an existing image.

Parameters
imageDestination image; its allocation remains owned by the caller.
frame_counterPer-source animation counter, advanced after each frame.
Returns
ASCIICHAT_OK on success, or ERROR_INVALID_PARAM for invalid arguments.

Definition at line 38 of file video/rgba/image.c.

38 {
39 if (!image || !image->pixels || image->w <= 0 || image->h <= 0 || !frame_counter) {
40 return SET_ERRNO(ERROR_INVALID_PARAM, "Test pattern requires a valid image and frame counter");
41 }
42
43 // Advance every generated frame. Discovery's FPS indicator measures visual
44 // changes, so holding each test pattern phase for two frames makes a 60 Hz
45 // source appear to run at only 30 FPS.
46 unsigned int animation_phase = *frame_counter;
47 *frame_counter += 5; // Speed up animation by a factor of five.
48
49 for (int y = 0; y < image->h; y++) {
50 for (int x = 0; x < image->w; x++) {
51 rgb_pixel_t *pixel = &image->pixels[y * image->w + x];
52
53 // Animated color bars that shift based on frame counter
54 int animated_x = (x + animation_phase) % image->w;
55 int grid_x = animated_x / 40;
56
57 // Base pattern: color bars that animate horizontally
58 switch (grid_x % 3) {
59 case 0: // Red
60 pixel->r = 255;
61 pixel->g = 0;
62 pixel->b = 0;
63 break;
64 case 1: // Green
65 pixel->r = 0;
66 pixel->g = 255;
67 pixel->b = 0;
68 break;
69 case 2: // Blue
70 default:
71 pixel->r = 0;
72 pixel->g = 0;
73 pixel->b = 255;
74 break;
75 }
76
77 // Add animated grid lines. The phase alternates the cell pattern each
78 // generated frame so a downsampled terminal client still has a visible
79 // frame-to-frame change when --test-pattern is used for cadence tests.
80 if (animated_x % 40 == 0 || y % 30 == 0 || (((x / 10) + (y / 10) + animation_phase) & 1) == 0) {
81 pixel->r = 0;
82 pixel->g = 0;
83 pixel->b = 0;
84 }
85 }
86 }
87
88 return ASCIICHAT_OK;
89}

References ASCIICHAT_OK, rgb_pixel_t::b, ERROR_INVALID_PARAM, rgb_pixel_t::g, image_t::h, image_t::pixels, rgb_pixel_t::r, SET_ERRNO, and image_t::w.

◆ image_resize()

void image_resize ( const image_t *  source,
image_t *  dest 
)

#include <image.h>

Resize image using nearest-neighbor interpolation.

Parameters
sourceSource image (must not be NULL)
destDestination image (must be allocated and have valid dimensions)

Resizes an image using nearest-neighbor interpolation. Fast but may produce aliasing artifacts. Source pixels are sampled to nearest destination pixel positions.

NEAREST-NEIGHBOR INTERPOLATION:

  • Fast algorithm (no interpolation calculations)
  • May produce aliasing artifacts
  • Pixel values are copied directly (no averaging)
  • Suitable for integer scaling (2x, 3x, etc.)
Note
Destination image dimensions must be set before calling.
Source and destination images must be valid (not NULL).
Fast algorithm suitable for real-time resizing.

Definition at line 309 of file video/rgba/image.c.

309 {
310 if (!s || !d) {
311 SET_ERRNO(ERROR_INVALID_PARAM, "image_resize: s or d is NULL");
312 return;
313 }
314
316}
void image_resize_interpolation(const image_t *source, image_t *dest)
Resize image using bilinear interpolation.

References ERROR_INVALID_PARAM, image_resize_interpolation(), and SET_ERRNO.

Referenced by ascii_convert(), ascii_convert_with_capabilities(), and session_capture_process_for_transmission().

◆ image_resize_interpolation()

void image_resize_interpolation ( const image_t *  source,
image_t *  dest 
)

#include <image.h>

Resize image using bilinear interpolation.

Parameters
sourceSource image (must not be NULL)
destDestination image (must be allocated and have valid dimensions)

Resizes an image using bilinear interpolation. Produces smoother results than nearest-neighbor but slower. Pixel values are interpolated from four nearest source pixels.

BILINEAR INTERPOLATION:

  • Slower algorithm (interpolation calculations required)
  • Produces smoother results (reduced aliasing)
  • Pixel values are interpolated from 4 nearest neighbors
  • Suitable for fractional scaling (1.5x, 2.3x, etc.)
Note
Destination image dimensions must be set before calling.
Source and destination images must be valid (not NULL).
Slower algorithm but produces better quality.

Definition at line 320 of file video/rgba/image.c.

320 {
321 if (!source || !dest || !source->pixels || !dest->pixels) {
322 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters to image_resize_interpolation");
323 return;
324 }
325
326 const int src_w = source->w;
327 const int src_h = source->h;
328 const int dst_w = dest->w;
329 const int dst_h = dest->h;
330
331 // Handle edge cases
332 if (src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0) {
333 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid image dimensions for resize: src=%dx%d dst=%dx%d", src_w, src_h, dst_w,
334 dst_h);
335 return;
336 }
337
338 // Defensive checks to prevent invalid shift (UBSan protection)
339 if (src_w < 1 || src_h < 1 || dst_w < 1 || dst_h < 1) {
340 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid dimensions detected after first check");
341 return;
342 }
343
344 // Use fixed-point arithmetic for better performance
345 // Use uint64_t intermediate to prevent overflow when shifting large dimensions
346 const uint32_t x_ratio = (uint32_t)((((uint64_t)src_w << 16) / (uint64_t)dst_w) + 1);
347 const uint32_t y_ratio = (uint32_t)((((uint64_t)src_h << 16) / (uint64_t)dst_h) + 1);
348
349 const rgb_pixel_t *src_pixels = source->pixels;
350 rgb_pixel_t *dst_pixels = dest->pixels;
351
352 for (int y = 0; y < dst_h; y++) {
353 const uint32_t src_y = ((uint32_t)(unsigned int)y * y_ratio) >> 16;
354 // Explicitly clamp to valid range [0, src_h-1]
355 const uint32_t safe_src_y = src_y >= (uint32_t)(unsigned int)src_h ? (uint32_t)(src_h - 1) : src_y;
356
357 // Bounds check: ensure safe_src_y is valid
358 if (safe_src_y >= (uint32_t)(unsigned int)src_h) {
359 SET_ERRNO(ERROR_INVALID_PARAM, "safe_src_y out of bounds: %u >= %d", safe_src_y, src_h);
360 return;
361 }
362
363 const rgb_pixel_t *src_row = src_pixels + (safe_src_y * (size_t)src_w);
364
365 rgb_pixel_t *dst_row = dst_pixels + ((size_t)y * (size_t)dst_w);
366
367 for (int x = 0; x < dst_w; x++) {
368 const uint32_t src_x = ((uint32_t)(unsigned int)x * x_ratio) >> 16;
369 // Explicitly clamp to valid range [0, src_w-1]
370 const uint32_t safe_src_x = src_x >= (uint32_t)(unsigned int)src_w ? (uint32_t)(src_w - 1) : src_x;
371
372 // Bounds check: ensure safe_src_x is valid
373 if (safe_src_x >= (uint32_t)(unsigned int)src_w) {
374 SET_ERRNO(ERROR_INVALID_PARAM, "safe_src_x out of bounds: %u >= %d", safe_src_x, src_w);
375 return;
376 }
377
378 dst_row[x] = src_row[safe_src_x];
379 }
380 }
381}

References ERROR_INVALID_PARAM, image_t::h, image_t::pixels, SET_ERRNO, and image_t::w.

Referenced by image_resize().

◆ init_dec3()

void init_dec3 ( void  )

#include <common.h>

Initialize decimal lookup table.

Must be called before using any SIMD conversion functions.

Initialize decimal lookup table.

Pre-builds a cache of ASCII representations for all byte values. Used by color code generation to avoid repeated int-to-string conversions.

Definition at line 546 of file video/ascii/common.c.

546 {
548 return;
549 for (int v = 0; v < 256; ++v) {
550 int d2 = v / 100; // 0..2
551 int r = v - d2 * 100; // 0..99
552 int d1 = r / 10; // 0..9
553 int d0 = r - d1 * 10; // 0..9
554
555 if (d2) {
557 g_dec3_cache.dec3_table[v].s[0] = '0' + d2;
558 g_dec3_cache.dec3_table[v].s[1] = '0' + d1;
559 g_dec3_cache.dec3_table[v].s[2] = '0' + d0;
560 } else if (d1) {
562 g_dec3_cache.dec3_table[v].s[0] = '0' + d1;
563 g_dec3_cache.dec3_table[v].s[1] = '0' + d0;
564 } else {
566 g_dec3_cache.dec3_table[v].s[0] = '0' + d0;
567 }
568 }
570}
global_dec3_cache_t g_dec3_cache
Global decimal cache instance.

References global_dec3_cache_t::dec3_initialized, global_dec3_cache_t::dec3_table, g_dec3_cache, dec3_t::len, and dec3_t::s.

Referenced by append_truecolor_bg(), append_truecolor_fg(), append_truecolor_fg_bg(), and ascii_simd_init().

◆ init_default_luminance_palette()

void init_default_luminance_palette ( void  )

#include <common.h>

Initialize default luminance palette.

Definition at line 589 of file video/ascii/common.c.

589 {
590 if (!lifecycle_init(&g_default_palette_lc, "default_palette")) {
591 return; // Already initialized
592 }
593 do_init_default_luminance_palette();
594}
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26

References lifecycle_init().

Referenced by ascii_simd_init().

◆ ob_putc()

void ob_putc ( outbuf_t *  ob,
char  c 
)

#include <output_buffer.h>

Append a character to buffer.

Parameters
obOutput buffer structure (must not be NULL)
cCharacter to append

Appends a single character to the output buffer. Automatically expands the buffer if necessary. Buffer remains null-terminated after append.

Note
Buffer is automatically expanded if needed.
Length is incremented by 1 after append.
Null terminator is updated automatically.

Definition at line 43 of file output_buffer.c.

43 {
44 if (!ob)
45 return;
46 ob_reserve(ob, 1);
47 ob->buf[ob->len++] = c;
48}
size_t len
Current length in bytes (excluding null terminator)

References outbuf_t::buf, outbuf_t::len, and ob_reserve().

Referenced by ansi_compress_rle(), emit_rep(), emit_reset(), emit_set_bg(), emit_set_fg(), emit_set_truecolor_bg(), emit_set_truecolor_fg(), image_print(), ob_u8(), rgb_to_16color_halfblocks_scalar(), rgb_to_256color_halfblocks_scalar(), rgb_to_halfblocks_scalar(), and rgb_to_truecolor_halfblocks_scalar().

◆ ob_reserve()

void ob_reserve ( outbuf_t *  ob,
size_t  need 
)

#include <output_buffer.h>

Reserve buffer space for upcoming writes.

Parameters
obOutput buffer structure (must not be NULL)
needMinimum bytes needed (must be > 0)

Ensures the buffer has at least 'need' bytes of available space. Automatically expands the buffer if necessary. Buffer grows exponentially (doubles in size) to minimize reallocation overhead.

Note
Buffer is automatically allocated on first reservation.
Buffer doubles in size when capacity is exceeded.
This function does not modify buffer length (only capacity).

Definition at line 22 of file output_buffer.c.

22 {
23 if (!ob)
24 return;
25 if (ob->cap == 0) {
26 // Always allocate at least default capacity on first call
27 size_t ncap = 4096;
28 while (ncap < ob->len + need)
29 ncap = (ncap * 3) / 2;
30 // SAFE_REALLOC handles NULL ptr as malloc - this is safe
31 ob->buf = SAFE_REALLOC(ob->buf, ncap, char *);
32 ob->cap = ncap;
33 } else if (ob->len + need > ob->cap) {
34 // Expand existing buffer
35 size_t ncap = ob->cap;
36 while (ncap < ob->len + need)
37 ncap = (ncap * 3) / 2;
38 ob->buf = SAFE_REALLOC(ob->buf, ncap, char *);
39 ob->cap = ncap;
40 }
41}
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:284

References outbuf_t::buf, outbuf_t::cap, outbuf_t::len, and SAFE_REALLOC.

Referenced by ansi_compress_rle(), ansi_expand_rle(), ob_putc(), ob_term(), ob_u32(), and ob_write().

◆ ob_term()

void ob_term ( outbuf_t *  ob)

#include <output_buffer.h>

Append null terminator to buffer.

Parameters
obOutput buffer structure (must not be NULL)

Ensures the buffer is null-terminated by appending a null character. Does not increment length counter (null terminator is not counted). Useful for ensuring buffer is a valid C string.

Note
This function ensures buffer[len] == '\0'.
Length counter is not modified (null terminator excluded from length).

Definition at line 58 of file output_buffer.c.

58 {
59 if (!ob)
60 return;
61 // Reserve space for null terminator if needed
62 if (ob->len >= ob->cap) {
63 ob_reserve(ob, 1);
64 }
65 // Append null terminator WITHOUT incrementing length
66 // (null terminator is not part of the string length)
67 ob->buf[ob->len] = '\0';
68}

References outbuf_t::buf, outbuf_t::cap, outbuf_t::len, and ob_reserve().

Referenced by ansi_compress_rle(), ansi_expand_rle(), image_print(), rgb_to_16color_halfblocks_scalar(), rgb_to_256color_halfblocks_scalar(), rgb_to_halfblocks_scalar(), and rgb_to_truecolor_halfblocks_scalar().

◆ ob_u32()

void ob_u32 ( outbuf_t *  ob,
uint32_t  v 
)

#include <output_buffer.h>

Append unsigned 32-bit integer as decimal string.

Parameters
obOutput buffer structure (must not be NULL)
vValue to append (0 to 4294967295)

Converts an unsigned 32-bit integer to decimal string representation and appends it to the buffer. Useful for formatting numeric values (dimensions, counts, timestamps, etc.).

Note
Buffer is automatically expanded if needed.
String representation is appended (1-10 characters for 0-4294967295).

Definition at line 92 of file output_buffer.c.

92 {
93 if (!ob)
94 return;
95 char tmp[10];
96 int i = 0;
97 do {
98 tmp[i++] = '0' + (v % 10u);
99 v /= 10u;
100 } while (v);
101 ob_reserve(ob, (size_t)i);
102 while (i--)
103 ob->buf[ob->len++] = tmp[i];
104}

References outbuf_t::buf, outbuf_t::len, and ob_reserve().

Referenced by emit_rep().

◆ ob_u8()

void ob_u8 ( outbuf_t *  ob,
uint8_t  v 
)

#include <output_buffer.h>

Append unsigned 8-bit integer as decimal string.

Parameters
obOutput buffer structure (must not be NULL)
vValue to append (0-255)

Converts an unsigned 8-bit integer to decimal string representation and appends it to the buffer. Useful for formatting small numeric values (color indices, counts, etc.).

Note
Buffer is automatically expanded if needed.
String representation is appended (1-3 characters for 0-255).

Definition at line 71 of file output_buffer.c.

71 {
72 if (!ob)
73 return;
74 if (v >= 100) {
75 uint8_t d0 = v / 100;
76 uint8_t r = v % 100;
77 uint8_t d1 = r / 10;
78 uint8_t d2 = r % 10;
79 ob_putc(ob, '0' + d0);
80 ob_putc(ob, '0' + d1);
81 ob_putc(ob, '0' + d2);
82 } else if (v >= 10) {
83 uint8_t d1 = v / 10;
84 uint8_t d2 = v % 10;
85 ob_putc(ob, '0' + d1);
86 ob_putc(ob, '0' + d2);
87 } else {
88 ob_putc(ob, '0' + v);
89 }
90}

References ob_putc().

Referenced by emit_set_bg(), emit_set_fg(), emit_set_truecolor_bg(), and emit_set_truecolor_fg().

◆ ob_write()

void ob_write ( outbuf_t *  ob,
const char *  s,
size_t  n 
)

#include <output_buffer.h>

Append a string to buffer.

Parameters
obOutput buffer structure (must not be NULL)
sString to append (must not be NULL)
nNumber of bytes to append (must be > 0)

Appends n bytes from string s to the output buffer. Automatically expands the buffer if necessary. Buffer remains null-terminated after append.

Note
Buffer is automatically expanded if needed.
Length is incremented by n after append.
Null terminator is updated automatically.
This function copies n bytes (may include embedded nulls).

Definition at line 50 of file output_buffer.c.

50 {
51 if (!ob || n == 0)
52 return;
53 ob_reserve(ob, n);
54 SAFE_MEMCPY(ob->buf + ob->len, ob->cap - ob->len, s, n);
55 ob->len += n;
56}

References outbuf_t::buf, outbuf_t::cap, outbuf_t::len, ob_reserve(), and SAFE_MEMCPY.

Referenced by ansi_compress_rle(), ansi_expand_rle(), emit_set_256_color_bg(), emit_set_256_color_fg(), emit_set_bg(), emit_set_fg(), emit_set_truecolor_bg(), emit_set_truecolor_fg(), image_print(), rgb_to_16color_halfblocks_scalar(), rgb_to_256color_halfblocks_scalar(), rgb_to_halfblocks_scalar(), and rgb_to_truecolor_halfblocks_scalar().

◆ precalc_rgb_palettes()

void precalc_rgb_palettes ( const float  red,
const float  green,
const float  blue 
)

#include <image.h>

Precalculate RGB palettes with color adjustment.

Parameters
redRed adjustment factor (1.0 = no adjustment, >1.0 = increase, <1.0 = decrease)
greenGreen adjustment factor (1.0 = no adjustment, >1.0 = increase, <1.0 = decrease)
blueBlue adjustment factor (1.0 = no adjustment, >1.0 = increase, <1.0 = decrease)

Precalculates RGB color palettes with color adjustment factors. Color adjustment allows brightness, contrast, and color balance adjustments to be applied efficiently during ASCII conversion.

COLOR ADJUSTMENT:

  • Adjustment factors are multiplied with color components
  • Factors > 1.0 increase color intensity
  • Factors < 1.0 decrease color intensity
  • Precalculated palettes improve conversion performance
Note
Precalculated palettes are cached for efficient conversion.
Color adjustment is applied during ASCII conversion.
Useful for brightness/contrast control.

Definition at line 385 of file video/rgba/image.c.

385 {
386 // Validate input parameters to prevent overflow
387 // Luminance weights should typically be in range 0.0-1.0
388 // But allow slightly larger values for brightness adjustment
389 const float max_weight = 255.0f; // Maximum value that won't overflow when multiplied by 255
390 const float min_weight = -255.0f; // Allow negative for color correction
391
392 // Note: isfinite() checks are skipped in Release builds due to -ffinite-math-only flag
393 // which disables NaN/infinity support for performance. Range checks below are sufficient.
394#if !defined(NDEBUG) && !defined(__FAST_MATH__)
395 if (!isfinite(red) || !isfinite(green) || !isfinite(blue)) {
396 log_error("Invalid weight values (non-finite): red=%f, green=%f, blue=%f", red, green, blue);
397 SET_ERRNO(ERROR_INVALID_PARAM, "precalc_rgb_palettes: non-finite weight values");
398 return;
399 }
400#endif
401
402 if (red < min_weight || red > max_weight || green < min_weight || green > max_weight || blue < min_weight ||
403 blue > max_weight) {
404 log_warn(
405 "precalc_rgb_palettes: Weight values out of expected range: red=%f, green=%f, blue=%f (clamping to safe range)",
406 red, green, blue);
407 }
408
409 // Clamp weights to safe range to prevent overflow
410 const float safe_red = (red < min_weight) ? min_weight : ((red > max_weight) ? max_weight : red);
411 const float safe_green = (green < min_weight) ? min_weight : ((green > max_weight) ? max_weight : green);
412 const float safe_blue = (blue < min_weight) ? min_weight : ((blue > max_weight) ? max_weight : blue);
413
414 const unsigned short max_ushort = 65535;
415 const unsigned short min_ushort = 0;
416
417 for (int n = 0; n < ASCII_LUMINANCE_LEVELS; ++n) {
418 // Compute with float, then clamp to unsigned short range
419 float red_val = (float)n * safe_red;
420 float green_val = (float)n * safe_green;
421 float blue_val = (float)n * safe_blue;
422
423 // Clamp to unsigned short range before assignment
424 if (red_val < (float)min_ushort) {
425 red_val = (float)min_ushort;
426 } else if (red_val > (float)max_ushort) {
427 red_val = (float)max_ushort;
428 }
429
430 if (green_val < (float)min_ushort) {
431 green_val = (float)min_ushort;
432 } else if (green_val > (float)max_ushort) {
433 green_val = (float)max_ushort;
434 }
435
436 if (blue_val < (float)min_ushort) {
437 blue_val = (float)min_ushort;
438 } else if (blue_val > (float)max_ushort) {
439 blue_val = (float)max_ushort;
440 }
441
442 RED[n] = (unsigned short)red_val;
443 GREEN[n] = (unsigned short)green_val;
444 BLUE[n] = (unsigned short)blue_val;
445 GRAY[n] = (unsigned short)n;
446 }
447}
unsigned short int GRAY[256]
Grayscale lookup table.
unsigned short int BLUE[256]
Blue channel lookup table.
unsigned short int GREEN[256]
Green channel lookup table.
unsigned short int RED[256]
Red channel lookup table.
#define ASCII_LUMINANCE_LEVELS
Number of luminance levels supported (256)
Definition ascii.h:410

References ASCII_LUMINANCE_LEVELS, BLUE, ERROR_INVALID_PARAM, GRAY, GREEN, log_error, log_warn, RED, and SET_ERRNO.

Referenced by server_main().

◆ prewarm_sgr256_cache()

void prewarm_sgr256_cache ( void  )

#include <common.h>

Prewarm 256-color foreground/background cache for benchmarks.

Prevents first-frame performance penalty in benchmarks.

Definition at line 129 of file sgr.c.

129 {
130 init_sgr256_cache();
131}

◆ prewarm_sgr256_fg_cache()

void prewarm_sgr256_fg_cache ( void  )

#include <common.h>

Prewarm 256-color foreground cache for benchmarks.

Prevents first-frame performance penalty in benchmarks.

Definition at line 125 of file sgr.c.

125 {
126 init_sgr256_cache();
127}

◆ print_simd_capabilities()

void print_simd_capabilities ( void  )

#include <common.h>

Print detected SIMD capabilities.

Prints available SIMD instruction sets to stdout.

◆ quantize_color()

void quantize_color ( int *  r,
int *  g,
int *  b,
int  levels 
)

#include <image.h>

Quantize color to specified number of levels.

Parameters
rRed component (input/output, must not be NULL, range 0-255)
gGreen component (input/output, must not be NULL, range 0-255)
bBlue component (input/output, must not be NULL, range 0-255)
levelsNumber of quantization levels (must be > 0)

Quantizes RGB color components to a specified number of levels. Reduces color precision for color palette mapping. Input colors are modified in-place to quantized values.

QUANTIZATION:

  • Divides color range (0-255) into 'levels' bins
  • Maps each component to nearest bin center
  • Reduces color precision for palette mapping
  • Used for 16-color and 256-color mode conversion
Note
Input/output parameters are modified in-place.
Quantization reduces color accuracy but enables palette mapping.
Typical values: 2 levels for 16-color, 6 levels for 256-color.

Definition at line 140 of file foreground.c.

140 {
141 if (!r || !g || !b) {
142 SET_ERRNO(ERROR_INVALID_PARAM, "quantize_color: r, g, or b is NULL");
143 return;
144 }
145
146 if (levels <= 0) {
147 SET_ERRNO(ERROR_INVALID_PARAM, "quantize_color: levels must be positive, got %d", levels);
148 return;
149 }
150
151 int step = 256 / levels;
152 *r = (*r / step) * step;
153 *g = (*g / step) * step;
154 *b = (*b / step) * step;
155}

References ERROR_INVALID_PARAM, and SET_ERRNO.

◆ rainbow_replace_ansi_colors()

char * rainbow_replace_ansi_colors ( const char *  ansi_string,
float  time_seconds 
)

#include <color_filter.h>

Replace all RGB ANSI codes with rainbow color.

Parameters
ansi_stringInput string with ANSI escape sequences
time_secondsCurrent time in seconds (for rainbow animation)
Returns
New string with rainbow colors, or NULL if no changes needed

Replaces all RGB foreground color codes (\x1b[38;2;R;G;Bm) in the input string with a single rainbow color calculated from the current time. This preserves ASCII character selection while applying rainbow colors.

The function:

  1. Calculates rainbow color from time using color_filter_calculate_rainbow()
  2. Builds replacement ANSI code string
  3. Scans input for \x1b[38;2; sequences
  4. Replaces each RGB code with rainbow code
  5. Returns new string (caller must SAFE_FREE)
Note
Returns NULL if no ANSI codes found (use original string)
Caller must free returned string with SAFE_FREE()
Input string is not modified
Usable in both terminal apps (lib/session) and WASM apps (src/web)

Definition at line 348 of file color_filter.c.

348 {
349 if (!ansi_string) {
350 return NULL;
351 }
352
353 // Calculate rainbow color once for this frame
354 uint8_t rainbow_r = 0, rainbow_g = 0, rainbow_b = 0;
355 color_filter_calculate_rainbow(time_seconds, &rainbow_r, &rainbow_g, &rainbow_b);
356
357 // Build the replacement ANSI code string once
358 char rainbow_code[32];
359 snprintf(rainbow_code, sizeof(rainbow_code), "\x1b[38;2;%d;%d;%dm", rainbow_r, rainbow_g, rainbow_b);
360 size_t rainbow_code_len = strlen(rainbow_code);
361
362 // Check if there are any ANSI codes to replace
363 if (!strstr(ansi_string, "\x1b[38;2;")) {
364 return NULL; // No ANSI codes found, no replacement needed
365 }
366
367 // Allocate buffer for result (2x size for safety with many replacements)
368 size_t input_len = strlen(ansi_string);
369 char *new_result = SAFE_MALLOC(input_len * 2, char *);
370 if (!new_result) {
371 return NULL;
372 }
373
374 // Replace all RGB color codes with rainbow color
375 const char *src = ansi_string;
376 char *dst = new_result;
377
378 while (*src) {
379 const char *ansi_start = strstr(src, "\x1b[38;2;");
380 if (ansi_start) {
381 // Copy everything before this ANSI code
382 size_t before_len = (size_t)(ansi_start - src);
383 memcpy(dst, src, before_len);
384 dst += before_len;
385
386 // Skip the old ANSI code (find the 'm' that ends it)
387 const char *ansi_end = strchr(ansi_start + 7, 'm');
388 if (ansi_end) {
389 // Copy the rainbow replacement code
390 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary data
391 memcpy(dst, rainbow_code, rainbow_code_len);
392 dst += rainbow_code_len;
393
394 // Move source past the old code
395 src = ansi_end + 1;
396 } else {
397 // Malformed ANSI code, just copy it
398 *dst++ = *src++;
399 }
400 } else {
401 // No more ANSI codes, copy the rest
402 memcpy(dst, src, strlen(src) + 1);
403 break;
404 }
405 }
406
407 return new_result;
408}

References color_filter_calculate_rainbow(), and SAFE_MALLOC.

Referenced by mirror_convert_frame(), and session_display_convert_to_ascii().

◆ rep_is_profitable()

bool rep_is_profitable ( uint32_t  runlen)

#include <output_buffer.h>

Check if run-length encoding is profitable.

Parameters
runlenRun length to check (number of repeated characters)
Returns
true if RLE is profitable, false otherwise

Determines whether run-length encoding (RLE) would reduce output size compared to emitting individual characters. RLE is profitable when the overhead of the repeat sequence is less than the characters it replaces.

RLE PROFITABILITY:

  • RLE uses format: ESC[runs (e.g., ESC[100X repeats 'X' 100 times)
  • Overhead: ~6-8 bytes for repeat sequence
  • Profit: runlen - overhead bytes saved
  • Profitable when runlen > ~10-15 characters
Note
Returns true if runlen exceeds RLE overhead threshold.
Threshold is tuned for typical ASCII art character runs.

Definition at line 148 of file output_buffer.c.

148 {
149 if (runlen <= 2)
150 return false;
151 uint32_t k = runlen - 1; // Extra repetitions beyond the first character
152 uint32_t rep_cost = (uint32_t)(digits_u32(k) + 3); // ESC [ digits b
153 return k > rep_cost; // Manual repetition cost vs REP cost
154}

Referenced by ansi_compress_rle(), image_print(), rgb_to_16color_halfblocks_scalar(), rgb_to_256color_halfblocks_scalar(), rgb_to_halfblocks_scalar(), and rgb_to_truecolor_halfblocks_scalar().

◆ rgb_to_16color_halfblocks_scalar()

char * rgb_to_16color_halfblocks_scalar ( const uint8_t *  rgb,
int  width,
int  height,
int  stride_bytes,
const char *  palette 
)

#include <halfblock.h>

Convert RGB to 16-color half-blocks using scalar code.

Parameters
rgbRGB pixel data (RGB24 format, 3 bytes per pixel)
widthImage width in pixels
heightImage height in pixels
stride_bytesRow stride in bytes (if 0, calculated as width * 3)
paletteCharacter palette (unused, for API consistency)
pad_heightTop padding height (number of newlines to output before frame)
Returns
Allocated ASCII string with half-block characters (caller must free), or NULL on error

Renders an RGB image using halfblock characters with 16-color ANSI codes. Processes 2 rows of source pixels per 1 output line with foreground and background color selection from 16-color palette.

Note
Caller must free the returned buffer with free().

Convert RGB to 16-color half-blocks using scalar code.

Definition at line 297 of file scalar/halfblock.c.

298 {
299 (void)palette; // Unused for 16-color mode
300 if (width <= 0 || height <= 0)
301 return platform_strdup("");
302 if (stride_bytes <= 0)
303 stride_bytes = width * 3;
304
305 // Initialize 16-color lookup table
307
308 outbuf_t ob = {0};
309 // Estimate: per cell ~ 12 bytes (16-color codes); half the rows + newlines + resets
310 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
311 ob.cap = est_cells * 12u + (size_t)((height + 1) / 2) * 8u + 64u;
312 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
313 if (!ob.buf)
314 return NULL;
315
316 // Track 16-color state
317 int cur_fg = -1, cur_bg = -1;
318 static const char HB[3] = {(char)0xE2, (char)0x96, (char)0x80};
319
320 for (int y = 0; y < height; y += 2) {
321 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
322 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
323
324 int x = 0;
325 while (x < width) {
326 const uint8_t *pT = rowT + (size_t)x * 3u;
327 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
328 uint8_t color_fg = rgb_to_16color(rT, gT, bT);
329
330 uint8_t rB = rT, gB = gT, bB = bT;
331 if (rowB) {
332 const uint8_t *pB = rowB + (size_t)x * 3u;
333 rB = pB[0];
334 gB = pB[1];
335 bB = pB[2];
336 }
337 uint8_t color_bg = rgb_to_16color(rB, gB, bB);
338
339 // Extend run while colors match
340 int j = x + 1;
341 for (; j < width; ++j) {
342 const uint8_t *qT = rowT + (size_t)j * 3u;
343 uint8_t color_fg2 = rgb_to_16color(qT[0], qT[1], qT[2]);
344
345 uint8_t color_bg2 = color_fg2;
346 if (rowB) {
347 const uint8_t *qB = rowB + (size_t)j * 3u;
348 color_bg2 = rgb_to_16color(qB[0], qB[1], qB[2]);
349 }
350
351 if (color_fg2 != color_fg || color_bg2 != color_bg)
352 break;
353 }
354 uint32_t run = (uint32_t)(j - x);
355
356 // Check if transparent
357 bool is_transparent = (rT == 0 && gT == 0 && bT == 0 && rB == 0 && gB == 0 && bB == 0);
358
359 if (is_transparent) {
360 if (cur_fg != -1 || cur_bg != -1) {
361 emit_reset(&ob);
362 cur_fg = cur_bg = -1;
363 }
364 // Emit spaces for transparent area (no RLE for padding)
365 for (uint32_t k = 0; k < run; ++k) {
366 ob_write(&ob, " ", 1);
367 }
368 } else {
369 // Emit 16-color codes if changed
370 if (cur_fg != color_fg) {
371 char buf[16];
372 char *ptr = append_16color_fg(buf, color_fg);
373 ob_write(&ob, buf, (size_t)(ptr - buf));
374 cur_fg = color_fg;
375 }
376 if (cur_bg != color_bg) {
377 char buf[16];
378 char *ptr = append_16color_bg(buf, color_bg);
379 ob_write(&ob, buf, (size_t)(ptr - buf));
380 cur_bg = color_bg;
381 }
382
383 ob_write(&ob, HB, 3);
384 if (rep_is_profitable(run)) {
385 emit_rep(&ob, run - 1);
386 } else {
387 for (uint32_t k = 1; k < run; ++k) {
388 ob_write(&ob, HB, 3);
389 }
390 }
391 }
392
393 x = j;
394 }
395
396 emit_reset(&ob);
397 if (y + 2 < height) {
398 ob_putc(&ob, '\n');
399 }
400 cur_fg = cur_bg = -1;
401 }
402
403 ob_term(&ob);
404 return ob.buf;
405}
char * platform_strdup(const char *s)
Duplicate string (strdup replacement)
void emit_reset(outbuf_t *ob)
Emit ANSI reset sequence.

References ansi_fast_init_16color(), append_16color_bg(), append_16color_fg(), outbuf_t::buf, outbuf_t::cap, emit_rep(), emit_reset(), ob_putc(), ob_term(), ob_write(), platform_strdup(), rep_is_profitable(), rgb_to_16color(), and SAFE_MALLOC.

Referenced by image_print_with_capabilities().

◆ rgb_to_256color_halfblocks_scalar()

char * rgb_to_256color_halfblocks_scalar ( const uint8_t *  rgb,
int  width,
int  height,
int  stride_bytes,
const char *  palette 
)

#include <halfblock.h>

Convert RGB to 256-color half-blocks using scalar code.

Parameters
rgbRGB pixel data (RGB24 format, 3 bytes per pixel)
widthImage width in pixels
heightImage height in pixels
stride_bytesRow stride in bytes (if 0, calculated as width * 3)
paletteCharacter palette (unused, for API consistency)
pad_heightTop padding height (number of newlines to output before frame)
Returns
Allocated ASCII string with half-block characters (caller must free), or NULL on error

Renders an RGB image using halfblock characters with 256-color ANSI codes. Processes 2 rows of source pixels per 1 output line with foreground and background color selection from 256-color palette.

Note
Caller must free the returned buffer with free().

Convert RGB to 256-color half-blocks using scalar code.

Definition at line 416 of file scalar/halfblock.c.

417 {
418 (void)palette; // Unused for 256-color mode
419 if (width <= 0 || height <= 0)
420 return platform_strdup("");
421 if (stride_bytes <= 0)
422 stride_bytes = width * 3;
423
424 // Initialize 256-color lookup table
426
427 outbuf_t ob = {0};
428 // Estimate: per cell ~ 14 bytes (256-color codes); half the rows + newlines + resets
429 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
430 ob.cap = est_cells * 14u + (size_t)((height + 1) / 2) * 8u + 64u;
431 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
432 if (!ob.buf)
433 return NULL;
434
435 // Track 256-color state
436 int cur_fg = -1, cur_bg = -1;
437 static const char HB[3] = {(char)0xE2, (char)0x96, (char)0x80};
438
439 for (int y = 0; y < height; y += 2) {
440 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
441 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
442
443 int x = 0;
444 while (x < width) {
445 const uint8_t *pT = rowT + (size_t)x * 3u;
446 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
447 uint8_t color_fg = rgb_to_256color(rT, gT, bT);
448
449 uint8_t rB = rT, gB = gT, bB = bT;
450 if (rowB) {
451 const uint8_t *pB = rowB + (size_t)x * 3u;
452 rB = pB[0];
453 gB = pB[1];
454 bB = pB[2];
455 }
456 uint8_t color_bg = rgb_to_256color(rB, gB, bB);
457
458 // Extend run while colors match
459 int j = x + 1;
460 for (; j < width; ++j) {
461 const uint8_t *qT = rowT + (size_t)j * 3u;
462 uint8_t color_fg2 = rgb_to_256color(qT[0], qT[1], qT[2]);
463
464 uint8_t color_bg2 = color_fg2;
465 if (rowB) {
466 const uint8_t *qB = rowB + (size_t)j * 3u;
467 color_bg2 = rgb_to_256color(qB[0], qB[1], qB[2]);
468 }
469
470 if (color_fg2 != color_fg || color_bg2 != color_bg)
471 break;
472 }
473 uint32_t run = (uint32_t)(j - x);
474
475 // Check if transparent
476 bool is_transparent = (rT == 0 && gT == 0 && bT == 0 && rB == 0 && gB == 0 && bB == 0);
477
478 if (is_transparent) {
479 if (cur_fg != -1 || cur_bg != -1) {
480 emit_reset(&ob);
481 cur_fg = cur_bg = -1;
482 }
483 // Emit spaces for transparent area (no RLE for padding)
484 for (uint32_t k = 0; k < run; ++k) {
485 ob_write(&ob, " ", 1);
486 }
487 } else {
488 // Emit 256-color codes if changed
489 if (cur_fg != color_fg) {
490 char buf[16];
491 char *ptr = append_256color_fg(buf, color_fg);
492 ob_write(&ob, buf, (size_t)(ptr - buf));
493 cur_fg = color_fg;
494 }
495 if (cur_bg != color_bg) {
496 char buf[16];
497 char *ptr = append_256color_bg(buf, color_bg);
498 ob_write(&ob, buf, (size_t)(ptr - buf));
499 cur_bg = color_bg;
500 }
501
502 ob_write(&ob, HB, 3);
503 if (rep_is_profitable(run)) {
504 emit_rep(&ob, run - 1);
505 } else {
506 for (uint32_t k = 1; k < run; ++k) {
507 ob_write(&ob, HB, 3);
508 }
509 }
510 }
511
512 x = j;
513 }
514
515 emit_reset(&ob);
516 if (y + 2 < height) {
517 ob_putc(&ob, '\n');
518 }
519 cur_fg = cur_bg = -1;
520 }
521
522 ob_term(&ob);
523 return ob.buf;
524}
char * append_256color_bg(char *dst, uint8_t color_index)
Definition ansi.c:352

References ansi_fast_init_256color(), append_256color_bg(), append_256color_fg(), outbuf_t::buf, outbuf_t::cap, emit_rep(), emit_reset(), ob_putc(), ob_term(), ob_write(), platform_strdup(), rep_is_profitable(), rgb_to_256color(), and SAFE_MALLOC.

Referenced by image_print_with_capabilities().

◆ rgb_to_ansi_8bit()

void rgb_to_ansi_8bit ( int  r,
int  g,
int  b,
int *  fg_code,
int *  bg_code 
)

#include <image.h>

Convert RGB to 8-bit ANSI color codes.

Parameters
rRed component (0-255)
gGreen component (0-255)
bBlue component (0-255)
fg_codeOutput foreground color code (must not be NULL, range 0-255)
bg_codeOutput background color code (must not be NULL, range 0-255)

Converts RGB color values to 8-bit ANSI color codes (256-color palette). Quantizes RGB colors to 256-color palette indices for foreground and background colors. Output codes are indices into 256-color ANSI palette.

COLOR QUANTIZATION:

  • RGB colors are quantized to 256-color palette
  • Quantization uses standard 6x6x6 color cube
  • Output codes are palette indices (0-255)
  • Foreground and background codes may differ
Note
Output parameters are modified to contain palette indices.
Color codes are indices into 256-color ANSI palette.
Quantization reduces color accuracy but enables 256-color mode.

Definition at line 381 of file foreground.c.

381 {
382 if (!fg_code || !bg_code) {
383 SET_ERRNO(ERROR_INVALID_PARAM, "rgb_to_ansi_8bit: fg_code or bg_code is NULL");
384 return;
385 }
386
387 // Convert RGB to 8-bit color code (216 color cube + 24 grayscale)
388 if (r == g && g == b) {
389 // Grayscale
390 if (r < 8) {
391 *fg_code = 16;
392 } else if (r > 248) {
393 *fg_code = 231;
394 } else {
395 *fg_code = 232 + (r - 8) / 10;
396 }
397 } else {
398 // Color cube: 16 + 36*r + 6*g + b where r,g,b are 0-5
399 int r_level = (r * 5) / 255;
400 int g_level = (g * 5) / 255;
401 int b_level = (b * 5) / 255;
402 *fg_code = 16 + 36 * r_level + 6 * g_level + b_level;
403 }
404 *bg_code = *fg_code; // Same logic for background
405}

References ERROR_INVALID_PARAM, and SET_ERRNO.

◆ rgb_to_ansi_bg()

char * rgb_to_ansi_bg ( int  r,
int  g,
int  b 
)

#include <image.h>

Convert RGB to ANSI background color code.

Parameters
rRed component (0-255)
gGreen component (0-255)
bBlue component (0-255)
Returns
Allocated ANSI color code string (caller must free), or NULL on error

Converts RGB color values to ANSI background color escape sequence. Returns a string containing the ANSI escape sequence for background color (e.g., "\033[48;2;255;0;0m" for red background).

Note
Returns NULL on error (memory allocation failure).
ANSI string must be freed by caller using free().
ANSI sequence format: ESC[48;2;r;g;bm (truecolor mode).
Color components are clamped to 0-255 range automatically.

Definition at line 375 of file foreground.c.

375 {
376 _Thread_local static char color_code[20]; // \033[48;2;255;255;255m + \0 = 20 bytes max
377 SAFE_SNPRINTF(color_code, sizeof(color_code), "\033[48;2;%d;%d;%dm", r, g, b);
378 return color_code;
379}

References SAFE_SNPRINTF.

◆ rgb_to_ansi_fg()

char * rgb_to_ansi_fg ( int  r,
int  g,
int  b 
)

#include <image.h>

Convert RGB to ANSI foreground color code.

Parameters
rRed component (0-255)
gGreen component (0-255)
bBlue component (0-255)
Returns
Allocated ANSI color code string (caller must free), or NULL on error

Converts RGB color values to ANSI foreground color escape sequence. Returns a string containing the ANSI escape sequence for foreground color (e.g., "\033[38;2;255;0;0m" for red).

Note
Returns NULL on error (memory allocation failure).
ANSI string must be freed by caller using free().
ANSI sequence format: ESC[38;2;r;g;bm (truecolor mode).
Color components are clamped to 0-255 range automatically.

Definition at line 369 of file foreground.c.

369 {
370 _Thread_local static char color_code[20]; // \033[38;2;255;255;255m + \0 = 20 bytes max
371 SAFE_SNPRINTF(color_code, sizeof(color_code), "\033[38;2;%d;%d;%dm", r, g, b);
372 return color_code;
373}

References SAFE_SNPRINTF.

◆ rgb_to_halfblocks_scalar()

char * rgb_to_halfblocks_scalar ( const uint8_t *  rgb,
int  width,
int  height,
int  stride_bytes,
const char *  palette 
)

#include <halfblock.h>

Convert RGB to monochrome (grayscale) half-blocks using scalar code.

Parameters
rgbRGB pixel data (RGB24 format, 3 bytes per pixel)
widthImage width in pixels
heightImage height in pixels
stride_bytesRow stride in bytes (if 0, calculated as width * 3)
paletteCharacter palette for luminance mapping
Returns
Allocated ASCII string with half-block characters (caller must free), or NULL on error

Renders an RGB image using halfblock characters without color codes. Uses ITU-R BT.601 luminance formula for grayscale conversion.

Note
Caller must free the returned buffer with free().

Convert RGB to monochrome (grayscale) half-blocks using scalar code.

Parameters
rgbInput RGB image data (RGB24, 3 bytes per pixel)
widthImage width in pixels
heightImage height in pixels
stride_bytesBytes per row (if 0, calculated as width * 3)
paletteCharacter palette (unused, for API consistency)
Returns
Malloc'd NUL-terminated ASCII string (caller must free)

Renders an RGB image using halfblock characters with no color codes. Simple monochrome rendering without any ANSI escape sequences.

Definition at line 184 of file scalar/halfblock.c.

184 {
185 (void)palette; // Unused for monochrome mode
186 if (width <= 0 || height <= 0)
187 return platform_strdup("");
188 if (stride_bytes <= 0)
189 stride_bytes = width * 3;
190
191 outbuf_t ob = {0};
192 // Estimate: per cell ~ 3 bytes (UTF-8 block shade); half the rows + newlines
193 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
194 ob.cap = est_cells * 3u + (size_t)((height + 1) / 2) * 2u + 64u;
195 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
196 if (!ob.buf)
197 return NULL;
198
199 // Process 2 source rows per output line
200 for (int y = 0; y < height; y += 2) {
201 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
202 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
203
204 int x = 0;
205 while (x < width) {
206 // Read top pixel
207 const uint8_t *pT = rowT + (size_t)x * 3u;
208 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
209
210 // Read bottom pixel (or duplicate top if no bottom row)
211 uint8_t rB = rT, gB = gT, bB = bT;
212 if (rowB) {
213 const uint8_t *pB = rowB + (size_t)x * 3u;
214 rB = pB[0];
215 gB = pB[1];
216 bB = pB[2];
217 }
218
219 // Extend run while colors match exactly (treating as monochrome)
220 int j = x + 1;
221 for (; j < width; ++j) {
222 const uint8_t *qT = rowT + (size_t)j * 3u;
223 uint8_t rT2 = qT[0], gT2 = qT[1], bT2 = qT[2];
224
225 uint8_t rB2 = rT2, gB2 = gT2, bB2 = bT2;
226 if (rowB) {
227 const uint8_t *qB = rowB + (size_t)j * 3u;
228 rB2 = qB[0];
229 gB2 = qB[1];
230 bB2 = qB[2];
231 }
232
233 if (!((rT2 == rT && gT2 == gT && bT2 == bT) && (rB2 == rB && gB2 == gB && bB2 == bB)))
234 break;
235 }
236 uint32_t run = (uint32_t)(j - x);
237
238 // Convert to luminance for monochrome shading
239 uint8_t lum_top = (rT * 76 + gT * 150 + bT * 29) >> 8; // ITU-R BT.601
240 uint8_t lum_bot = (rB * 76 + gB * 150 + bB * 29) >> 8;
241
242 // Check if transparent (both pixels very dark)
243 bool is_transparent = (lum_top < 16 && lum_bot < 16);
244
245 if (is_transparent) {
246 // Emit spaces for transparent area (no RLE for padding)
247 for (uint32_t k = 0; k < run; ++k) {
248 ob_write(&ob, " ", 1);
249 }
250 } else {
251 // Use block characters based on luminance levels
252 // Map luminance (0-255) to block shade characters
253 uint8_t shade_top = (lum_top >> 6); // Map 0-255 to 0-3
254
255 static const char shades[4][3] = {
256 {(char)0xE2, (char)0x96, (char)0x91}, // ░ light shade
257 {(char)0xE2, (char)0x96, (char)0x92}, // ▒ medium shade
258 {(char)0xE2, (char)0x96, (char)0x93}, // ▓ dark shade
259 {(char)0xE2, (char)0x96, (char)0x88} // █ full block
260 };
261
262 // Emit the appropriate shade character
263 const char *shade_char = shades[shade_top];
264 ob_write(&ob, shade_char, 3);
265 if (rep_is_profitable(run)) {
266 emit_rep(&ob, run - 1);
267 } else {
268 for (uint32_t k = 1; k < run; ++k) {
269 ob_write(&ob, shade_char, 3);
270 }
271 }
272 }
273
274 x = j;
275 }
276
277 // End of line: reset and newline (except for last output line)
278 // Note: even monochrome calls reset for structural consistency
279 if (y + 2 < height) {
280 ob_putc(&ob, '\n');
281 }
282 }
283
284 ob_term(&ob);
285 return ob.buf;
286}

References outbuf_t::buf, outbuf_t::cap, emit_rep(), ob_putc(), ob_term(), ob_write(), platform_strdup(), rep_is_profitable(), and SAFE_MALLOC.

Referenced by image_print_with_capabilities().

◆ rgb_to_truecolor_halfblocks_scalar()

char * rgb_to_truecolor_halfblocks_scalar ( const uint8_t *  rgb,
int  width,
int  height,
int  stride_bytes 
)

#include <halfblock.h>

Convert RGB to truecolor half-blocks using scalar code.

Parameters
rgbRGB pixel data (RGB24 format, 3 bytes per pixel)
widthImage width in pixels
heightImage height in pixels
stride_bytesRow stride in bytes (if 0, calculated as width * 3)
Returns
Allocated ASCII string with half-block characters (caller must free), or NULL on error

Renders an RGB image using halfblock characters (▀) with truecolor foreground and background colors. Processes 2 rows of source pixels per 1 output line:

  • Top pixel: foreground color for halfblock
  • Bottom pixel: background color for halfblock
  • Last row (odd height): duplicates top row as bottom

Features:

  • Run-length encoding (RLE) for repeated halfblocks
  • Transparent area detection (fully black pixels)
  • Color state tracking to minimize ANSI sequences
  • Proper vertical resolution (height / 2 output rows)
Note
Caller must free the returned buffer with free().
Returns empty string for invalid dimensions (width <= 0 or height <= 0).
Stride of 0 defaults to width * 3 (tightly packed RGB24).

Convert RGB to truecolor half-blocks using scalar code.

Parameters
rgbInput RGB image data (RGB24, 3 bytes per pixel)
widthImage width in pixels
heightImage height in pixels
stride_bytesBytes per row (if 0, calculated as width * 3)
Returns
Malloc'd NUL-terminated ANSI escape string (caller must free)

Renders an RGB image using halfblock characters with truecolor foreground and background colors. Processes 2 source rows per output line:

  • Top pixel: foreground color for halfblock
  • Bottom pixel: background color for halfblock
  • Last row (odd height): duplicates top row as bottom

Uses RLE optimization for repeated halfblocks and detects transparent areas (fully black pixels) which are rendered as spaces instead of halfblocks.

Definition at line 48 of file scalar/halfblock.c.

48 {
49
50 if (width <= 0 || height <= 0)
51 return platform_strdup("");
52 if (stride_bytes <= 0)
53 stride_bytes = width * 3;
54
55 outbuf_t ob = {0};
56 // Estimate: per cell ~ 10-14 bytes (ANSI sequences); half the rows + newlines
57 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
58 ob.cap = est_cells * 14u + (size_t)((height + 1) / 2) * 8u + 64u;
59 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
60 if (!ob.buf)
61 return NULL;
62
63 // Track SGR state (use -1 to indicate "not set")
64 int cur_fr = -1, cur_fg = -1, cur_fb = -1;
65 int cur_br = -1, cur_bg = -1, cur_bb = -1;
66
67 // Upper half-block character: U+2580 = UTF-8 0xE2 0x96 0x80
68 static const char HB[3] = {(char)0xE2, (char)0x96, (char)0x80};
69
70 // Process 2 source rows per output line
71 for (int y = 0; y < height; y += 2) {
72 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
73 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
74
75 int x = 0;
76 while (x < width) {
77 // Read top pixel
78 const uint8_t *pT = rowT + (size_t)x * 3u;
79 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
80
81 // Read bottom pixel (or duplicate top if no bottom row)
82 uint8_t rB = rT, gB = gT, bB = bT;
83 if (rowB) {
84 const uint8_t *pB = rowB + (size_t)x * 3u;
85 rB = pB[0];
86 gB = pB[1];
87 bB = pB[2];
88 }
89
90 // Extend run while top and bottom colors match exactly
91 int j = x + 1;
92 for (; j < width; ++j) {
93 const uint8_t *qT = rowT + (size_t)j * 3u;
94 uint8_t rT2 = qT[0], gT2 = qT[1], bT2 = qT[2];
95
96 uint8_t rB2 = rT2, gB2 = gT2, bB2 = bT2;
97 if (rowB) {
98 const uint8_t *qB = rowB + (size_t)j * 3u;
99 rB2 = qB[0];
100 gB2 = qB[1];
101 bB2 = qB[2];
102 }
103
104 // Check if colors match
105 if (!((rT2 == rT && gT2 == gT && bT2 == bT) && (rB2 == rB && gB2 == gB && bB2 == bB)))
106 break;
107 }
108 uint32_t run = (uint32_t)(j - x);
109
110 // Check if transparent (all black = padding)
111 bool is_transparent = (rT == 0 && gT == 0 && bT == 0 && rB == 0 && gB == 0 && bB == 0);
112
113 if (is_transparent) {
114 // Reset colors before transparent areas to prevent color bleeding
115 if (cur_fr != -1 || cur_fg != -1 || cur_fb != -1 || cur_br != -1 || cur_bg != -1 || cur_bb != -1) {
116 emit_reset(&ob);
117 cur_fr = cur_fg = cur_fb = -1;
118 cur_br = cur_bg = cur_bb = -1;
119 }
120 // Emit spaces for transparent area (no RLE for padding)
121 for (uint32_t k = 0; k < run; ++k) {
122 ob_write(&ob, " ", 1);
123 }
124 } else {
125 // Normal colored halfblocks - set fg to TOP, bg to BOTTOM if changed
126 if (cur_fr != rT || cur_fg != gT || cur_fb != bT) {
127 emit_set_fg(&ob, rT, gT, bT);
128 cur_fr = rT;
129 cur_fg = gT;
130 cur_fb = bT;
131 }
132 if (cur_br != rB || cur_bg != gB || cur_bb != bB) {
133 emit_set_bg(&ob, rB, gB, bB);
134 cur_br = rB;
135 cur_bg = gB;
136 cur_bb = bB;
137 }
138
139 // Emit halfblock once, then RLE or literals
140 ob_write(&ob, HB, 3);
141 if (rep_is_profitable(run)) {
142 emit_rep(&ob, run - 1);
143 } else {
144 for (uint32_t k = 1; k < run; ++k) {
145 ob_write(&ob, HB, 3);
146 }
147 }
148 }
149
150 x = j;
151 }
152
153 // End of line: reset and newline (except for last output line)
154 emit_reset(&ob);
155 if (y + 2 < height) {
156 ob_putc(&ob, '\n');
157 }
158 cur_fr = cur_fg = cur_fb = -1;
159 cur_br = cur_bg = cur_bb = -1;
160 }
161
162 // Ensure null terminator
163 ob_term(&ob);
164 return ob.buf;
165}
void emit_set_bg(outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
Emit background color sequence (auto-select mode)
void emit_set_fg(outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
Emit foreground color sequence (auto-select mode)

References outbuf_t::buf, outbuf_t::cap, emit_rep(), emit_reset(), emit_set_bg(), emit_set_fg(), ob_putc(), ob_term(), ob_write(), platform_strdup(), rep_is_profitable(), and SAFE_MALLOC.

Referenced by ascii_convert(), and image_print_with_capabilities().

◆ set_color_quality_mode()

void set_color_quality_mode ( bool  high_quality)

#include <common.h>

Set color quality mode.

Parameters
high_qualitytrue for 24-bit truecolor, false for 256-color

Controls tradeoff between color fidelity and rendering speed.

◆ simd_caches_destroy_all()

void simd_caches_destroy_all ( void  )

#include <common.h>

Destroy all SIMD caches (palette and other caches)

Called during shutdown to clean up resources.

Definition at line 497 of file video/ascii/common.c.

497 {
498 log_dev("SIMD_CACHE: Starting cleanup of all SIMD caches");
499
500 // Only destroy caches if they were ever initialized
501 if (lifecycle_is_initialized(&g_utf8_cache_lc)) {
502 // Destroy shared UTF-8 palette cache (write lock for cleanup)
503 rwlock_wrlock(&g_utf8_cache_rwlock);
504 if (g_utf8_cache_table) {
505 // Free all UTF-8 cache entries using HASH_ITER
506 utf8_palette_cache_t *cache, *tmp;
507 HASH_ITER(hh, g_utf8_cache_table, cache, tmp) {
508 HASH_DEL(g_utf8_cache_table, cache);
509 SAFE_FREE(cache);
510 }
511 g_utf8_cache_table = NULL;
512 // WASM: Skip debug log to avoid blocking
513 // log_debug("UTF8_CACHE: Destroyed shared UTF-8 palette cache");
514 }
515 // Clean up heap arrays
516 if (g_utf8_heap) {
517 SAFE_FREE(g_utf8_heap);
518 g_utf8_heap = NULL;
519 g_utf8_heap_size = 0;
520 }
521 rwlock_wrunlock(&g_utf8_cache_rwlock);
522 // Shutdown lifecycle and destroy rwlock together
523 lifecycle_shutdown(&g_utf8_cache_lc);
524 }
525
526 // Call architecture-specific cache cleanup functions
527 // Note: Only ONE SIMD implementation is compiled based on highest available instruction set
528 // Higher instruction sets (AVX2, SSSE3) handle cleanup for lower ones (SSE2)
529#if SIMD_SUPPORT_SSSE3
530 ssse3_caches_destroy();
531#elif SIMD_SUPPORT_SSE2
532 sse2_caches_destroy();
533#elif SIMD_SUPPORT_SVE
534 sve_caches_destroy();
535#endif
536
537 log_dev("SIMD_CACHE: All SIMD caches destroyed");
538}
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108

References lifecycle_is_initialized(), lifecycle_shutdown(), log_dev, rwlock_wrlock, rwlock_wrunlock, and SAFE_FREE.

Referenced by asciichat_shared_destroy().

◆ str_append_bytes()

void str_append_bytes ( Str *  s,
const void *  src,
size_t  n 
)

#include <common.h>

Append bytes to string buffer.

Parameters
sString buffer structure
srcSource data
nNumber of bytes to append

◆ str_append_c()

void str_append_c ( Str *  s,
char  c 
)

#include <common.h>

Append character to string buffer.

Parameters
sString buffer structure
cCharacter to append

◆ str_destroy()

void str_destroy ( Str *  s)

#include <common.h>

Free string buffer.

Parameters
sString buffer structure

◆ str_init()

void str_init ( Str *  s)

#include <common.h>

Initialize string buffer.

Parameters
sString buffer structure

◆ str_printf()

void str_printf ( Str *  s,
const char *  fmt,
  ... 
)

#include <common.h>

Append formatted string to buffer.

Parameters
sString buffer structure
fmtFormat string
...Variable arguments

◆ str_reserve()

void str_reserve ( Str *  s,
size_t  need 
)

#include <common.h>

Reserve space in string buffer.

Parameters
sString buffer structure
needMinimum bytes needed

◆ write_row_rep_from_arrays_enhanced()

size_t write_row_rep_from_arrays_enhanced ( const uint8_t *  fg_r,
const uint8_t *  fg_g,
const uint8_t *  fg_b,
const uint8_t *  bg_r,
const uint8_t *  bg_g,
const uint8_t *  bg_b,
const uint8_t *  fg_idx,
const uint8_t *  bg_idx,
const char *  ascii_chars,
int  width,
char *  dst,
size_t  cap,
bool  is_truecolor 
)

#include <common.h>

Enhanced REP compression writer.

Parameters
fg_rForeground red array
fg_gForeground green array
fg_bForeground blue array
bg_rBackground red array
bg_gBackground green array
bg_bBackground blue array
fg_idxForeground color indices
bg_idxBackground color indices
ascii_charsCharacter palette
widthImage width in pixels
dstOutput buffer
capOutput buffer capacity
is_truecolorUse truecolor mode
Returns
Number of bytes written

Used by scalar fallbacks and declared in NEON implementation.

Variable Documentation

◆ _pad

uint8_t utf8_char_t::_pad

Padding for alignment

Definition at line 471 of file video/ascii/common.h.

◆ access_count

atomic_t utf8_palette_cache::access_count

Total access count

Definition at line 479 of file video/ascii/common.h.

Referenced by get_utf8_palette_cache().

◆ ascii_palette

char ascii_palette[]
extern

#include <ascii.h>

Default ASCII palette characters.

Default character palette for ASCII art conversion. Contains characters ordered by luminance (dark to light) for mapping pixel brightness to ASCII characters.

Note
External variable (defined in ascii.c).
Default palette: " .:-=+*#%@$" (common ASCII art palette).

◆ avx2_time

double simd_benchmark_t::avx2_time

AVX2 implementation time (seconds)

Definition at line 282 of file video/ascii/common.h.

◆ best_method

const char* simd_benchmark_t::best_method

Name of fastest method

Definition at line 286 of file video/ascii/common.h.

◆ byte_len

◆ cache

◆ cache64

utf8_char_t utf8_palette_cache::cache64[64]

Cache of 64 most common characters

Definition at line 476 of file video/ascii/common.h.

Referenced by get_utf8_palette_cache(), image_print(), image_print_16color(), and image_print_16color_dithered().

◆ cached_score

double utf8_palette_cache::cached_score

Eviction score (higher = keep longer)

Definition at line 482 of file video/ascii/common.h.

◆ cap

size_t Str::cap

Total bytes allocated

Definition at line 154 of file video/ascii/common.h.

◆ cBB

int RLEState::cBB

Current background RGB

Definition at line 220 of file video/ascii/common.h.

◆ cBG

int RLEState::cBG

Definition at line 220 of file video/ascii/common.h.

◆ cBR

int RLEState::cBR

Definition at line 220 of file video/ascii/common.h.

◆ cFB

int RLEState::cFB

Current foreground RGB

Definition at line 219 of file video/ascii/common.h.

◆ cFG

int RLEState::cFG

Definition at line 219 of file video/ascii/common.h.

◆ cFR

int RLEState::cFR

Definition at line 219 of file video/ascii/common.h.

◆ char_index_ramp

uint8_t utf8_palette_cache::char_index_ramp[256]

Luminance to character index mapping

Definition at line 477 of file video/ascii/common.h.

Referenced by get_utf8_palette_cache(), and image_print().

◆ creation_time

uint64_t utf8_palette_cache::creation_time

When this cache was created

Definition at line 481 of file video/ascii/common.h.

Referenced by get_utf8_palette_cache().

◆ data

char* Str::data

Pointer to allocated buffer

Definition at line 152 of file video/ascii/common.h.

◆ dec3_initialized

bool global_dec3_cache_t::dec3_initialized

Whether cache is initialized

Definition at line 107 of file video/ascii/common.h.

Referenced by append_truecolor_bg(), append_truecolor_fg(), append_truecolor_fg_bg(), and init_dec3().

◆ dec3_table

dec3_t global_dec3_cache_t::dec3_table[256]

◆ g_dec3_cache

◆ g_default_luminance_palette

char g_default_luminance_palette[256]
extern

#include <common.h>

Default luminance palette (256 characters)

Definition at line 38 of file video/ascii/common.c.

38{0};

◆ h

int ImageRGB::h

Image height in pixels

Definition at line 234 of file video/ascii/common.h.

◆ heap_index

size_t utf8_palette_cache::heap_index

Index in the eviction min-heap

Definition at line 483 of file video/ascii/common.h.

◆ hh

UT_hash_handle utf8_palette_cache::hh

uthash handle

Definition at line 490 of file video/ascii/common.h.

◆ initialized

bool utf8_palette_cache::initialized

Whether cache is initialized

Definition at line 486 of file video/ascii/common.h.

◆ is_valid

bool utf8_palette_cache::is_valid

Whether cache has valid data

Definition at line 487 of file video/ascii/common.h.

Referenced by get_utf8_palette_cache().

◆ key

uint32_t utf8_palette_cache::key

Palette string hash (uthash key)

Definition at line 484 of file video/ascii/common.h.

Referenced by get_utf8_palette_cache().

◆ last_access_time

atomic_t utf8_palette_cache::last_access_time

Last access timestamp

Definition at line 478 of file video/ascii/common.h.

Referenced by get_utf8_palette_cache().

◆ len [1/2]

◆ len [2/2]

size_t Str::len

Number of bytes currently used

Definition at line 153 of file video/ascii/common.h.

◆ neon_time

double simd_benchmark_t::neon_time

NEON implementation time (seconds)

Definition at line 283 of file video/ascii/common.h.

◆ palette_hash

char utf8_palette_cache::palette_hash[256]

Original palette string for validation

Definition at line 485 of file video/ascii/common.h.

Referenced by get_utf8_palette_cache().

◆ pixels

uint8_t* ImageRGB::pixels

RGB data: w * h * 3 bytes

Definition at line 235 of file video/ascii/common.h.

◆ runLen

int RLEState::runLen

Current run length

Definition at line 221 of file video/ascii/common.h.

◆ s

◆ scalar_time

double simd_benchmark_t::scalar_time

Scalar implementation time (seconds)

Definition at line 279 of file video/ascii/common.h.

◆ seeded

int RLEState::seeded

Whether state is initialized

Definition at line 222 of file video/ascii/common.h.

◆ speedup_best

double simd_benchmark_t::speedup_best

Best speedup ratio

Definition at line 285 of file video/ascii/common.h.

◆ sse2_time

double simd_benchmark_t::sse2_time

SSE2 implementation time (seconds)

Definition at line 280 of file video/ascii/common.h.

◆ ssse3_time

double simd_benchmark_t::ssse3_time

SSSE3 implementation time (seconds)

Definition at line 281 of file video/ascii/common.h.

◆ sve_time

double simd_benchmark_t::sve_time

SVE implementation time (seconds)

Definition at line 284 of file video/ascii/common.h.

◆ total_age_seconds

uint32_t utf8_palette_cache::total_age_seconds

Total age in seconds

Definition at line 480 of file video/ascii/common.h.

◆ utf8_bytes

◆ w

int ImageRGB::w

Image width in pixels

Definition at line 233 of file video/ascii/common.h.

◆ width

uint8_t utf8_char_t::width

Character display width (1 or 2)

Definition at line 468 of file video/ascii/common.h.