ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Character Palettes

🎨 Character palettes for video-to-ASCII conversion More...

Files

file  palette.c
 🎨 Terminal color palette management with Unicode character width detection
 
file  palette.h
 ASCII Palette Management for Video-to-ASCII Conversion.
 

Data Structures

struct  palette_def_t
 Palette definition structure. More...
 
struct  utf8_capabilities_t
 UTF-8 capability detection structure. More...
 
struct  utf8_char_info_t
 UTF-8 character information structure. More...
 
struct  utf8_palette_t
 UTF-8 palette structure. More...
 

Variables

const char * palette_def_t::name
 Human-readable palette name.
 
const char * palette_def_t::chars
 Character sequence (ordered from dark to light)
 
size_t palette_def_t::length
 Number of characters in sequence.
 
bool palette_def_t::requires_utf8
 True if palette contains UTF-8 multi-byte characters.
 
bool palette_def_t::is_validated
 True if palette passed validation checks.
 
bool utf8_capabilities_t::utf8_support
 True if terminal supports UTF-8 (detected automatically)
 
bool utf8_capabilities_t::forced_utf8
 True if user forced UTF-8 via –utf8 flag.
 
char utf8_capabilities_t::terminal_type [32]
 $TERM environment variable value (for detection)
 
char utf8_capabilities_t::locale_encoding [16]
 Current locale encoding (e.g., "UTF-8")
 
char utf8_char_info_t::bytes [4]
 UTF-8 byte sequence (max 4 bytes per character)
 
uint8_t utf8_char_info_t::byte_len
 Number of bytes in UTF-8 encoding (1-4)
 
uint8_t utf8_char_info_t::display_width
 Terminal display width in character cells (1-2)
 
utf8_char_info_t * utf8_palette_t::chars
 Array of UTF-8 character information.
 
size_t utf8_palette_t::char_count
 Number of characters (not bytes!)
 
size_t utf8_palette_t::total_bytes
 Total byte length of palette string.
 
char * utf8_palette_t::raw_string
 Original palette string (for reference/debugging)
 

ASCII Palette Types

enum  palette_type_t {
  PALETTE_UNSET = 0 , PALETTE_STANDARD , PALETTE_BLOCKS , PALETTE_DIGITAL ,
  PALETTE_MINIMAL , PALETTE_COOL , PALETTE_CUSTOM , PALETTE_COUNT
}
 Built-in palette type enumeration. More...
 

Default Palette Constants

const char DEFAULT_ASCII_PALETTE []
 Default ASCII palette for legacy functions.
 
const size_t DEFAULT_ASCII_PALETTE_LEN
 Length of default ASCII palette.
 

Palette Management Functions

const palette_def_t * get_builtin_palette (palette_type_t type)
 Get built-in palette definition.
 
bool validate_palette_chars (const char *chars, size_t len)
 Validate palette character sequence.
 
bool palette_requires_utf8_encoding (const char *chars, size_t len)
 Check if palette requires UTF-8 encoding.
 
bool detect_client_utf8_support (utf8_capabilities_t *caps)
 Detect client UTF-8 support capabilities.
 
palette_type_t select_compatible_palette (palette_type_t requested, bool client_utf8)
 Select compatible palette based on client capabilities.
 
int apply_palette_config (palette_type_t type, const char *custom_chars)
 Apply palette configuration (set global palette)
 
int build_client_luminance_palette (const char *palette_chars, size_t palette_len, char luminance_mapping[256])
 Build luminance mapping table from palette characters.
 
int initialize_client_palette (palette_type_t palette_type, const char *custom_chars, char client_palette_chars[256], size_t *client_palette_len, char client_luminance_palette[256])
 Initialize client palette with full configuration.
 

UTF-8 Palette Functions

utf8_palette_t * utf8_palette_create (const char *palette_string)
 Create a UTF-8 palette from string.
 
void utf8_palette_destroy (utf8_palette_t *palette)
 Destroy a UTF-8 palette and free resources.
 
const utf8_char_info_t * utf8_palette_get_char (const utf8_palette_t *palette, size_t index)
 Get UTF-8 character information at index.
 
size_t utf8_palette_get_char_count (const utf8_palette_t *palette)
 Get number of characters in UTF-8 palette.
 
bool utf8_palette_contains_char (const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes)
 Check if UTF-8 palette contains a specific character.
 
size_t utf8_palette_find_char_index (const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes)
 Find index of UTF-8 character in palette.
 
size_t utf8_palette_find_all_char_indices (const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes, size_t *indices, size_t max_indices)
 Find all indices of UTF-8 character in palette.
 

Built-in Palette Character Strings

#define PALETTE_CHARS_STANDARD   " ...',;:clodxkO0KXNWM"
 Standard ASCII palette character string.
 
#define PALETTE_CHARS_BLOCKS   " ░░▒▒▓▓██"
 Unicode block palette character string.
 
#define PALETTE_CHARS_DIGITAL   " -=≡≣▰▱◼"
 Digital/glitch palette character string.
 
#define PALETTE_CHARS_MINIMAL   " .-+*#"
 Minimal ASCII palette character string.
 
#define PALETTE_CHARS_COOL   " ▁▂▃▄▅▆▇█"
 Cool ascending blocks palette character string.
 

Detailed Description

🎨 Character palettes for video-to-ASCII conversion

This header provides ASCII palette management for converting video frames to ASCII art in ascii-chat. The system supports multiple built-in palettes and custom user-defined palettes, with full UTF-8 support for enhanced visual effects.

CORE FEATURES:

BUILT-IN PALETTES:

The system provides five built-in palette types:

UTF-8 SUPPORT:

The system provides:

PALETTE ARCHITECTURE:

Palettes are organized as:

CLIENT COMPATIBILITY:

The system automatically:

Note
Palettes are ordered from darkest to lightest character.
UTF-8 palettes require terminal UTF-8 support for proper display.
Custom palettes are validated to ensure proper character ordering and compatibility.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
2025

Palette README

Overview

Welcome to the Palette Management module! This is where we turn video frames into beautiful ASCII art. The palette system is all about choosing the right characters to represent different brightness levels—from dark spaces to light block characters, we've got options for every terminal and every aesthetic.

What does the palette system do?

