ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
rle.c
Go to the documentation of this file.
1
7#include "rle.h"
8#include "output_buffer.h"
9#include "common.h"
10
11#include <string.h>
12
13char *ansi_expand_rle(const char *input, size_t input_len) {
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}
97
98char *ansi_compress_rle(const char *input, size_t input_len) {
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
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:388
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.
char * ansi_compress_rle(const char *input, size_t input_len)
Compress repeated characters using RLE escape sequences.
Definition rle.c:98
void ob_write(outbuf_t *ob, const char *s, size_t n)
Append a string to buffer.
char * ansi_expand_rle(const char *input, size_t input_len)
Expand RLE escape sequences in a string.
Definition rle.c:13
Dynamic Output Buffer with ANSI Sequence Support.
ANSI RLE (REP) sequence compression and expansion.
Dynamic output buffer (auto-expanding)
char * buf
Buffer pointer (allocated, owned by caller, must be freed)