ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
video/ascii/neon/common.c
Go to the documentation of this file.
1
4#if SIMD_SUPPORT_NEON
5#include <arm_neon.h>
8
9static inline uint8x16_t simd_luma_neon(uint8x16_t r, uint8x16_t g, uint8x16_t b) {
10 uint16x8_t rl = vmovl_u8(vget_low_u8(r));
11 uint16x8_t rh = vmovl_u8(vget_high_u8(r));
12 uint16x8_t gl = vmovl_u8(vget_low_u8(g));
13 uint16x8_t gh = vmovl_u8(vget_high_u8(g));
14 uint16x8_t bl = vmovl_u8(vget_low_u8(b));
15 uint16x8_t bh = vmovl_u8(vget_high_u8(b));
16
17 uint32x4_t l0 = vmull_n_u16(vget_low_u16(rl), LUMA_RED);
18 uint32x4_t l1 = vmull_n_u16(vget_high_u16(rl), LUMA_RED);
19 l0 = vmlal_n_u16(l0, vget_low_u16(gl), LUMA_GREEN);
20 l1 = vmlal_n_u16(l1, vget_high_u16(gl), LUMA_GREEN);
21 l0 = vmlal_n_u16(l0, vget_low_u16(bl), LUMA_BLUE);
22 l1 = vmlal_n_u16(l1, vget_high_u16(bl), LUMA_BLUE);
23
24 uint32x4_t h0 = vmull_n_u16(vget_low_u16(rh), LUMA_RED);
25 uint32x4_t h1 = vmull_n_u16(vget_high_u16(rh), LUMA_RED);
26 h0 = vmlal_n_u16(h0, vget_low_u16(gh), LUMA_GREEN);
27 h1 = vmlal_n_u16(h1, vget_high_u16(gh), LUMA_GREEN);
28 h0 = vmlal_n_u16(h0, vget_low_u16(bh), LUMA_BLUE);
29 h1 = vmlal_n_u16(h1, vget_high_u16(bh), LUMA_BLUE);
30
31 uint16x8_t l = vcombine_u16(vrshrn_n_u32(l0, 8), vrshrn_n_u32(l1, 8));
32 uint16x8_t h = vcombine_u16(vrshrn_n_u32(h0, 8), vrshrn_n_u32(h1, 8));
33 return vcombine_u8(vqmovn_u16(l), vqmovn_u16(h));
34}
35
36// ===== SIMD helpers for 256-color quantization =====
37
38// Approximate quantize 0..255 -> 0..5 : q ≈ round(x*5/255) = (x*5 + 128)>>8
39static inline uint8x16_t q6_from_u8(uint8x16_t x) {
40 uint16x8_t xl = vmovl_u8(vget_low_u8(x));
41 uint16x8_t xh = vmovl_u8(vget_high_u8(x));
42 xl = vmlaq_n_u16(vdupq_n_u16(0), xl, 5);
43 xh = vmlaq_n_u16(vdupq_n_u16(0), xh, 5);
44 xl = vaddq_u16(xl, vdupq_n_u16(128));
45 xh = vaddq_u16(xh, vdupq_n_u16(128));
46 xl = vshrq_n_u16(xl, 8);
47 xh = vshrq_n_u16(xh, 8);
48 return vcombine_u8(vqmovn_u16(xl), vqmovn_u16(xh)); // 0..5
49}
50
51// Make 256-color index (cube vs gray). threshold: max-min < thr ⇒ gray
52#ifndef CUBE_GRAY_THRESHOLD
53#define CUBE_GRAY_THRESHOLD 10
54#endif
55
56// Apply ordered dithering to reduce color variations (creates longer runs)
57static inline uint8x16_t apply_ordered_dither(uint8x16_t color, int pixel_offset, uint8_t dither_strength) {
58 // Bayer 4x4 dithering matrix (classic ordered dithering pattern)
59 static const uint8_t bayer4x4[16] = {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
60
61 // Load dithering matrix into NEON register
62 const uint8x16_t dither_matrix = vld1q_u8(bayer4x4);
63
64 // Create pixel position indices for 16 consecutive pixels
65 uint8_t pos_indices[16];
66 for (int i = 0; i < 16; i++) {
67 pos_indices[i] = (pixel_offset + i) & 15; // Wrap to 4x4 matrix (0-15)
68 }
69 const uint8x16_t position_vec = vld1q_u8(pos_indices);
70
71 // Lookup dither values for each pixel position using table lookup
72#if defined(__aarch64__)
73 uint8x16_t dither_values = vqtbl1q_u8(dither_matrix, position_vec);
74#else
75 uint8x8x2_t table = {{vget_low_u8(dither_matrix), vget_high_u8(dither_matrix)}};
76 uint8x16_t dither_values =
77 vcombine_u8(vtbl2_u8(table, vget_low_u8(position_vec)), vtbl2_u8(table, vget_high_u8(position_vec)));
78#endif
79
80 // Scale dither values by strength (0-255 range)
81 // dither_strength controls how much dithering to apply
82 uint16x8_t dither_lo = vmulq_n_u16(vmovl_u8(vget_low_u8(dither_values)), dither_strength);
83 uint16x8_t dither_hi = vmulq_n_u16(vmovl_u8(vget_high_u8(dither_values)), dither_strength);
84 dither_lo = vshrq_n_u16(dither_lo, 4); // Scale down (/16)
85 dither_hi = vshrq_n_u16(dither_hi, 4);
86 uint8x16_t scaled_dither = vcombine_u8(vqmovn_u16(dither_lo), vqmovn_u16(dither_hi));
87
88 // Apply dithering with saturation to prevent overflow
89 return vqaddq_u8(color, scaled_dither);
90}
91
92uint8x16_t palette256_index_dithered_neon(uint8x16_t r, uint8x16_t g, uint8x16_t b, int pixel_offset) {
93 // Dithering disabled in speed mode (no-op)
94 r = apply_ordered_dither(r, pixel_offset, 0);
95 g = apply_ordered_dither(g, pixel_offset + 1, 0);
96 b = apply_ordered_dither(b, pixel_offset + 2, 0);
97
98 // cube index
99 uint8x16_t R6 = q6_from_u8(r);
100 uint8x16_t G6 = q6_from_u8(g);
101 uint8x16_t B6 = q6_from_u8(b);
102
103 // idx_cube = 16 + R6*36 + G6*6 + B6 (do in 16-bit to avoid overflow)
104 uint16x8_t R6l = vmovl_u8(vget_low_u8(R6));
105 uint16x8_t R6h = vmovl_u8(vget_high_u8(R6));
106 uint16x8_t G6l = vmovl_u8(vget_low_u8(G6));
107 uint16x8_t G6h = vmovl_u8(vget_high_u8(G6));
108 uint16x8_t B6l = vmovl_u8(vget_low_u8(B6));
109 uint16x8_t B6h = vmovl_u8(vget_high_u8(B6));
110
111 uint16x8_t idxl = vmlaq_n_u16(vmulq_n_u16(R6l, 36), G6l, 6);
112 uint16x8_t idxh = vmlaq_n_u16(vmulq_n_u16(R6h, 36), G6h, 6);
113 idxl = vaddq_u16(idxl, B6l);
114 idxh = vaddq_u16(idxh, B6h);
115 idxl = vaddq_u16(idxl, vdupq_n_u16(16));
116 idxh = vaddq_u16(idxh, vdupq_n_u16(16));
117
118 // gray decision: max-min < thr ?
119 uint8x16_t maxrg = vmaxq_u8(r, g);
120 uint8x16_t minrg = vminq_u8(r, g);
121 uint8x16_t maxrgb = vmaxq_u8(maxrg, b);
122 uint8x16_t minrgb = vminq_u8(minrg, b);
123 uint8x16_t diff = vsubq_u8(maxrgb, minrgb);
124 uint8x16_t thr = vdupq_n_u8((uint8_t)CUBE_GRAY_THRESHOLD);
125 uint8x16_t is_gray = vcltq_u8(diff, thr);
126
127 // gray idx = 232 + round(Y*23/255)
128 uint8x16_t Y = simd_luma_neon(r, g, b);
129 // q23 ≈ round(Y*23/255) = (Y*23 + 128)>>8
130 uint16x8_t Yl = vmovl_u8(vget_low_u8(Y));
131 uint16x8_t Yh = vmovl_u8(vget_high_u8(Y));
132 Yl = vmlaq_n_u16(vdupq_n_u16(0), Yl, 23);
133 Yh = vmlaq_n_u16(vdupq_n_u16(0), Yh, 23);
134 Yl = vaddq_u16(Yl, vdupq_n_u16(128));
135 Yh = vaddq_u16(Yh, vdupq_n_u16(128));
136 Yl = vshrq_n_u16(Yl, 8);
137 Yh = vshrq_n_u16(Yh, 8);
138 uint16x8_t gidxl = vaddq_u16(Yl, vdupq_n_u16(232));
139 uint16x8_t gidxh = vaddq_u16(Yh, vdupq_n_u16(232));
140
141 // select gray or cube per lane
142 uint8x16_t idx_cube = vcombine_u8(vqmovn_u16(idxl), vqmovn_u16(idxh));
143 uint8x16_t idx_gray = vcombine_u8(vqmovn_u16(gidxl), vqmovn_u16(gidxh));
144 return vbslq_u8(is_gray, idx_gray, idx_cube);
145}
146
153void image_flip_horizontal_neon(image_t *image) {
154 if (!image || !image->pixels || image->w < 2) {
155 return;
156 }
157
158 // Process each row - swap pixels from both ends using NEON for faster loads/stores
159 for (int y = 0; y < image->h; y++) {
160 rgb_pixel_t *row = &image->pixels[y * image->w];
161 int width = image->w;
162
163 // NEON-accelerated swapping: process 4 pixels at a time using uint32 loads
164 // Each RGB pixel is 3 bytes, so 4 pixels = 12 bytes that can be loaded as 3x u32
165 int left_pix = 0;
166 int right_pix = width - 1;
167
168 // Fast path: swap 4-pixel groups using uint32 operations
169 while (left_pix + 3 < right_pix - 3) {
170 // Load left 4 pixels (12 bytes) as 3 uint32 values using NEON
171 uint32_t *left_ptr = (uint32_t *)&row[left_pix];
172 uint32_t *right_ptr = (uint32_t *)&row[right_pix - 3];
173
174 uint32x2_t left_0 = vld1_u32(left_ptr); // first 8 bytes
175 uint32_t left_1 = left_ptr[2]; // last 4 bytes
176
177 uint32x2_t right_0 = vld1_u32(right_ptr); // first 8 bytes
178 uint32_t right_1 = right_ptr[2]; // last 4 bytes
179
180 // Store swapped using NEON
181 vst1_u32(right_ptr, left_0);
182 right_ptr[2] = left_1;
183 vst1_u32(left_ptr, right_0);
184 left_ptr[2] = right_1;
185
186 left_pix += 4;
187 right_pix -= 4;
188 }
189
190 // Scalar cleanup for remaining pixels
191 while (left_pix < right_pix) {
192 rgb_pixel_t temp = row[left_pix];
193 row[left_pix] = row[right_pix];
194 row[right_pix] = temp;
195 left_pix++;
196 right_pix--;
197 }
198 }
199}
200
201#endif // SIMD_SUPPORT_NEON
unsigned int width
Definition fft.c:23
unsigned int uint32_t
Definition common.h:62
unsigned char uint8_t
Definition common.h:60
#define LUMA_BLUE
Luminance blue coefficient (0.114 * 256 = 29).
#define LUMA_GREEN
Luminance green coefficient (0.587 * 256 = 150).
#define LUMA_RED
Luminance red coefficient (0.299 * 256 = 77).
NEON-optimized ASCII rendering functions.
SIMD-friendly storage for one RGB pixel. r, g, and b hold the color channels and padding keeps the la...
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).
SIMD-optimized ASCII conversion interface.