ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
neon/halfblock.c
Go to the documentation of this file.
1
10#if SIMD_SUPPORT_NEON
11#include <stdio.h>
12#include <stdlib.h>
13#include <stdint.h>
14#include <string.h>
15#include <stdarg.h>
16#include <time.h>
17#include <assert.h>
18#include <ascii-chat/atomic.h>
19#include <math.h>
20
21#include <arm_neon.h>
22
23#include <ascii-chat/common.h>
33#include <ascii-chat/log/log.h>
34
35//=============================================================================
36// Optimized NEON Half-block renderer (based on ChatGPT reference)
37//=============================================================================
38char *rgb_to_truecolor_halfblocks_neon(const uint8_t *rgb, int width, int height, int stride_bytes) {
39 /* Main: half-block renderer. Returns NUL-terminated malloc'd string; caller free(). */
40 if (width <= 0 || height <= 0)
41 return platform_strdup("");
42 if (stride_bytes <= 0)
43 stride_bytes = width * 3;
44
45 outbuf_t ob = {0};
46 // generous guess: per cell ~ 10–14 bytes avg; half the rows + newlines
47 size_t est_cells = (size_t)width * ((size_t)(height + 1) / 2);
48 ob.cap = est_cells * 14u + (size_t)((height + 1) / 2) * 8u + 64u;
49 ob.buf = SAFE_MALLOC(ob.cap ? ob.cap : 1, char *);
50 if (!ob.buf)
51 return NULL;
52
53 // current SGR state; -1 means unknown
54 int cur_fr = -1, cur_fg = -1, cur_fb = -1;
55 int cur_br = -1, cur_bg = -1, cur_bb = -1;
56
57 // process two source rows per emitted line
58 for (int y = 0; y < height; y += 2) {
59 const uint8_t *rowT = rgb + (size_t)y * (size_t)stride_bytes;
60 const uint8_t *rowB = (y + 1 < height) ? rowT + (size_t)stride_bytes : NULL;
61
62 int x = 0;
63 while (x + 16 <= width) {
64 // Load 16 top and bottom pixels (RGB interleaved)
65 const uint8_t *pT = rowT + (size_t)x * 3u;
66 uint8x16x3_t top = vld3q_u8(pT);
67
68 uint8x16x3_t bot;
69 if (rowB) {
70 const uint8_t *pB = rowB + (size_t)x * 3u;
71 bot = vld3q_u8(pB);
72 } else {
73 // synthesize bottom = top for odd-height last row
74 bot.val[0] = top.val[0];
75 bot.val[1] = top.val[1];
76 bot.val[2] = top.val[2];
77 }
78
79 // Spill to small arrays (cheap; enables simple scalar RLE over 16)
80 uint8_t Rt[16], Gt[16], Bt[16], Rb[16], Gb[16], Bb[16];
81 vst1q_u8(Rt, top.val[0]);
82 vst1q_u8(Gt, top.val[1]);
83 vst1q_u8(Bt, top.val[2]);
84 vst1q_u8(Rb, bot.val[0]);
85 vst1q_u8(Gb, bot.val[1]);
86 vst1q_u8(Bb, bot.val[2]);
87
88 // RLE over the 16 cells
89 for (int i = 0; i < 16;) {
90 uint8_t rT = Rt[i], gT = Gt[i], bT = Bt[i];
91 uint8_t rB = Rb[i], gB = Gb[i], bB = Bb[i];
92
93 // Always half-block: U+2580 "▀" (upper half)
94 const uint8_t glyph_utf8[3] = {0xE2, 0x96, 0x80};
95
96 // Extend run while next cell has same top+bottom colors
97 int j = i + 1;
98 for (; j < 16; ++j) {
99 if (!(Rt[j] == rT && Gt[j] == gT && Bt[j] == bT && Rb[j] == rB && Gb[j] == gB && Bb[j] == bB))
100 break;
101 }
102 uint32_t run = (uint32_t)(j - i);
103
104 // Check if this is a transparent area (black pixels = padding/background)
105 bool is_transparent = (rT == 0 && gT == 0 && bT == 0 && rB == 0 && gB == 0 && bB == 0);
106
107 if (is_transparent) {
108 // Reset colors before transparent areas to prevent color bleeding
109 if (cur_fr != -1 || cur_fg != -1 || cur_fb != -1 || cur_br != -1 || cur_bg != -1 || cur_bb != -1) {
110 emit_reset(&ob);
111 cur_fr = cur_fg = cur_fb = -1;
112 cur_br = cur_bg = cur_bb = -1;
113 }
114 // Emit spaces for transparent area (no RLE for padding)
115 for (uint32_t k = 0; k < run; ++k) {
116 ob_write(&ob, " ", 1);
117 }
118 } else {
119 // Normal colored half-blocks - set fg to TOP, bg to BOTTOM if changed
120 if (cur_fr != rT || cur_fg != gT || cur_fb != bT) {
121 emit_set_fg(&ob, rT, gT, bT);
122 cur_fr = rT;
123 cur_fg = gT;
124 cur_fb = bT;
125 }
126 if (cur_br != rB || cur_bg != gB || cur_bb != bB) {
127 emit_set_bg(&ob, rB, gB, bB);
128 cur_br = rB;
129 cur_bg = gB;
130 cur_bb = bB;
131 }
132
133 // Emit glyph once, then REP or literals
134 ob_write(&ob, (const char *)glyph_utf8, 3);
135 if (rep_is_profitable(run)) {
136 emit_rep(&ob, run - 1);
137 } else {
138 for (uint32_t k = 1; k < run; ++k) {
139 ob_write(&ob, (const char *)glyph_utf8, 3);
140 }
141 }
142 }
143
144 i = j;
145 }
146 x += 16;
147 }
148
149 // Scalar tail (or full row if no NEON)
150 for (; x < width;) {
151 const uint8_t *pT = rowT + (size_t)x * 3u;
152 const uint8_t *pB = rowB ? rowB + (size_t)x * 3u : NULL;
153
154 uint8_t rT = pT[0], gT = pT[1], bT = pT[2];
155 uint8_t rB = rT, gB = gT, bB = bT;
156 if (pB) {
157 rB = pB[0];
158 gB = pB[1];
159 bB = pB[2];
160 }
161
162 // Extend run while top and bottom colors match exactly
163 int j = x + 1;
164 for (; j < width; ++j) {
165 const uint8_t *qT = rowT + (size_t)j * 3u;
166 const uint8_t *qB = rowB ? rowB + (size_t)j * 3u : NULL;
167 uint8_t rT2 = qT[0], gT2 = qT[1], bT2 = qT[2];
168 uint8_t rB2 = qB ? qB[0] : rT2, gB2 = qB ? qB[1] : gT2, bB2 = qB ? qB[2] : bT2;
169 if (!((rT2 == rT && gT2 == gT && bT2 == bT) && (rB2 == rB && gB2 == gB && bB2 == bB)))
170 break;
171 }
172 uint32_t run = (uint32_t)(j - x);
173
174 // Check if this is a transparent area (black pixels = padding/background)
175 bool is_transparent = (rT == 0 && gT == 0 && bT == 0 && rB == 0 && gB == 0 && bB == 0);
176
177 if (is_transparent) {
178 // Reset colors before transparent areas to prevent color bleeding
179 if (cur_fr != -1 || cur_fg != -1 || cur_fb != -1 || cur_br != -1 || cur_bg != -1 || cur_bb != -1) {
180 emit_reset(&ob);
181 cur_fr = cur_fg = cur_fb = -1;
182 cur_br = cur_bg = cur_bb = -1;
183 }
184 // Emit spaces for transparent area (no RLE for padding)
185 for (uint32_t k = 0; k < run; ++k) {
186 ob_write(&ob, " ", 1);
187 }
188 } else {
189 // SGR: fg = TOP, bg = BOTTOM for colored areas
190 if (cur_fr != rT || cur_fg != gT || cur_fb != bT) {
191 emit_set_fg(&ob, rT, gT, bT);
192 cur_fr = rT;
193 cur_fg = gT;
194 cur_fb = bT;
195 }
196 if (cur_br != rB || cur_bg != gB || cur_bb != bB) {
197 emit_set_bg(&ob, rB, gB, bB);
198 cur_br = rB;
199 cur_bg = gB;
200 cur_bb = bB;
201 }
202
203 // Always the upper half block "▀" (U+2580)
204 static const char HB[3] = {(char)0xE2, (char)0x96, (char)0x80};
205 ob_write(&ob, HB, 3);
206 if (rep_is_profitable(run)) {
207 emit_rep(&ob, run - 1);
208 } else {
209 for (uint32_t k = 1; k < run; ++k) {
210 ob_write(&ob, HB, 3);
211 }
212 }
213 }
214
215 x = j;
216 }
217
218 // End emitted line: reset and newline (only for non-final lines)
219 emit_reset(&ob);
220 // Check if this is the last output line (since we process 2 pixel rows per output line)
221 if (y + 2 < height) { // Only add newline if not the last output line
222 ob_putc(&ob, '\n');
223 }
224 cur_fr = cur_fg = cur_fb = -1;
225 cur_br = cur_bg = cur_bb = -1;
226 }
227
228 ob_term(&ob);
229 return ob.buf;
230}
231
232#endif
ANSI escape sequence utilities and fast color code generation.
⚛️ Atomic operations abstraction layer with debug tracking
⚙️ Common definitions, error codes, macros, and types shared throughout the application
unsigned int uint32_t
Definition common.h:58
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned char uint8_t
Definition common.h:56
char * platform_strdup(const char *s)
Duplicate string (strdup replacement)
void emit_set_bg(outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
Emit background color sequence (auto-select mode)
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 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.
void emit_reset(outbuf_t *ob)
Emit ANSI reset sequence.
void emit_set_fg(outbuf_t *ob, uint8_t r, uint8_t g, uint8_t b)
Emit foreground color sequence (auto-select mode)
Platform initialization and static synchronization helpers.
Lock-free module lifecycle state machine using pure stdatomic.
📝 Logging API with multiple log levels and terminal output control
🔢 Mathematical Utility Functions
NEON-optimized ASCII rendering functions.
✅ Safe Integer Arithmetic and Overflow Detection
Dynamic output buffer (auto-expanding)
size_t cap
Buffer capacity in bytes (maximum length before reallocation)
char * buf
Buffer pointer (allocated, owned by caller, must be freed)
⏱️ High-precision timing utilities using sokol_time.h and uthash
SIMD-optimized ASCII conversion interface.