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/anim/test_pattern.c
Go to the documentation of this file.
1
2/* Existing buffer budgets in bytes, including NUL for text storage. */
3#define TEST_PATTERN_GRADIENT_INTERVALS 4096
4
6#include "backends.h"
8#include <ascii-chat/common.h>
9#include <math.h>
10#include <string.h>
11
21
22static uint8_t channel(double value) {
23 return (uint8_t)(fmax(0, fmin(255, value)) + 0.5);
24}
25static rgb_pixel_t hue(double degrees, double lightness) {
26 double h = fmod(degrees, 360) / 60;
27 double c = 1 - fabs(2 * lightness - 1), x = c * (1 - fabs(fmod(h, 2) - 1)), m = lightness - c / 2;
28 double r = 0, g = 0, b = 0;
29 if (h < 1) {
30 r = c;
31 g = x;
32 } else if (h < 2) {
33 r = x;
34 g = c;
35 } else if (h < 3) {
36 g = c;
37 b = x;
38 } else if (h < 4) {
39 g = x;
40 b = c;
41 } else if (h < 5) {
42 r = x;
43 b = c;
44 } else {
45 r = c;
46 b = x;
47 }
48 return (rgb_pixel_t){channel((r + m) * 255), channel((g + m) * 255), channel((b + m) * 255)};
49}
50static void blend(rgb_pixel_t *p, rgb_pixel_t color, double alpha) {
51 if (alpha <= 0)
52 return;
53 if (alpha >= 1) {
54 *p = color;
55 return;
56 }
57 p->r = channel(p->r * (1 - alpha) + color.r * alpha);
58 p->g = channel(p->g * (1 - alpha) + color.g * alpha);
59 p->b = channel(p->b * (1 - alpha) + color.b * alpha);
60}
61static double overlap(double pixel, double start, double end) {
62 return fmax(0, fmin(pixel + 1, end) - fmax(pixel, start));
63}
64
66 if (!p || width < 1 || height < 1)
67 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid test pattern dimensions");
68 if (p->image && p->image->w == width && p->image->h == height)
69 return ASCIICHAT_OK;
70 image_t *next = image_new(width, height);
71 if (!next)
72 return SET_ERRNO(ERROR_MEMORY, "Test pattern image allocation failed");
73 if (p->image)
75 p->image = next;
76 SAFE_FREE(p->rgba);
77 return ASCIICHAT_OK;
78}
79
80// Integer-sized black strokes leave readable negative space after ASCII sampling.
81static void render_test_label(image_t *image) {
82 static const uint8_t glyphs[4][7] = {
83 {4, 4, 31, 4, 4, 5, 2}, // t
84 {0, 0, 14, 17, 31, 16, 15}, // e
85 {0, 0, 15, 16, 14, 1, 30}, // s
86 {4, 4, 31, 4, 4, 5, 2}, // t
87 };
88 int scale = image->w / 31;
89 if (scale > image->h / 22)
90 scale = image->h / 22;
91 // Below this size a four-letter raster label cannot be read reliably.
92 if (scale < 1)
93 return;
94 int left = (image->w - 23 * scale) / 2, top = (image->h - 7 * scale) / 2;
95 for (int letter = 0; letter < 4; ++letter)
96 for (int row = 0; row < 7; ++row)
97 for (int col = 0; col < 5; ++col) {
98 if (!(glyphs[letter][row] & (1u << (4 - col))))
99 continue;
100 for (int dy = 0; dy < scale; ++dy)
101 for (int dx = 0; dx < scale; ++dx) {
102 int x = left + (letter * 6 + col) * scale + dx;
103 int y = top + row * scale + dy;
104 image->pixels[(size_t)y * image->w + x] = (rgb_pixel_t){0, 0, 0};
105 }
106 }
107}
108
110 if (!out)
111 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing test pattern output");
112 *out = NULL;
113 test_pattern_t *p = SAFE_CALLOC(1, sizeof(*p), test_pattern_t *);
115 if (result != ASCIICHAT_OK) {
117 return result;
118 }
119 *out = p;
120 return ASCIICHAT_OK;
121}
123 if (!p)
124 return;
125 if (p->image)
127 SAFE_FREE(p->rgba);
128 SAFE_FREE(p);
129}
131 return p ? p->image : NULL;
132}
133
135 if (!p || !p->image)
136 return NULL;
137 size_t count = (size_t)p->image->w * p->image->h;
138 if (!p->rgba)
139 p->rgba = SAFE_MALLOC(count * 4, uint8_t *);
140 for (size_t i = 0; i < count; i++) {
141 p->rgba[i * 4] = p->image->pixels[i].r;
142 p->rgba[i * 4 + 1] = p->image->pixels[i].g;
143 p->rgba[i * 4 + 2] = p->image->pixels[i].b;
144 p->rgba[i * 4 + 3] = 255;
145 }
146 return p->rgba;
147}
148
149asciichat_error_t test_pattern_render(test_pattern_t *p, int index, double time_ms, bool cadence) {
150 animation_sample_t sample;
151 animation_config_t config = {.type = ANIMATION_TEST_PATTERN, .fps = 60, .speed = 1};
152 asciichat_error_t err = animation_sample_at(config, time_ms / 1000.0, &sample);
153 if (err != ASCIICHAT_OK)
154 return err;
156 .test_pattern = {.context = p, .index = index, .cadence = cadence}};
157 return animation_apply(&sample, &target);
158}
159
160asciichat_error_t test_pattern_render_at(test_pattern_t *p, int index, double time_ms, bool cadence) {
161 if (!p || !p->image || index < 0 || index > 1 || !isfinite(time_ms) || time_ms < 0)
162 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid test pattern frame parameters");
163 int w = p->image->w, h = p->image->h;
164 double phase = fmod(time_ms, index ? 4000 : 10000) / (index ? 4000 : 10000);
165 rgb_pixel_t dark = {16, 24, 32}, white = {255, 255, 255};
166 rgb_pixel_t first = hue(phase * 360, 0.5), last = hue(fmod(phase + 0.5, 1) * 360, 0.5);
167 double box = fmin(fmax(32, fmin(w, h) / 4.0), fmin(w, h) / 2.0);
168 double bx = fmod(time_ms / 11, w + box) - box, by = fmod(time_ms / 17, h + box) - box;
169 double stripe = w / 12.0, offset = phase * w;
170 int first_stripe = (int)ceil(-offset / stripe);
171 double radius = fmin(fmax(12, fmin(w, h) / 8.0), fmin(w, h) / 4.0);
172 double cx = w + radius - phase * (w + radius * 2), cy = h / 2.0 + sin(phase * 12.566370614359172) * h / 4.0;
174 for (int i = 0; i < 12; i++)
175 colors[i] = hue(i * 30, 0.55);
176 uint32_t frame = (uint32_t)fmod(floor(time_ms / (1000.0 / 60)), 512);
177 uint32_t code = frame ^ (frame >> 1);
178 const int slots[8] = {6, 4, 2, 0, 1, 3, 5, 7};
179 rgb_pixel_t *pixels = p->image->pixels;
180 if (index == 1) {
181 // The background is constant down each column. Rasterize one row, then copy.
182 for (int x = 0; x < w; x++) {
183 rgb_pixel_t pixel = dark;
184 if (cadence) {
185 int slot = (int)(x / fmax(2, w / 8.0));
186 if (slot < 8)
187 pixel = (code & (1u << slots[slot])) ? white : (rgb_pixel_t){0, 0, 0};
188 } else {
189 int k = (int)floor((x - offset) / stripe) + 1;
190 for (int j = k; j <= k + 1; j++)
191 if (j >= first_stripe && j <= first_stripe + 12)
192 blend(&pixel, colors[(j % 12 + 12) % 12], overlap(x, j * stripe + offset - stripe, j * stripe + offset));
193 }
194 pixels[x] = pixel;
195 }
196 for (int y = 1; y < h; y++)
197 memcpy(pixels + (size_t)y * w, pixels, (size_t)w * sizeof(*pixels));
198 if (cadence)
199 return ASCIICHAT_OK;
200 int top = (int)fmax(0, floor(cy - radius - 1)), bottom = (int)fmin(h, ceil(cy + radius + 1));
201 int left = (int)fmax(0, floor(cx - radius - 1)), right = (int)fmin(w, ceil(cx + radius + 1));
202 for (int y = top; y < bottom; y++)
203 for (int x = left; x < right; x++) {
204 double dx0 = x + 0.5 - cx, dy0 = y + 0.5 - cy, d2 = dx0 * dx0 + dy0 * dy0;
205 if (d2 > (radius + 0.75) * (radius + 0.75))
206 continue;
207 double coverage = 1;
208 if (d2 > (radius - 0.75) * (radius - 0.75)) {
209 int inside = 0;
210 for (int sy = 0; sy < 4; sy++)
211 for (int sx = 0; sx < 4; sx++) {
212 double dx = x + (sx + 0.5) / 4 - cx, dy = y + (sy + 0.5) / 4 - cy;
213 if (dx * dx + dy * dy < radius * radius)
214 inside++;
215 }
216 coverage = inside / 16.0;
217 }
218 blend(&pixels[(size_t)y * w + x], white, coverage);
219 }
220 } else {
221 // A fixed-point ramp avoids three floating-point conversions per source
222 // pixel. 4096 intervals keep rounding within one RGB level of Canvas.
224 for (int i = 0; i <= TEST_PATTERN_GRADIENT_INTERVALS; i++) {
225 double t = i / (double)TEST_PATTERN_GRADIENT_INTERVALS;
226 gradient[i] = (rgb_pixel_t){(uint8_t)(first.r * (1 - t) + last.r * t + 0.5),
227 (uint8_t)(first.g * (1 - t) + last.g * t + 0.5),
228 (uint8_t)(first.b * (1 - t) + last.b * t + 0.5)};
229 }
230 double scale = (double)TEST_PATTERN_GRADIENT_INTERVALS * 65536 / ((double)w * w + (double)h * h);
231 uint32_t step = (uint32_t)(w * scale + 0.5);
232 for (int y = 0; y < h; y++) {
233 uint32_t position = (uint32_t)((0.5 * w + (y + 0.5) * h) * scale + 0.5);
234 for (int x = 0; x < w; x++, position += step)
235 pixels[(size_t)y * w + x] = gradient[position >> 16];
236 }
237 int top = (int)fmax(0, floor(by)), bottom = (int)fmin(h, ceil(by + box));
238 int left = (int)fmax(0, floor(bx)), right = (int)fmin(w, ceil(bx + box));
239 for (int y = top; y < bottom; y++)
240 for (int x = left; x < right; x++)
241 blend(&pixels[(size_t)y * w + x], white, 0.8 * overlap(x, bx, bx + box) * overlap(y, by, by + box));
242 }
243 render_test_label(p->image);
244 return ASCIICHAT_OK;
245}
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
unsigned char colors[(512 *256 *2) *3]
Definition fft.c:22
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
unsigned int uint32_t
Definition common.h:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
unsigned char uint8_t
Definition common.h:60
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
void image_destroy(image_t *image)
Destroy an image allocated with image_new().
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
🔢 Mathematical Utility Functions
static bool size_t index
Definition queries.h:8
static bool size_t gotel_query_t * out
Definition queries.h:8
🔤 String Manipulation and Shell Escaping Utilities
For animation config, this is the live record that ties type, fps, speed, hidden_policy together....
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).
uint8_t b
Blue color component (0-255).
uint8_t g
Green color component (0-255).
uint8_t r
Red color component (0-255).
test pattern is intentionally a small, concrete record. Fields such as image, rgba give callers the i...
asciichat_error_t animation_apply(const animation_sample_t *s, animation_target_t *t)
asciichat_error_t animation_sample_at(animation_config_t config, double seconds, animation_sample_t *out)
@ ANIMATION_TEST_PATTERN
@ ANIMATION_TARGET_TEST_PATTERN
image_t * test_pattern_image(test_pattern_t *p)
asciichat_error_t test_pattern_render(test_pattern_t *p, int index, double time_ms, bool cadence)
asciichat_error_t test_pattern_create(int width, int height, test_pattern_t **out)
const uint8_t * test_pattern_rgba(test_pattern_t *p)
void test_pattern_destroy(test_pattern_t *p)
asciichat_error_t test_pattern_render_at(test_pattern_t *p, int index, double time_ms, bool cadence)
asciichat_error_t test_pattern_resize(test_pattern_t *p, int width, int height)
#define TEST_PATTERN_GRADIENT_INTERVALS
struct test_pattern test_pattern_t