The Palette Management module provides comprehensive ASCII character palette management for converting video frames to ASCII art in ascii-chat. Here's what it gives you:

  • Five built-in palette types (standard, blocks, digital, minimal, cool) so you can choose the style that fits your terminal
  • Custom user-defined palettes via command-line options (if you want something unique, you can create your own)
  • UTF-8 character support with proper encoding handling (for terminals that support Unicode)
  • Automatic UTF-8 capability detection and compatibility checking (we detect what your terminal supports and pick the right palette automatically)
  • Palette validation to ensure proper character ordering (so your custom palettes work correctly)
  • Luminance mapping for optimal brightness-to-character conversion (so dark pixels become dark characters and bright pixels become bright characters)
  • Client compatibility checking and automatic fallback (if your terminal doesn't support UTF-8, we fall back to ASCII automatically)

Implementation: lib/palette.h

Built-in Palettes

ascii-chat provides five built-in palette types, each optimized for different visual styles and terminal capabilities. Think of them as different "art styles" for your ASCII art—some are classic and simple, others are modern and smooth. Let's explore what each one offers:

Standard Palette

The Standard palette is the classic ASCII art style—if you've ever seen ASCII art in movies or old-school terminal demos, this is probably what they were using. It's simple, reliable, and works everywhere.

Characters: ‘" ...’,;:clodxkO0KXNWM"`

This palette uses traditional ASCII characters ordered from darkest (spaces) to lightest (capital letters) for optimal brightness mapping. It's the "tried and true" option—nothing fancy, but it works great.

What makes it special?

  • Pure ASCII: No UTF-8 required, so it works in any terminal, even ancient ones
  • 23 characters total: A good balance between detail and simplicity
  • Wide compatibility: Works in all terminals, even terminals that don't support UTF-8
  • Classic ASCII art aesthetic: The traditional look that everyone recognizes

This is probably what you want if you're not sure which palette to use—it's the safe default that works everywhere.

Blocks Palette

The Blocks palette is our high-quality option for terminals that support UTF-8. It uses Unicode block characters to create smooth gradients that look almost like real video—it's really impressive what you can do with block characters!

Characters: " ░░▒▒▓▓██"

This palette uses Unicode half-block and full-block characters to create a smooth gradient effect. The blocks range from light to dark, creating a continuous gradient that looks much smoother than ASCII characters.

What makes it special?

  • Requires UTF-8 support: Your terminal needs to support UTF-8 for this to work, but most modern terminals do
  • 10 characters total: Just spaces and 4 block pairs (light half-block, dark half-block, light full-block, dark full-block)
  • Smooth gradient effect: The blocks create a much smoother gradient than ASCII characters
  • Best visual quality for UTF-8 terminals: If your terminal supports UTF-8, this is probably the best-looking option

This is what you want if you have a modern terminal and want the best visual quality!

Digital Palette

The Digital palette has a unique glitch/digital aesthetic—think "Matrix" meets "cyberpunk terminal". It uses mathematical and geometric Unicode characters to create a technical, modern look.

Characters: " -=≡≣▰▱◼"

This palette combines mathematical symbols (equals, equivalent) with geometric shapes (squares, blocks) to create a unique visual style. It's not for everyone, but if you like the "cyberpunk hacker" aesthetic, this is for you!

What makes it special?

  • Requires UTF-8 support: Needs UTF-8 for the special characters
  • 10 characters total: A mix of mathematical and geometric symbols
  • Unique glitch/digital aesthetic: Very distinctive look
  • Technical appearance: Looks like something from a sci-fi movie

This is for when you want something unique and technical-looking.

Minimal Palette

The Minimal palette is for when you want the absolute simplest possible rendering. It uses just seven characters (spaces, dots, dashes, plus signs, asterisks, and hashes), so it's fast, simple, and works everywhere.

Characters: " .-+*#"

This is the "less is more" palette—maximum simplicity, maximum compatibility. If you're running on an ancient terminal or want the absolute fastest rendering, this is what you want.

What makes it special?

  • Pure ASCII: No UTF-8 required, works everywhere
  • 7 characters total: Just the basics—spaces, dots, dashes, plus, asterisk, hash
  • Maximum simplicity: As simple as it gets while still being usable
  • Fast rendering: Fewer characters means faster conversion

This is for minimalism lovers and performance purists.

Cool Palette

The Cool palette is another UTF-8 option that uses ascending block characters to create a smooth gradient. It's similar to the Blocks palette, but uses a different set of characters that create a slightly different look.

Characters: " ▁▂▃▄▅▆▇█"

This palette uses Unicode ascending block characters (think "stair steps going up") to create a visually appealing gradient from dark to light. It's smooth, modern, and looks great in UTF-8 terminals.

What makes it special?

  • Requires UTF-8 support: Needs UTF-8 for the ascending block characters
  • 10 characters total: Spaces plus 7 ascending block characters
  • Smooth gradient effect: Creates a nice smooth transition from dark to light
  • Modern visual style: Clean, modern look

This is another great option if you have UTF-8 support and want smooth gradients!

UTF-8 Support

UTF-8 Capability Detection

The system automatically detects client UTF-8 support using multiple methods:

Detection Methods:

  • $TERM environment variable analysis
  • Locale encoding detection (checks LC_ALL, LC_CTYPE, LANG)
  • Terminal type compatibility checking
  • User override via --utf8 flag

Detection Logic:

if (caps.utf8_support) {
// Client supports UTF-8, can use UTF-8 palettes
} else {
// Client doesn't support UTF-8, use ASCII palette
}
}
bool detect_client_utf8_support(utf8_capabilities_t *caps)
Detect client UTF-8 support capabilities.
Definition palette.c:148
palette_type_t select_compatible_palette(palette_type_t requested, bool client_utf8)
Select compatible palette based on client capabilities.
Definition palette.c:219
bool utf8_support
True if terminal supports UTF-8 (detected automatically)
Definition palette.h:139
@ PALETTE_BLOCKS
Unicode block characters: " ░░▒▒▓▓██".
Definition palette.h:90
@ PALETTE_STANDARD
Standard ASCII palette: " ...',;:clodxkO0KXNWM".
Definition palette.h:88
UTF-8 capability detection structure.
Definition palette.h:137

UTF-8 Character Encoding

UTF-8 palettes properly handle multi-byte characters:

Character Information:

  • Byte Sequence: Up to 4 bytes per character
  • Byte Length: 1-4 bytes depending on character
  • Display Width: 1-2 character cells (for full-width characters)

UTF-8 Palette Structure:

utf8_palette_t *palette = utf8_palette_create(" ░░▒▒▓▓██");
size_t char_count = utf8_palette_get_char_count(palette); // 10 characters
// Get character at index
const utf8_char_info_t *char_info = utf8_palette_get_char(palette, 3);
// char_info->bytes = "░"
// char_info->byte_len = 3
// char_info->display_width = 1
void utf8_palette_destroy(utf8_palette_t *palette)
Destroy a UTF-8 palette and free resources.
Definition palette.c:485
size_t utf8_palette_get_char_count(const utf8_palette_t *palette)
Get number of characters in UTF-8 palette.
Definition palette.c:502
const utf8_char_info_t * utf8_palette_get_char(const utf8_palette_t *palette, size_t index)
Get UTF-8 character information at index.
Definition palette.c:494
utf8_palette_t * utf8_palette_create(const char *palette_string)
Create a UTF-8 palette from string.
Definition palette.c:363
UTF-8 character information structure.
Definition palette.h:213
UTF-8 palette structure.
Definition palette.h:236

Important Notes:

  • Character count (not byte count) is used for palette indexing
  • Display width ensures proper terminal rendering
  • Multi-byte characters are handled transparently

Compatibility Checking

The system automatically selects compatible palettes based on client capabilities:

// Request UTF-8 palette
// Check client UTF-8 support
// Select compatible palette
requested,
);
// compatible will be PALETTE_BLOCKS if UTF-8 supported,
// otherwise falls back to PALETTE_STANDARD
palette_type_t
Built-in palette type enumeration.
Definition palette.h:84

Automatic Fallback:

  • UTF-8 palettes (BLOCKS, DIGITAL, COOL) require UTF-8 support
  • If client doesn't support UTF-8, automatically falls back to STANDARD
  • ASCII palettes (STANDARD, MINIMAL) always work
  • Custom palettes are validated for compatibility

Custom Palettes

Defining Custom Palettes

Users can define custom palettes via command-line options:

Command-Line Usage:

# Use custom palette characters
asciichat-client --palette-chars " .:;+=xX$&"
# Or specify custom palette in config file
palette_type = "custom"
palette_chars = " ░▒▓█"

Programmatic Usage:

// Apply custom palette
const char *custom_chars = " .:;+=xX$&";
custom_chars
);
if (err != ASCIICHAT_OK) {
log_error("Failed to apply custom palette");
return err;
}
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
int apply_palette_config(palette_type_t type, const char *custom_chars)
Apply palette configuration (set global palette)
Definition palette.c:250
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:98

Custom Palette Validation

Custom palettes are validated to ensure proper formatting:

Validation Rules:

  • Non-empty character sequence
  • Proper character ordering (if applicable)
  • Valid UTF-8 encoding (for multi-byte characters)
  • Compatible with client capabilities

Validation Example:

const char *custom_chars = " .:;+=xX$&";
size_t len = strlen(custom_chars);
// Validate palette
if (!validate_palette_chars(custom_chars, len)) {
log_error("Invalid custom palette");
return ERROR_INVALID_PALETTE;
}
// Check UTF-8 requirements
if (palette_requires_utf8_encoding(custom_chars, len)) {
// Check client UTF-8 support before using
if (!caps.utf8_support) {
log_warn("Custom palette requires UTF-8 but client doesn't support it");
return ERROR_PALETTE_INCOMPATIBLE;
}
}
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
bool palette_requires_utf8_encoding(const char *chars, size_t len)
Check if palette requires UTF-8 encoding.
Definition palette.c:55
bool validate_palette_chars(const char *chars, size_t len)
Validate palette character sequence.
Definition palette.c:71

Palette Ordering:

  • Palettes should be ordered from darkest to lightest character
  • First character = darkest (for low brightness pixels)
  • Last character = lightest (for high brightness pixels)
  • Ordering affects luminance mapping quality

Luminance Mapping

Luminance mapping is the "secret sauce" that makes video-to-ASCII conversion fast. Instead of calculating which character to use for each pixel every time, we pre-compute a lookup table that maps brightness values directly to character indices. This makes the conversion super fast—we're talking O(1) lookup time!

Luminance Mapping Overview

Luminance mapping converts pixel brightness values (0-255) directly to palette character indices using a pre-computed lookup table. This eliminates the need for searching or calculation during video-to-ASCII conversion, which makes everything significantly faster.

How does it work?

Mapping Process:

  1. Pixel brightness extracted (0-255 range): We calculate how bright each pixel is (from completely black at 0 to completely white at 255)
  2. Direct lookup in luminance mapping table (256 entries): We use the brightness value as an index into a lookup table—no calculation needed!
  3. Character index retrieved (0 to palette_length-1): The lookup table tells us which character index to use
  4. Character selected from palette: We grab that character from the palette
  5. Character rendered to terminal: Finally, we render it to the terminal

The key insight is that we pre-compute the lookup table once during initialization, so during rendering we just do a simple array lookup. This is way faster than calculating which character to use for each pixel every time!

Building Luminance Maps

Luminance mapping tables are built from palette character sequences:

// Get palette characters
// Build luminance mapping
char luminance_mapping[256];
palette->chars,
palette->length,
luminance_mapping
);
// luminance_mapping[0] = 0 (darkest character)
// luminance_mapping[128] = palette->length / 2 (mid-tone character)
// luminance_mapping[255] = palette->length - 1 (lightest character)
const palette_def_t * get_builtin_palette(palette_type_t type)
Get built-in palette definition.
Definition palette.c:47
int build_client_luminance_palette(const char *palette_chars, size_t palette_len, char luminance_mapping[256])
Build luminance mapping table from palette characters.
Definition palette.c:279
const char * chars
Character sequence (ordered from dark to light)
Definition palette.h:120
size_t length
Number of characters in sequence.
Definition palette.h:122
Palette definition structure.
Definition palette.h:116

Mapping Distribution:

  • Brightness 0 → Character index 0 (darkest)
  • Brightness 128 → Character index length/2 (mid-tone)
  • Brightness 255 → Character index length-1 (lightest)
  • Mapping is proportional across the full brightness range

Performance Benefits:

  • O(1) lookup time (constant time)
  • No calculation required during rendering
  • Pre-computed for optimal performance
  • Single array access per pixel

Using Luminance Maps

Luminance maps are used during video-to-ASCII conversion:

// During video frame conversion
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Get pixel brightness (0-255)
uint8_t brightness = calculate_pixel_brightness(pixels, x, y);
// Direct lookup in luminance mapping
char char_index = luminance_mapping[brightness];
// Select character from palette
char ascii_char = palette_chars[char_index];
// Render to terminal
render_char(ascii_char);
}
}
unsigned char uint8_t
Definition common.h:56

Initialization and Configuration

Client Palette Initialization

Complete client palette initialization combines palette selection, validation, and luminance mapping creation:

// Initialize client palette with full configuration
char client_palette_chars[256];
size_t client_palette_len;
char client_luminance_palette[256];
PALETTE_BLOCKS, // Palette type
NULL, // Custom chars (NULL for built-in)
client_palette_chars, // Output: palette characters
&client_palette_len, // Output: palette length
client_luminance_palette // Output: luminance mapping
);
if (err != ASCIICHAT_OK) {
log_error("Failed to initialize client palette");
return err;
}
// Use palette for video conversion
// client_palette_chars contains character sequence
// client_luminance_palette contains brightness-to-index mapping
int initialize_client_palette(palette_type_t palette_type, const char *custom_chars, char client_palette_chars[256], size_t *client_palette_len, char client_luminance_palette[256])
Initialize client palette with full configuration.
Definition palette.c:299

Applying Palette Configuration

Global palette configuration is applied using apply_palette_config():

// Apply built-in palette globally
NULL // NULL for built-in palettes
);
// Apply custom palette globally
" .:;+=xX$&" // Custom character sequence
);

Configuration Steps:

  1. Validate palette (if custom)
  2. Check UTF-8 requirements
  3. Verify client compatibility
  4. Apply palette globally
  5. Build luminance mapping
  6. Make available for rendering

Architecture

Data Structures

Palette Definition:

typedef struct {
const char *name; // Human-readable name
const char *chars; // Character sequence
size_t length; // Number of characters
bool requires_utf8; // UTF-8 requirement flag
bool is_validated; // Validation status

UTF-8 Character Information:

typedef struct {
char bytes[4]; // UTF-8 byte sequence (max 4 bytes)
uint8_t byte_len; // Number of bytes (1-4)
uint8_t display_width; // Terminal display width (1-2)
int display_width(const char *text)
Calculate the visible display width of text with ANSI escape codes.

UTF-8 Palette:

typedef struct {
utf8_char_info_t *chars; // Array of character info
size_t char_count; // Number of characters (not bytes!)
size_t total_bytes; // Total byte length of string
char *raw_string; // Original palette string

UTF-8 Capabilities:

typedef struct {
bool utf8_support; // Detected UTF-8 support
bool forced_utf8; // User-forced UTF-8 flag
char terminal_type[32]; // $TERM value
char locale_encoding[16]; // Locale encoding (e.g., "UTF-8")

API Functions

Palette Management:

UTF-8 Palette Functions:

UTF-8 Detection:

Usage Examples

Basic Palette Usage

Using Built-in Palette:

#include "palette.h"
// Get built-in palette
// Check UTF-8 requirements
if (palette->requires_utf8) {
if (!detect_client_utf8_support(&caps) || !caps.utf8_support) {
log_error("Palette requires UTF-8 but terminal doesn't support it");
return ERROR_PALETTE_INCOMPATIBLE;
}
}
// Apply palette globally
bool requires_utf8
True if palette contains UTF-8 multi-byte characters.
Definition palette.h:124
ASCII Palette Management for Video-to-ASCII Conversion.

Custom Palette Usage

Defining and Using Custom Palette:

// Define custom palette
const char *custom_chars = " .:-=+*#%@";
// Validate palette
if (!validate_palette_chars(custom_chars, strlen(custom_chars))) {
log_error("Invalid custom palette");
return ERROR_INVALID_PALETTE;
}
// Apply custom palette
custom_chars
);
if (err != ASCIICHAT_OK) {
log_error("Failed to apply custom palette");
return err;
}

UTF-8 Palette Usage

Working with UTF-8 Palettes:

// Create UTF-8 palette
utf8_palette_t *palette = utf8_palette_create(" ░░▒▒▓▓██");
// Get character count (not byte count!)
size_t char_count = utf8_palette_get_char_count(palette);
// char_count = 10 (characters, not bytes)
// Get character at index
const utf8_char_info_t *char_info = utf8_palette_get_char(palette, 3);
// char_info->bytes = "░"
// char_info->byte_len = 3
// char_info->display_width = 1
// Find character index
size_t index = utf8_palette_find_char_index(palette, "▒", 3);
// index = 5 (5th character in palette)
// Clean up
size_t utf8_palette_find_char_index(const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes)
Find index of UTF-8 character in palette.
Definition palette.c:526

Client Palette Initialization

Complete Client Palette Setup:

// Detect client UTF-8 support
// Select compatible palette
PALETTE_BLOCKS, // Requested
);
// Initialize complete palette configuration
char client_palette_chars[256];
size_t client_palette_len;
char client_luminance_palette[256];
palette_type,
NULL, // Built-in palette
client_palette_chars,
&client_palette_len,
client_luminance_palette
);
// Use palette for rendering
// client_palette_chars: character sequence
// client_luminance_palette: brightness-to-index mapping

Best Practices

Character Ordering

Always Order from Dark to Light:

  • First character = darkest (for black/low brightness)
  • Last character = lightest (for white/high brightness)
  • Proper ordering ensures accurate luminance mapping
  • Incorrect ordering produces inverted or incorrect output

Example Ordering:

// ✅ CORRECT: Dark to light
" .:-=+*#" // spaces (dark) → # (light)
// ❌ WRONG: Light to dark
"#*+=:-. " // Inverted order

UTF-8 Compatibility

Always Check UTF-8 Requirements:

  • Check if palette requires UTF-8 before using
  • Verify client UTF-8 support for UTF-8 palettes
  • Provide fallback to ASCII palettes
  • Use select_compatible_palette() for automatic selection

Example:

// Check UTF-8 requirements
if (!caps.utf8_support) {
log_warn("UTF-8 palette incompatible with client");
// Fall back to ASCII palette
}
}

Always Validate Custom Palettes

Validate Before Use:

  • Always validate custom palettes
  • Check for empty or invalid sequences
  • Verify UTF-8 encoding for multi-byte characters
  • Ensure proper character ordering

Example:

// Validate before applying
if (!validate_palette_chars(custom_chars, len)) {
log_error("Invalid custom palette");
return ERROR_INVALID_PALETTE;
}
// Apply only if valid

Performance Optimization

Use Luminance Mapping:

  • Always build luminance mapping for performance
  • Use direct lookup instead of calculation
  • Pre-compute mappings during initialization
  • Cache mappings for repeated use

Example:

// Build mapping once during initialization
char luminance_mapping[256];
build_client_luminance_palette(palette_chars, palette_len, luminance_mapping);
// Use direct lookup during rendering (fast!)
char char_index = luminance_mapping[pixel_brightness];
char ascii_char = palette_chars[char_index];

Performance Characteristics

Palette performance is all about speed—we're converting video frames to ASCII in real-time, so every optimization matters. The luminance mapping system makes this super fast, but let's look at what you can expect:

How fast is palette lookup?

Palette Lookup:

  • Luminance mapping: O(1) constant time lookup—that means it takes the same amount of time regardless of how many characters are in the palette
  • No calculation required during rendering: We just do a simple array lookup—no math, no searching, just grab the value from the table
  • Single array access per pixel: One lookup, one character selection, done!
  • Optimal for real-time video conversion: Fast enough that you can convert video frames at 60 FPS without any problems

How fast is UTF-8 processing?

UTF-8 Processing:

  • UTF-8 palette creation: O(n) where n is byte length—this happens once during initialization, so it's not a bottleneck
  • Character index lookup: O(1) with pre-computed structure—once the palette is built, lookups are instant
  • UTF-8 detection: O(1) environment variable checks—we just check a few environment variables, which is essentially free

How much memory does it use?

Memory Usage:

  • ASCII palette: ~23 bytes (STANDARD palette)—super tiny!
  • UTF-8 palette: ~40-100 bytes depending on character count (still very small, even for larger palettes)
  • Luminance mapping: 256 bytes (fixed size)—this is the lookup table, and it's always 256 bytes regardless of palette size
  • UTF-8 character info: ~8 bytes per character—just a tiny bit of metadata per character to track byte length and display width

Memory usage is minimal—even with UTF-8 palettes, you're looking at well under a kilobyte per palette. The luminance mapping table is always 256 bytes (one byte per possible brightness value), which is nothing by modern standards.

Integration with Video-to-ASCII

The palette module integrates with the video conversion pipeline:

Conversion Pipeline:

  1. Video frame captured (RGB pixels)
  2. Pixel brightness calculated (0-255)
  3. Luminance mapping lookup (brightness → char index)
  4. Character selected from palette
  5. Character rendered to terminal

Example Integration:

// Initialize palette
char palette_chars[256];
size_t palette_len;
char luminance_mapping[256];
palette_chars, &palette_len, luminance_mapping);
// Convert frame to ASCII
for (int y = 0; y < frame_height; y++) {
for (int x = 0; x < frame_width; x++) {
// Get pixel brightness
uint8_t brightness = get_pixel_brightness(frame, x, y);
// Lookup character index
size_t char_index = luminance_mapping[brightness];
// Select character
char ascii_char = palette_chars[char_index];
// Render
render_char(ascii_char);
}
}
int frame
Definition splash.c:99
See also
palette.h
Video to ASCII Conversion
Video Frame Buffers

Macro Definition Documentation

◆ PALETTE_CHARS_BLOCKS

#define PALETTE_CHARS_BLOCKS   " ░░▒▒▓▓██"

#include <palette.h>

Unicode block palette character string.

Unicode block characters (half and full blocks) for solid rendering.

Definition at line 170 of file palette.h.

◆ PALETTE_CHARS_COOL

#define PALETTE_CHARS_COOL   " ▁▂▃▄▅▆▇█"

#include <palette.h>

Cool ascending blocks palette character string.

Unicode ascending blocks for smooth gradient effect.

Definition at line 197 of file palette.h.

◆ PALETTE_CHARS_DIGITAL

#define PALETTE_CHARS_DIGITAL   " -=≡≣▰▱◼"

#include <palette.h>

Digital/glitch palette character string.

Digital aesthetic characters for unique visual style.

Definition at line 179 of file palette.h.

◆ PALETTE_CHARS_MINIMAL

#define PALETTE_CHARS_MINIMAL   " .-+*#"

#include <palette.h>

Minimal ASCII palette character string.

Simple ASCII characters for basic rendering.

Definition at line 188 of file palette.h.

◆ PALETTE_CHARS_STANDARD

#define PALETTE_CHARS_STANDARD   " ...',;:clodxkO0KXNWM"

#include <palette.h>

Standard ASCII palette character string.

Classic ASCII palette with varying character density.

Definition at line 161 of file palette.h.

Enumeration Type Documentation

◆ palette_type_t

#include <palette.h>

Built-in palette type enumeration.

Defines the available built-in palette types. Palettes are ordered from darkest to lightest character for optimal brightness mapping.

Enumerator
PALETTE_UNSET 

Unset palette type (will be resolved to default or custom)

PALETTE_STANDARD 

Standard ASCII palette: " ...',;:clodxkO0KXNWM".

PALETTE_BLOCKS 

Unicode block characters: " ░░▒▒▓▓██".

PALETTE_DIGITAL 

Digital/glitch aesthetic: " -=≡≣▰▱◼".

PALETTE_MINIMAL 

Simple ASCII: " .-+*#".

PALETTE_COOL 

Ascending blocks: " ▁▂▃▄▅▆▇█".

PALETTE_CUSTOM 

User-defined via –palette-chars.

PALETTE_COUNT 

Number of palette types (not a valid palette)

Definition at line 84 of file palette.h.

84 {
86 PALETTE_UNSET = 0,
@ PALETTE_COUNT
Number of palette types (not a valid palette)
Definition palette.h:100
@ PALETTE_COOL
Ascending blocks: " ▁▂▃▄▅▆▇█".
Definition palette.h:96
@ PALETTE_UNSET
Unset palette type (will be resolved to default or custom)
Definition palette.h:86
@ PALETTE_DIGITAL
Digital/glitch aesthetic: " -=≡≣▰▱◼".
Definition palette.h:92
@ PALETTE_MINIMAL
Simple ASCII: " .-+*#".
Definition palette.h:94

Function Documentation

◆ apply_palette_config()

int apply_palette_config ( palette_type_t  type,
const char *  custom_chars 
)

#include <palette.h>

Apply palette configuration (set global palette)

Parameters
typePalette type to apply
custom_charsCustom palette characters (required if type is PALETTE_CUSTOM, NULL otherwise)
Returns
ASCIICHAT_OK on success, error code on failure

Applies a palette configuration globally. For built-in palettes, uses the built-in character sequence. For custom palettes, validates and uses the provided custom_chars string.

Note
Custom palette characters must pass validation before being applied.
This function sets the global palette used for video-to-ASCII conversion.
Warning
Custom palette characters must be ordered from darkest to lightest.

Definition at line 250 of file palette.c.

250 {
251 // This function is now used only for client-side initialization
252 // Server uses initialize_client_palette() for per-client palettes
253
254 log_debug("Client palette config: type=%d, custom_chars=%s", type, custom_chars ? custom_chars : "(none)");
255
256 // Just validate the palette - no global state changes
257 if (type == PALETTE_CUSTOM) {
258 if (!custom_chars || strlen(custom_chars) == 0) {
259 SET_ERRNO(ERROR_INVALID_PARAM, "Custom palette requested but no characters provided");
260 return -1;
261 }
262
263 if (!validate_palette_chars(custom_chars, strlen(custom_chars))) {
264 SET_ERRNO(ERROR_INVALID_PARAM, "Custom palette validation failed");
265 return -1;
266 }
267 } else {
268 const palette_def_t *palette = get_builtin_palette(type);
269 if (!palette) {
270 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid palette type: %d", type);
271 return -1;
272 }
273 }
274
275 return 0; // Validation successful, no global state to update
276}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_PARAM
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548

References ERROR_INVALID_PARAM, get_builtin_palette(), log_debug, PALETTE_CUSTOM, SET_ERRNO, and validate_palette_chars().

Referenced by main().

◆ build_client_luminance_palette()

int build_client_luminance_palette ( const char *  palette_chars,
size_t  palette_len,
char  luminance_mapping[256] 
)

#include <palette.h>

Build luminance mapping table from palette characters.

Parameters
palette_charsPalette character sequence (must not be NULL)
palette_lenLength of palette character sequence
luminance_mappingOutput array for luminance mapping (must not be NULL, 256 elements)
Returns
ASCIICHAT_OK on success, error code on failure

Builds a luminance mapping table that maps pixel brightness values (0-255) to palette character indices. The mapping is used for efficient video-to-ASCII conversion by direct lookup instead of searching.

Note
The mapping array must have 256 elements (one per possible brightness value).
Palette characters are mapped proportionally across the brightness range.
Resulting mapping provides optimal brightness-to-character conversion.

Definition at line 279 of file palette.c.

279 {
280 if (!palette_chars || palette_len == 0 || !luminance_mapping) {
281 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for client luminance palette");
282 return -1;
283 }
284
285 // Map 256 luminance values to palette indices for this specific client
286 for (int i = 0; i < 256; i++) {
287 // Linear mapping with proper rounding
288 size_t palette_index = (i * (palette_len - 1) + 127) / 255;
289 if (palette_index >= palette_len) {
290 palette_index = palette_len - 1;
291 }
292 luminance_mapping[i] = palette_chars[palette_index];
293 }
294
295 return 0;
296}

References ERROR_INVALID_PARAM, and SET_ERRNO.

Referenced by initialize_client_palette().

◆ detect_client_utf8_support()

bool detect_client_utf8_support ( utf8_capabilities_t *  caps)

#include <palette.h>

Detect client UTF-8 support capabilities.

Parameters
capsOutput structure for capabilities (must not be NULL)
Returns
true if detection succeeded, false otherwise

Detects client terminal UTF-8 support by checking environment variables, locale settings, and terminal type. Results are stored in the capabilities structure.

Note
Detection checks $TERM environment variable and locale encoding.
User can force UTF-8 support via –utf8 flag (stored in forced_utf8).

Definition at line 148 of file palette.c.

148 {
149 if (!caps) {
150 return false;
151 }
152
153 // Initialize structure
154 SAFE_MEMSET(caps, sizeof(utf8_capabilities_t), 0, sizeof(utf8_capabilities_t));
155
156 // Check environment variables
157 const char *term = SAFE_GETENV("TERM");
158
159 // Store terminal type
160 if (term) {
161 SAFE_STRNCPY(caps->terminal_type, term, sizeof(caps->terminal_type));
162 }
163
164 // Use platform-specific UTF-8 detection from platform abstraction layer
166
167 if (caps->utf8_support) {
168 SAFE_STRNCPY(caps->locale_encoding, "UTF-8", sizeof(caps->locale_encoding));
169 } else {
170 // Try to detect encoding via locale
171 char *current_locale = setlocale(LC_CTYPE, NULL);
172 char *old_locale = NULL;
173 if (current_locale) {
174 SAFE_STRDUP(old_locale, current_locale);
175 }
176 if (setlocale(LC_CTYPE, "")) {
177#ifndef _WIN32
178 const char *codeset = nl_langinfo(CODESET);
179 if (codeset) {
180 SAFE_STRNCPY(caps->locale_encoding, codeset, sizeof(caps->locale_encoding));
181 }
182#else
183 // Windows may not have locale set but still support UTF-8
184 SAFE_STRNCPY(caps->locale_encoding, "CP1252", sizeof(caps->locale_encoding));
185#endif
186 // Restore old locale
187 if (old_locale) {
188 (void)setlocale(LC_CTYPE, old_locale);
189 }
190 }
191 // Always free old_locale regardless of setlocale success
192 if (old_locale) {
193 SAFE_FREE(old_locale);
194 }
195 }
196
197 // Check for known UTF-8 supporting terminals
198 if (term) {
199 const char *utf8_terminals[] = {
200 "xterm-256color", "screen-256color", "tmux-256color", "alacritty", "kitty", "iterm",
201 "iterm2", "gnome-terminal", "konsole", "terminology", NULL};
202
203 for (int i = 0; utf8_terminals[i]; i++) {
204 if (strstr(term, utf8_terminals[i])) {
205 caps->utf8_support = true;
206 break;
207 }
208 }
209 }
210
211 log_debug("UTF-8 support detection: %s (term=%s, encoding=%s)", caps->utf8_support ? "supported" : "not supported",
212 caps->terminal_type[0] ? caps->terminal_type : "unknown",
213 caps->locale_encoding[0] ? caps->locale_encoding : "unknown");
214
215 return caps->utf8_support;
216}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:469
#define SAFE_GETENV(name)
Definition common.h:434
#define SAFE_STRDUP(dst, src)
Definition common.h:418
char terminal_type[32]
$TERM environment variable value (for detection)
Definition palette.h:143
char locale_encoding[16]
Current locale encoding (e.g., "UTF-8")
Definition palette.h:145
bool terminal_supports_utf8(void)
Check if terminal supports UTF-8.

References utf8_capabilities_t::locale_encoding, log_debug, SAFE_FREE, SAFE_GETENV, SAFE_MEMSET, SAFE_STRDUP, SAFE_STRNCPY, terminal_supports_utf8(), utf8_capabilities_t::terminal_type, and utf8_capabilities_t::utf8_support.

◆ get_builtin_palette()

const palette_def_t * get_builtin_palette ( palette_type_t  type)

#include <palette.h>

Get built-in palette definition.

Parameters
typePalette type (must be < PALETTE_COUNT and != PALETTE_CUSTOM)
Returns
Pointer to palette definition, or NULL if type is invalid

Returns a pointer to the built-in palette definition for the specified type. The returned pointer points to a statically allocated structure and should not be freed.

Note
PALETTE_CUSTOM is not a built-in palette and returns NULL.
The palette definition includes character sequence, length, and UTF-8 requirement information.

Definition at line 47 of file palette.c.

47 {
48 if (type >= PALETTE_COUNT || type == PALETTE_CUSTOM || type == PALETTE_UNSET) {
49 return NULL;
50 }
51 return &builtin_palettes[type];
52}

References PALETTE_COUNT, PALETTE_CUSTOM, and PALETTE_UNSET.

Referenced by apply_palette_config(), initialize_client_palette(), and select_compatible_palette().

◆ initialize_client_palette()

int initialize_client_palette ( palette_type_t  palette_type,
const char *  custom_chars,
char  client_palette_chars[256],
size_t *  client_palette_len,
char  client_luminance_palette[256] 
)

#include <palette.h>

Initialize client palette with full configuration.

Parameters
palette_typePalette type to initialize
custom_charsCustom palette characters (required if type is PALETTE_CUSTOM, NULL otherwise)
client_palette_charsOutput buffer for palette characters (must not be NULL, 256 elements)
client_palette_lenOutput pointer for palette length (must not be NULL)
client_luminance_paletteOutput buffer for luminance mapping (must not be NULL, 256 elements)
Returns
ASCIICHAT_OK on success, error code on failure

Initializes a complete client palette configuration including character sequence and luminance mapping. This is a convenience function that combines palette selection, validation, and luminance mapping creation.

Note
Output buffers must be large enough (256 elements for palette_chars and luminance_palette).
The function handles both built-in and custom palettes.
Luminance mapping is automatically generated from the character sequence.

Definition at line 299 of file palette.c.

300 {
301 const palette_def_t *palette = NULL;
302 const char *chars_to_use = NULL;
303 size_t len_to_use = 0;
304
305 if (palette_type == PALETTE_CUSTOM) {
306 if (!custom_chars) {
307 SET_ERRNO(ERROR_INVALID_PARAM, "Client requested custom palette but custom_chars is NULL");
308 return -1;
309 }
310
311 len_to_use = strlen(custom_chars);
312 if (len_to_use == 0) {
313 SET_ERRNO(ERROR_INVALID_PARAM, "Client requested custom palette but custom_chars is empty");
314 return -1;
315 }
316 if (len_to_use >= 256) {
317 SET_ERRNO(ERROR_INVALID_PARAM, "Client custom palette too long: %zu chars", len_to_use);
318 return -1;
319 }
320
321 // Validate custom palette
322 if (!validate_palette_chars(custom_chars, len_to_use)) {
323 SET_ERRNO(ERROR_INVALID_PARAM, "Client custom palette validation failed");
324 return -1;
325 }
326
327 chars_to_use = custom_chars;
328 } else {
329 palette = get_builtin_palette(palette_type);
330 if (!palette) {
331 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid client palette type: %d", palette_type);
332 return -1;
333 }
334
335 chars_to_use = palette->chars;
336 len_to_use = strlen(palette->chars); // Use actual byte count for UTF-8, not character count
337
338 // Skip validation for built-in palettes since they're already validated
339 log_dev("Using built-in palette: %s, chars='%s', char_count=%zu, byte_len=%zu", palette->name, chars_to_use,
340 palette->length, len_to_use);
341 }
342
343 // Copy palette to client cache
344 SAFE_MEMCPY(client_palette_chars, len_to_use, chars_to_use, len_to_use);
345 client_palette_chars[len_to_use] = '\0';
346 *client_palette_len = len_to_use;
347
348 // Build client-specific luminance mapping
349 if (build_client_luminance_palette(chars_to_use, len_to_use, client_luminance_palette) != 0) {
350 SET_ERRNO(ERROR_INVALID_STATE, "Failed to build client luminance palette");
351 return -1;
352 }
353
354 log_dev("Initialized client palette: type=%d, %zu chars, first_char='%c', last_char='%c'", palette_type, len_to_use,
355 chars_to_use[0], chars_to_use[len_to_use - 1]);
356
357 return 0;
358}
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:468
@ ERROR_INVALID_STATE
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
const char * name
Human-readable palette name.
Definition palette.h:118

References build_client_luminance_palette(), palette_def_t::chars, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, get_builtin_palette(), palette_def_t::length, log_dev, palette_def_t::name, PALETTE_CUSTOM, SAFE_MEMCPY, SET_ERRNO, and validate_palette_chars().

Referenced by handle_client_capabilities_packet(), and session_display_create().

◆ palette_requires_utf8_encoding()

bool palette_requires_utf8_encoding ( const char *  chars,
size_t  len 
)

#include <palette.h>

Check if palette requires UTF-8 encoding.

Parameters
charsCharacter sequence to check (must not be NULL)
lenLength of character sequence in bytes
Returns
true if palette contains UTF-8 multi-byte characters, false otherwise

Determines whether a palette contains UTF-8 multi-byte characters that require UTF-8 terminal support. ASCII-only palettes return false.

Note
Single-byte ASCII characters (< 0x80) are not considered UTF-8.
This check determines if client must have UTF-8 support for palette.

Definition at line 55 of file palette.c.

55 {
56 // Handle NULL or empty string
57 if (!chars || len == 0) {
58 return false;
59 }
60
61 for (size_t i = 0; i < len; i++) {
62 // Any byte with high bit set indicates UTF-8
63 if ((unsigned char)chars[i] >= 128) {
64 return true;
65 }
66 }
67 return false;
68}

◆ select_compatible_palette()

palette_type_t select_compatible_palette ( palette_type_t  requested,
bool  client_utf8 
)

#include <palette.h>

Select compatible palette based on client capabilities.

Parameters
requestedRequested palette type
client_utf8True if client supports UTF-8, false otherwise
Returns
Compatible palette type (may differ from requested)

Selects a palette that is compatible with the client's UTF-8 capabilities. If the requested palette requires UTF-8 but the client doesn't support it, falls back to an ASCII-only palette.

Note
UTF-8 palettes (BLOCKS, DIGITAL, COOL) require UTF-8 support.
ASCII palettes (STANDARD, MINIMAL) work with any terminal.
Returns requested palette if compatible, otherwise returns STANDARD.

Definition at line 219 of file palette.c.

219 {
220 const palette_def_t *palette = get_builtin_palette(requested);
221
222 // Custom palettes are validated separately
223 if (requested == PALETTE_CUSTOM) {
224 return PALETTE_CUSTOM;
225 }
226
227 if (!palette) {
228 log_warn("Invalid palette type %d, falling back to standard", requested);
229 return PALETTE_STANDARD;
230 }
231
232 // If palette requires UTF-8 but client doesn't support it, find fallback
233 if (palette->requires_utf8 && !client_utf8) {
234 log_warn("Client doesn't support UTF-8, falling back from %s", palette->name);
235
236 // Fallback hierarchy
237 switch (requested) {
238 case PALETTE_BLOCKS:
239 case PALETTE_DIGITAL:
240 case PALETTE_COOL:
241 default:
242 return PALETTE_STANDARD; // ASCII equivalent
243 }
244 }
245
246 return requested; // Compatible, use as requested
247}

References get_builtin_palette(), log_warn, palette_def_t::name, PALETTE_BLOCKS, PALETTE_COOL, PALETTE_CUSTOM, PALETTE_DIGITAL, PALETTE_STANDARD, and palette_def_t::requires_utf8.

◆ utf8_palette_contains_char()

bool utf8_palette_contains_char ( const utf8_palette_t *  palette,
const char *  utf8_char,
size_t  char_bytes 
)

#include <palette.h>

Check if UTF-8 palette contains a specific character.

Parameters
paletteUTF-8 palette (must not be NULL)
utf8_charUTF-8 character bytes (must not be NULL)
char_bytesNumber of bytes in UTF-8 character (1-4)
Returns
true if character is found in palette, false otherwise

Checks whether the UTF-8 palette contains a specific UTF-8 character by comparing the character's byte sequence with all characters in the palette.

Note
char_bytes must match the actual UTF-8 byte length of the character.
Character comparison is byte-accurate for multi-byte characters.

Definition at line 510 of file palette.c.

510 {
511 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4) {
512 return false;
513 }
514
515 for (size_t i = 0; i < palette->char_count; i++) {
516 const utf8_char_info_t *char_info = &palette->chars[i];
517 if (char_info->byte_len == char_bytes && memcmp(char_info->bytes, utf8_char, char_bytes) == 0) {
518 return true;
519 }
520 }
521
522 return false;
523}
uint8_t byte_len
Number of bytes in UTF-8 encoding (1-4)
Definition palette.h:217
char bytes[4]
UTF-8 byte sequence (max 4 bytes per character)
Definition palette.h:215
size_t char_count
Number of characters (not bytes!)
Definition palette.h:240
utf8_char_info_t * chars
Array of UTF-8 character information.
Definition palette.h:238

References utf8_char_info_t::byte_len, utf8_char_info_t::bytes, utf8_palette_t::char_count, and utf8_palette_t::chars.

◆ utf8_palette_create()

utf8_palette_t * utf8_palette_create ( const char *  palette_string)

#include <palette.h>

Create a UTF-8 palette from string.

Parameters
palette_stringUTF-8 palette string (must not be NULL)
Returns
Pointer to UTF-8 palette, or NULL on failure

Creates a UTF-8 palette structure by parsing a UTF-8 character string. The string is analyzed to extract individual UTF-8 characters and their encoding information (bytes, byte length, display width).

Note
The palette string can contain multi-byte UTF-8 characters.
Character count (not byte count) is stored in the palette structure.
Display width is calculated for proper terminal rendering.
Warning
Must call utf8_palette_destroy() to free resources.

Definition at line 363 of file palette.c.

363 {
364 if (!palette_string || *palette_string == '\0') {
365 return NULL;
366 }
367
368 utf8_palette_t *palette;
369 palette = SAFE_MALLOC(sizeof(utf8_palette_t), utf8_palette_t *);
370
371 // Count UTF-8 characters (not bytes)
372 size_t char_count = 0;
373 const char *p = palette_string;
374 size_t total_bytes = strlen(palette_string);
375
376 // First pass: count characters
377 size_t bytes_processed = 0;
378 while (bytes_processed < total_bytes) {
379 int bytes = 1;
380 unsigned char c = (unsigned char)p[0];
381
382 if ((c & 0x80) == 0) {
383 bytes = 1; // ASCII
384 } else if ((c & 0xE0) == 0xC0) {
385 bytes = 2; // 2-byte UTF-8
386 } else if ((c & 0xF0) == 0xE0) {
387 bytes = 3; // 3-byte UTF-8
388 } else if ((c & 0xF8) == 0xF0) {
389 bytes = 4; // 4-byte UTF-8
390 }
391
392 // Verify we have enough bytes left
393 if (bytes_processed + bytes > total_bytes) {
394 break;
395 }
396
397 p += bytes;
398 bytes_processed += bytes;
399 char_count++;
400 }
401
402 // Validate we got at least one character
403 if (char_count == 0) {
404 SET_ERRNO(ERROR_INVALID_PARAM, "Palette string contains no valid UTF-8 characters");
405 SAFE_FREE(palette);
406 return NULL;
407 }
408
409 // Allocate character array
410 palette->chars = SAFE_MALLOC(char_count * sizeof(utf8_char_info_t), utf8_char_info_t *);
411 palette->raw_string = SAFE_MALLOC(total_bytes + 1, char *);
412 // Explicit NULL checks to satisfy static analyzer (SAFE_MALLOC calls FATAL on failure)
413 if (palette->chars == NULL || palette->raw_string == NULL) {
414 SAFE_FREE(palette->chars);
415 SAFE_FREE(palette->raw_string);
416 SAFE_FREE(palette);
417 SET_ERRNO(ERROR_MEMORY, "Failed to allocate palette character array");
418 return NULL;
419 }
420
421 SAFE_MEMCPY(palette->raw_string, total_bytes + 1, palette_string, total_bytes + 1);
422 palette->char_count = char_count;
423 palette->total_bytes = total_bytes; // Use strlen() value
424
425 // Second pass: parse characters
426 p = palette_string;
427 size_t char_idx = 0;
428 bytes_processed = 0;
429
430 // Set locale for wcwidth - save a copy of the old locale
431 char old_locale[256] = {0};
432 char *current_locale = setlocale(LC_CTYPE, NULL);
433 if (current_locale) {
434 SAFE_STRNCPY(old_locale, current_locale, sizeof(old_locale));
435 }
436 (void)setlocale(LC_CTYPE, "");
437
438 while (char_idx < char_count && bytes_processed < total_bytes) {
439 utf8_char_info_t *char_info = &palette->chars[char_idx];
440
441 // Determine UTF-8 byte length
442 unsigned char c = (unsigned char)*p;
443 int bytes = 1;
444
445 if ((c & 0x80) == 0) {
446 bytes = 1;
447 } else if ((c & 0xE0) == 0xC0) {
448 bytes = 2;
449 } else if ((c & 0xF0) == 0xE0) {
450 bytes = 3;
451 } else if ((c & 0xF8) == 0xF0) {
452 bytes = 4;
453 }
454
455 // Verify we have enough bytes left
456 if (bytes_processed + bytes > total_bytes) {
457 break;
458 }
459
460 // Copy bytes and null-terminate
461 SAFE_MEMCPY(char_info->bytes, bytes, p, bytes);
462 if (bytes < 4) {
463 SAFE_MEMSET(char_info->bytes + bytes, 4 - bytes, 0, 4 - bytes);
464 }
465 char_info->byte_len = bytes;
466
467 // Get display width using utf8_display_width
468 int width = utf8_display_width_n(p, bytes);
469 char_info->display_width = (width > 0 && width <= 2) ? width : 1;
470
471 p += bytes;
472 bytes_processed += bytes;
473 char_idx++;
474 }
475
476 // Restore locale
477 if (old_locale[0] != '\0') {
478 (void)setlocale(LC_CTYPE, old_locale);
479 }
480
481 return palette;
482}
#define SAFE_MALLOC(size, cast)
Definition common.h:264
@ ERROR_MEMORY
Definition error_codes.h:56
uint8_t display_width
Terminal display width in character cells (1-2)
Definition palette.h:219
size_t total_bytes
Total byte length of palette string.
Definition palette.h:242
char * raw_string
Original palette string (for reference/debugging)
Definition palette.h:244
int utf8_display_width_n(const char *str, size_t max_bytes)
Calculate terminal display width of a UTF-8 string up to byte limit.
Definition utf8.c:91

References utf8_char_info_t::byte_len, utf8_char_info_t::bytes, utf8_palette_t::char_count, utf8_palette_t::chars, utf8_char_info_t::display_width, ERROR_INVALID_PARAM, ERROR_MEMORY, utf8_palette_t::raw_string, SAFE_FREE, SAFE_MALLOC, SAFE_MEMCPY, SAFE_MEMSET, SAFE_STRNCPY, SET_ERRNO, utf8_palette_t::total_bytes, and utf8_display_width_n().

Referenced by build_ramp64().

◆ utf8_palette_destroy()

void utf8_palette_destroy ( utf8_palette_t *  palette)

#include <palette.h>

Destroy a UTF-8 palette and free resources.

Parameters
paletteUTF-8 palette to destroy (can be NULL)

Destroys a UTF-8 palette and frees all associated memory including character arrays and the raw string. Safe to call multiple times or with NULL pointer.

Note
All memory allocated by utf8_palette_create() is freed.

Definition at line 485 of file palette.c.

485 {
486 if (palette) {
487 SAFE_FREE(palette->chars);
488 SAFE_FREE(palette->raw_string);
489 SAFE_FREE(palette);
490 }
491}

References utf8_palette_t::chars, utf8_palette_t::raw_string, and SAFE_FREE.

Referenced by build_ramp64().

◆ utf8_palette_find_all_char_indices()

size_t utf8_palette_find_all_char_indices ( const utf8_palette_t *  palette,
const char *  utf8_char,
size_t  char_bytes,
size_t *  indices,
size_t  max_indices 
)

#include <palette.h>

Find all indices of UTF-8 character in palette.

Parameters
paletteUTF-8 palette (must not be NULL)
utf8_charUTF-8 character bytes (must not be NULL)
char_bytesNumber of bytes in UTF-8 character (1-4)
indicesOutput array for indices (must not be NULL)
max_indicesMaximum number of indices to return (size of indices array)
Returns
Number of indices found (may be less than max_indices)

Finds all occurrences of a UTF-8 character in the palette and stores their indices in the provided array. Returns the number of indices found.

Note
If more occurrences exist than max_indices, only the first max_indices are stored.
Returns 0 if character is not found in palette.
Indices are stored in order of appearance in the palette.
Warning
Indices array must be large enough to hold max_indices size_t values.

Definition at line 543 of file palette.c.

544 {
545 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4 || !indices || max_indices == 0) {
546 return 0;
547 }
548
549 size_t found_count = 0;
550
551 for (size_t i = 0; i < palette->char_count && found_count < max_indices; i++) {
552 const utf8_char_info_t *char_info = &palette->chars[i];
553 if (char_info->byte_len == char_bytes && memcmp(char_info->bytes, utf8_char, char_bytes) == 0) {
554 indices[found_count++] = i;
555 }
556 }
557
558 return found_count;
559}

References utf8_char_info_t::byte_len, utf8_char_info_t::bytes, utf8_palette_t::char_count, and utf8_palette_t::chars.

◆ utf8_palette_find_char_index()

size_t utf8_palette_find_char_index ( const utf8_palette_t *  palette,
const char *  utf8_char,
size_t  char_bytes 
)

#include <palette.h>

Find index of UTF-8 character in palette.

Parameters
paletteUTF-8 palette (must not be NULL)
utf8_charUTF-8 character bytes (must not be NULL)
char_bytesNumber of bytes in UTF-8 character (1-4)
Returns
Character index if found, SIZE_MAX if not found

Searches the UTF-8 palette for a specific character and returns its index (0-based) if found. Returns SIZE_MAX if the character is not in the palette.

Note
Returns the first occurrence if character appears multiple times.
For multiple occurrences, use utf8_palette_find_all_char_indices().
Index is character-based, suitable for palette lookup operations.

Definition at line 526 of file palette.c.

526 {
527 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4) {
528 return (size_t)-1;
529 }
530
531 for (size_t i = 0; i < palette->char_count; i++) {
532 const utf8_char_info_t *char_info = &palette->chars[i];
533 if (char_info->byte_len == char_bytes && memcmp(char_info->bytes, utf8_char, char_bytes) == 0) {
534 return i;
535 }
536 }
537
538 return (size_t)-1;
539}

References utf8_char_info_t::byte_len, utf8_char_info_t::bytes, utf8_palette_t::char_count, and utf8_palette_t::chars.

◆ utf8_palette_get_char()

const utf8_char_info_t * utf8_palette_get_char ( const utf8_palette_t *  palette,
size_t  index 
)

#include <palette.h>

Get UTF-8 character information at index.

Parameters
paletteUTF-8 palette (must not be NULL)
indexCharacter index (0-based, must be < char_count)
Returns
Pointer to UTF-8 character info, or NULL if index is invalid

Returns a pointer to the UTF-8 character information structure at the specified index. The returned pointer points to internal palette data and should not be freed.

Note
Index is character-based, not byte-based (important for multi-byte characters).
Returns NULL if index is out of range.
The returned structure contains bytes, byte_len, and display_width.

Definition at line 494 of file palette.c.

494 {
495 if (!palette || index >= palette->char_count) {
496 return NULL;
497 }
498 return &palette->chars[index];
499}

References utf8_palette_t::char_count, and utf8_palette_t::chars.

Referenced by build_ramp64().

◆ utf8_palette_get_char_count()

size_t utf8_palette_get_char_count ( const utf8_palette_t *  palette)

#include <palette.h>

Get number of characters in UTF-8 palette.

Parameters
paletteUTF-8 palette (must not be NULL)
Returns
Number of characters (not bytes)

Returns the number of characters in the UTF-8 palette. This is the character count, not the byte count, which is important for proper palette indexing.

Note
For multi-byte UTF-8 characters, char_count is less than total_bytes.
Use this for palette iteration and bounds checking.

Definition at line 502 of file palette.c.

502 {
503 if (!palette) {
504 return 0;
505 }
506 return palette->char_count;
507}

References utf8_palette_t::char_count.

Referenced by build_ramp64().

◆ validate_palette_chars()

bool validate_palette_chars ( const char *  chars,
size_t  len 
)

#include <palette.h>

Validate palette character sequence.

Parameters
charsCharacter sequence to validate (must not be NULL)
lenLength of character sequence in bytes
Returns
true if palette is valid, false otherwise

Validates that a palette character sequence is properly formed and ordered. Checks for:

  • Non-empty sequence
  • Valid character ordering (if applicable)
  • Proper UTF-8 encoding (for multi-byte characters)
Note
Validation rules may vary for ASCII vs UTF-8 palettes.
Empty palettes or invalid sequences return false.

Definition at line 71 of file palette.c.

71 {
72 if (!chars || len == 0) {
73 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: empty or NULL palette");
74 return false;
75 }
76
77 if (len > 256) {
78 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: palette too long (%zu chars, max 256)", len);
79 return false;
80 }
81
82 // Decode UTF-8 string into codepoints
83 uint32_t codepoints[256];
84 size_t codepoint_count = utf8_to_codepoints(chars, codepoints, 256);
85
86 if (codepoint_count == SIZE_MAX) {
87 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: invalid UTF-8 sequence");
88 return false;
89 }
90
91 // Validate each codepoint
92 for (size_t i = 0; i < codepoint_count; i++) {
93 uint32_t cp = codepoints[i];
94
95 // Check for control characters (except tab)
96 if (cp < 32 && cp != '\t') {
97 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: control character at position %zu", i);
98 return false;
99 }
100
101 // Convert codepoint to UTF-8 and check display width
102 // For ASCII characters, this is straightforward
103 // For multi-byte UTF-8, we need to check the display width
104 if (cp < 128) {
105 // ASCII character - always width 1
106 continue;
107 } else {
108 // Non-ASCII: use utf8_display_width to check character width
109 // Create a temporary buffer with the UTF-8 encoded character
110 uint8_t utf8_buf[5] = {0};
111
112 // Encode codepoint to UTF-8 (simple for demo, should use proper encoder)
113 if (cp <= 0x7F) {
114 utf8_buf[0] = (uint8_t)cp;
115 } else if (cp <= 0x7FF) {
116 utf8_buf[0] = (uint8_t)(0xC0 | (cp >> 6));
117 utf8_buf[1] = (uint8_t)(0x80 | (cp & 0x3F));
118 } else if (cp <= 0xFFFF) {
119 utf8_buf[0] = (uint8_t)(0xE0 | (cp >> 12));
120 utf8_buf[1] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
121 utf8_buf[2] = (uint8_t)(0x80 | (cp & 0x3F));
122 } else if (cp <= 0x10FFFF) {
123 utf8_buf[0] = (uint8_t)(0xF0 | (cp >> 18));
124 utf8_buf[1] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
125 utf8_buf[2] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
126 utf8_buf[3] = (uint8_t)(0x80 | (cp & 0x3F));
127 } else {
128 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: invalid codepoint at position %zu", i);
129 return false;
130 }
131
132 // Check character width - allow width 1 and 2 (for emoji and wide characters)
133 int width = utf8_display_width((const char *)utf8_buf);
134 if (width < 0 || width > 2) {
136 "Palette validation failed: character at position %zu has invalid width %d (must be 1 or 2)", i,
137 width);
138 return false;
139 }
140 }
141 }
142
143 log_debug("Palette validation successful: %zu characters validated", codepoint_count);
144 return true;
145}
unsigned int uint32_t
Definition common.h:58
size_t utf8_to_codepoints(const char *str, uint32_t *out_codepoints, size_t max_codepoints)
Convert UTF-8 string to array of Unicode codepoints.
Definition utf8.c:185
int utf8_display_width(const char *str)
Calculate terminal display width of a UTF-8 string.
Definition utf8.c:46

References ERROR_INVALID_PARAM, log_debug, SET_ERRNO, utf8_display_width(), and utf8_to_codepoints().

Referenced by apply_palette_config(), and initialize_client_palette().

Variable Documentation

◆ byte_len

uint8_t utf8_char_info_t::byte_len

Number of bytes in UTF-8 encoding (1-4)

Definition at line 217 of file palette.h.

Referenced by utf8_palette_contains_char(), utf8_palette_create(), utf8_palette_find_all_char_indices(), and utf8_palette_find_char_index().

◆ bytes

char utf8_char_info_t::bytes[4]

UTF-8 byte sequence (max 4 bytes per character)

Definition at line 215 of file palette.h.

Referenced by build_ramp64(), utf8_palette_contains_char(), utf8_palette_create(), utf8_palette_find_all_char_indices(), and utf8_palette_find_char_index().

◆ char_count

size_t utf8_palette_t::char_count

◆ chars [1/2]

const char* palette_def_t::chars

Character sequence (ordered from dark to light)

Definition at line 120 of file palette.h.

Referenced by initialize_client_palette().

◆ chars [2/2]

◆ DEFAULT_ASCII_PALETTE

const char DEFAULT_ASCII_PALETTE[]
extern

#include <palette.h>

Default ASCII palette for legacy functions.

Standard ASCII palette used as fallback for legacy code paths.

Definition at line 25 of file palette.c.

◆ DEFAULT_ASCII_PALETTE_LEN

const size_t DEFAULT_ASCII_PALETTE_LEN
extern

#include <palette.h>

Length of default ASCII palette.

Number of characters in DEFAULT_ASCII_PALETTE.

Definition at line 26 of file palette.c.

◆ display_width

uint8_t utf8_char_info_t::display_width

Terminal display width in character cells (1-2)

Definition at line 219 of file palette.h.

Referenced by utf8_palette_create().

◆ forced_utf8

bool utf8_capabilities_t::forced_utf8

True if user forced UTF-8 via –utf8 flag.

Definition at line 141 of file palette.h.

◆ is_validated

bool palette_def_t::is_validated

True if palette passed validation checks.

Definition at line 126 of file palette.h.

◆ length

size_t palette_def_t::length

Number of characters in sequence.

Definition at line 122 of file palette.h.

Referenced by initialize_client_palette().

◆ locale_encoding

char utf8_capabilities_t::locale_encoding[16]

Current locale encoding (e.g., "UTF-8")

Definition at line 145 of file palette.h.

Referenced by detect_client_utf8_support().

◆ name

const char* palette_def_t::name

Human-readable palette name.

Definition at line 118 of file palette.h.

Referenced by initialize_client_palette(), and select_compatible_palette().

◆ raw_string

char* utf8_palette_t::raw_string

Original palette string (for reference/debugging)

Definition at line 244 of file palette.h.

Referenced by utf8_palette_create(), and utf8_palette_destroy().

◆ requires_utf8

bool palette_def_t::requires_utf8

True if palette contains UTF-8 multi-byte characters.

Definition at line 124 of file palette.h.

Referenced by select_compatible_palette().

◆ terminal_type

char utf8_capabilities_t::terminal_type[32]

$TERM environment variable value (for detection)

Definition at line 143 of file palette.h.

Referenced by detect_client_utf8_support().

◆ total_bytes

size_t utf8_palette_t::total_bytes

Total byte length of palette string.

Definition at line 242 of file palette.h.

Referenced by utf8_palette_create().

◆ utf8_support

bool utf8_capabilities_t::utf8_support

True if terminal supports UTF-8 (detected automatically)

Definition at line 139 of file palette.h.

Referenced by detect_client_utf8_support().