Monorepo for Aesthetic.Computer
aesthetic.computer
1#include "graph.h"
2#include "qrcodegen.h"
3#include <stdlib.h>
4#include <string.h>
5#include <math.h>
6
7#ifdef USE_SDL
8#include "drm-display.h"
9#endif
10
11static inline uint8_t clamp_u8(int v) {
12 if (v < 0) return 0;
13 if (v > 255) return 255;
14 return (uint8_t)v;
15}
16
17void graph_init(ACGraph *g, ACFramebuffer *screen) {
18 g->fb = screen;
19 g->screen = screen;
20 g->ink = (ACColor){255, 255, 255, 255};
21 g->ink_packed = 0xFFFFFFFF;
22 g->gpu_display = NULL;
23}
24
25void graph_init_gpu(ACGraph *g, void *display) {
26 g->gpu_display = display;
27}
28
29void graph_wipe(ACGraph *g, ACColor color) {
30 fb_clear(g->fb, color_pack(color));
31}
32
33void graph_ink(ACGraph *g, ACColor color) {
34 g->ink = color;
35 g->ink_packed = color_pack(color);
36}
37
38void graph_plot(ACGraph *g, int x, int y) {
39 if (g->ink.a == 255)
40 fb_put_pixel(g->fb, x, y, g->ink_packed);
41 else
42 fb_blend_pixel(g->fb, x, y, g->ink_packed);
43}
44
45// Bresenham's line algorithm
46void graph_line(ACGraph *g, int x0, int y0, int x1, int y1) {
47 int dx = abs(x1 - x0);
48 int dy = -abs(y1 - y0);
49 int sx = x0 < x1 ? 1 : -1;
50 int sy = y0 < y1 ? 1 : -1;
51 int err = dx + dy;
52
53 for (;;) {
54 graph_plot(g, x0, y0);
55 if (x0 == x1 && y0 == y1) break;
56 int e2 = 2 * err;
57 if (e2 >= dy) { err += dy; x0 += sx; }
58 if (e2 <= dx) { err += dx; y0 += sy; }
59 }
60}
61
62// Thick line: draw filled circles along the Bresenham path
63void graph_line_thick(ACGraph *g, int x0, int y0, int x1, int y1, int thickness) {
64 if (thickness <= 1) { graph_line(g, x0, y0, x1, y1); return; }
65 int r = (thickness - 1) / 2;
66 int dx = abs(x1 - x0);
67 int dy = -abs(y1 - y0);
68 int sx = x0 < x1 ? 1 : -1;
69 int sy = y0 < y1 ? 1 : -1;
70 int err = dx + dy;
71 for (;;) {
72 graph_circle(g, x0, y0, r, 1);
73 if (x0 == x1 && y0 == y1) break;
74 int e2 = 2 * err;
75 if (e2 >= dy) { err += dy; x0 += sx; }
76 if (e2 <= dx) { err += dx; y0 += sy; }
77 }
78}
79
80void graph_box(ACGraph *g, int x, int y, int w, int h, int filled) {
81 if (filled) {
82 // Clip to framebuffer bounds once
83 int x0 = x < 0 ? 0 : x;
84 int y0 = y < 0 ? 0 : y;
85 int x1 = x + w > g->fb->width ? g->fb->width : x + w;
86 int y1 = y + h > g->fb->height ? g->fb->height : y + h;
87 if (x0 >= x1 || y0 >= y1) return;
88 uint32_t color = g->ink_packed;
89 uint8_t sa = g->ink.a;
90 if (sa == 255) {
91 // Opaque fast path — memset-style
92 int span = x1 - x0;
93 for (int row = y0; row < y1; row++) {
94 uint32_t *dst = &g->fb->pixels[row * g->fb->stride + x0];
95 for (int i = 0; i < span; i++) dst[i] = color;
96 }
97 } else if (sa > 0) {
98 // Alpha blend — no per-pixel bounds check
99 uint8_t sr = (color >> 16) & 0xFF;
100 uint8_t sg_c = (color >> 8) & 0xFF;
101 uint8_t sb = color & 0xFF;
102 uint16_t inv = 255 - sa;
103 for (int row = y0; row < y1; row++) {
104 uint32_t *dst = &g->fb->pixels[row * g->fb->stride + x0];
105 for (int col = x0; col < x1; col++, dst++) {
106 uint32_t d = *dst;
107 uint8_t r = (sr * sa + ((d >> 16) & 0xFF) * inv) / 255;
108 uint8_t gc = (sg_c * sa + ((d >> 8) & 0xFF) * inv) / 255;
109 uint8_t b = (sb * sa + (d & 0xFF) * inv) / 255;
110 *dst = (255u << 24) | ((uint32_t)r << 16) | ((uint32_t)gc << 8) | b;
111 }
112 }
113 }
114 } else {
115 // Outline rectangle
116 graph_line(g, x, y, x + w - 1, y);
117 graph_line(g, x + w - 1, y, x + w - 1, y + h - 1);
118 graph_line(g, x + w - 1, y + h - 1, x, y + h - 1);
119 graph_line(g, x, y + h - 1, x, y);
120 }
121}
122
123// Midpoint circle algorithm
124void graph_circle(ACGraph *g, int cx, int cy, int r, int filled) {
125 int x = 0, y = r;
126 int d = 1 - r;
127
128 while (x <= y) {
129 if (filled) {
130 // Draw horizontal spans for filled circle
131 for (int i = cx - x; i <= cx + x; i++) {
132 graph_plot(g, i, cy + y);
133 graph_plot(g, i, cy - y);
134 }
135 for (int i = cx - y; i <= cx + y; i++) {
136 graph_plot(g, i, cy + x);
137 graph_plot(g, i, cy - x);
138 }
139 } else {
140 // Draw 8 symmetric points
141 graph_plot(g, cx + x, cy + y);
142 graph_plot(g, cx - x, cy + y);
143 graph_plot(g, cx + x, cy - y);
144 graph_plot(g, cx - x, cy - y);
145 graph_plot(g, cx + y, cy + x);
146 graph_plot(g, cx - y, cy + x);
147 graph_plot(g, cx + y, cy - x);
148 graph_plot(g, cx - y, cy - x);
149 }
150 if (d < 0) {
151 d += 2 * x + 3;
152 } else {
153 d += 2 * (x - y) + 5;
154 y--;
155 }
156 x++;
157 }
158}
159
160void graph_scroll(ACGraph *g, int dx, int dy) {
161 ACFramebuffer *fb = g->fb;
162 size_t buf_size = (size_t)fb->width * fb->height * sizeof(uint32_t);
163 uint32_t *tmp = malloc(buf_size);
164 if (!tmp) return;
165 memcpy(tmp, fb->pixels, buf_size);
166
167 for (int y = 0; y < fb->height; y++) {
168 int src_y = y - dy;
169 // Wrap
170 while (src_y < 0) src_y += fb->height;
171 while (src_y >= fb->height) src_y -= fb->height;
172
173 for (int x = 0; x < fb->width; x++) {
174 int src_x = x - dx;
175 while (src_x < 0) src_x += fb->width;
176 while (src_x >= fb->width) src_x -= fb->width;
177
178 fb->pixels[y * fb->stride + x] = tmp[src_y * fb->stride + src_x];
179 }
180 }
181 free(tmp);
182}
183
184void graph_blur(ACGraph *g, int strength) {
185 ACFramebuffer *fb = g->fb;
186 if (strength <= 0) return;
187
188#ifdef USE_SDL
189 // GPU path: downscale → upscale with bilinear filtering = fast blur
190 if (g->gpu_display) {
191 ACDisplay *d = (ACDisplay *)g->gpu_display;
192 if (d->is_sdl && d->sdl_renderer) {
193 // Divisor controls blur amount: higher = blurrier
194 int divisor = strength + 1;
195 if (divisor > 8) divisor = 8;
196 int small_w = fb->width / divisor;
197 int small_h = fb->height / divisor;
198 if (small_w < 1) small_w = 1;
199 if (small_h < 1) small_h = 1;
200
201 SDL_Texture *src = SDL_CreateTexture(d->sdl_renderer,
202 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING,
203 fb->width, fb->height);
204 SDL_Texture *small = SDL_CreateTexture(d->sdl_renderer,
205 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET,
206 small_w, small_h);
207 SDL_Texture *result = SDL_CreateTexture(d->sdl_renderer,
208 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET,
209 fb->width, fb->height);
210
211 if (src && small && result) {
212 // Upload framebuffer → source texture
213 SDL_UpdateTexture(src, NULL, fb->pixels,
214 fb->stride * (int)sizeof(uint32_t));
215 // Set bilinear filtering for smooth blur
216 SDL_SetTextureScaleMode(src, SDL_SCALEMODE_LINEAR);
217 SDL_SetTextureScaleMode(small, SDL_SCALEMODE_LINEAR);
218
219 // Pass 1: downscale (src → small)
220 SDL_SetRenderTarget(d->sdl_renderer, small);
221 SDL_RenderTexture(d->sdl_renderer, src, NULL, NULL);
222
223 // Pass 2: upscale (small → result)
224 SDL_SetRenderTarget(d->sdl_renderer, result);
225 SDL_RenderTexture(d->sdl_renderer, small, NULL, NULL);
226
227 // Read back from result render target
228 SDL_Rect full = {0, 0, fb->width, fb->height};
229 SDL_Surface *rsurf = SDL_RenderReadPixels(d->sdl_renderer, &full);
230 SDL_SetRenderTarget(d->sdl_renderer, NULL);
231 if (rsurf) {
232 const uint32_t *sp = (const uint32_t *)rsurf->pixels;
233 int spitch = rsurf->pitch / 4;
234 for (int y = 0; y < fb->height && y < rsurf->h; y++) {
235 memcpy(&fb->pixels[y * fb->stride],
236 &sp[y * spitch],
237 (size_t)fb->width * sizeof(uint32_t));
238 }
239 SDL_DestroySurface(rsurf);
240 }
241 }
242 if (src) SDL_DestroyTexture(src);
243 if (small) SDL_DestroyTexture(small);
244 if (result) SDL_DestroyTexture(result);
245 return;
246 }
247 }
248#endif
249
250 // CPU fallback: box blur
251 size_t buf_size = (size_t)fb->width * fb->height * sizeof(uint32_t);
252 uint32_t *tmp = malloc(buf_size);
253 if (!tmp) return;
254 memcpy(tmp, fb->pixels, buf_size);
255
256 int radius = strength;
257 for (int y = 0; y < fb->height; y++) {
258 for (int x = 0; x < fb->width; x++) {
259 int r = 0, gr = 0, b = 0, count = 0;
260 for (int dy = -radius; dy <= radius; dy++) {
261 for (int dx = -radius; dx <= radius; dx++) {
262 int sx = x + dx, sy = y + dy;
263 if (sx >= 0 && sx < fb->width && sy >= 0 && sy < fb->height) {
264 uint32_t p = tmp[sy * fb->stride + sx];
265 r += (p >> 16) & 0xFF;
266 gr += (p >> 8) & 0xFF;
267 b += p & 0xFF;
268 count++;
269 }
270 }
271 }
272 fb->pixels[y * fb->stride + x] = (0xFFu << 24) |
273 (((uint32_t)(r / count)) << 16) |
274 (((uint32_t)(gr / count)) << 8) |
275 (uint32_t)(b / count);
276 }
277 }
278 free(tmp);
279}
280
281void graph_zoom(ACGraph *g, double level) {
282 ACFramebuffer *fb = g->fb;
283 if (!fb || level <= 0.0 || fabs(level - 1.0) < 1e-6) return;
284
285#ifdef USE_SDL
286 // GPU path: render texture with scaled src rect for zoom
287 if (g->gpu_display) {
288 ACDisplay *d = (ACDisplay *)g->gpu_display;
289 if (d->is_sdl && d->sdl_renderer) {
290 const int w = fb->width;
291 const int h = fb->height;
292 const float inv = (float)(1.0 / level);
293
294 SDL_Texture *src = SDL_CreateTexture(d->sdl_renderer,
295 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, w, h);
296 SDL_Texture *dst = SDL_CreateTexture(d->sdl_renderer,
297 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, w, h);
298
299 if (src && dst) {
300 SDL_UpdateTexture(src, NULL, fb->pixels,
301 fb->stride * (int)sizeof(uint32_t));
302 SDL_SetTextureScaleMode(src, SDL_SCALEMODE_NEAREST);
303
304 // Source rect: the portion of the texture visible after zoom
305 float crop_w = w * inv;
306 float crop_h = h * inv;
307 SDL_FRect srcrect = {
308 (w - crop_w) * 0.5f, (h - crop_h) * 0.5f,
309 crop_w, crop_h
310 };
311 SDL_FRect dstrect = {0, 0, (float)w, (float)h};
312
313 SDL_SetRenderTarget(d->sdl_renderer, dst);
314 SDL_SetRenderDrawColor(d->sdl_renderer, 0, 0, 0, 255);
315 SDL_RenderClear(d->sdl_renderer);
316 SDL_RenderTexture(d->sdl_renderer, src, &srcrect, &dstrect);
317
318 // Read back
319 SDL_Rect full = {0, 0, w, h};
320 SDL_Surface *rsurf = SDL_RenderReadPixels(d->sdl_renderer, &full);
321 SDL_SetRenderTarget(d->sdl_renderer, NULL);
322 if (rsurf) {
323 const uint32_t *sp = (const uint32_t *)rsurf->pixels;
324 int spitch = rsurf->pitch / 4;
325 for (int y = 0; y < h && y < rsurf->h; y++) {
326 memcpy(&fb->pixels[y * fb->stride],
327 &sp[y * spitch],
328 (size_t)w * sizeof(uint32_t));
329 }
330 SDL_DestroySurface(rsurf);
331 }
332 }
333 if (src) SDL_DestroyTexture(src);
334 if (dst) SDL_DestroyTexture(dst);
335 return;
336 }
337 }
338#endif
339
340 // CPU fallback
341 const int w = fb->width;
342 const int h = fb->height;
343 size_t buf_size = (size_t)w * h * sizeof(uint32_t);
344 uint32_t *tmp = malloc(buf_size);
345 if (!tmp) return;
346 memcpy(tmp, fb->pixels, buf_size);
347
348 const double cx = (w - 1) * 0.5;
349 const double cy = (h - 1) * 0.5;
350 const double inv = 1.0 / level;
351
352 for (int y = 0; y < h; y++) {
353 for (int x = 0; x < w; x++) {
354 const double src_xf = cx + (x - cx) * inv;
355 const double src_yf = cy + (y - cy) * inv;
356 int sx = (int)llround(src_xf) % w;
357 int sy = (int)llround(src_yf) % h;
358 if (sx < 0) sx += w;
359 if (sy < 0) sy += h;
360
361 fb->pixels[y * fb->stride + x] = tmp[sy * fb->stride + sx];
362 }
363 }
364 free(tmp);
365}
366
367void graph_contrast(ACGraph *g, double level) {
368 ACFramebuffer *fb = g->fb;
369 if (!fb || fabs(level - 1.0) < 1e-6) return;
370
371 const size_t total = (size_t)fb->width * fb->height;
372 for (size_t i = 0; i < total; i++) {
373 uint32_t p = fb->pixels[i];
374 int a = (p >> 24) & 0xFF;
375 int r = (p >> 16) & 0xFF;
376 int gr = (p >> 8) & 0xFF;
377 int b = p & 0xFF;
378
379 r = (int)llround((r - 128) * level + 128);
380 gr = (int)llround((gr - 128) * level + 128);
381 b = (int)llround((b - 128) * level + 128);
382
383 fb->pixels[i] = ((uint32_t)a << 24) |
384 ((uint32_t)clamp_u8(r) << 16) |
385 ((uint32_t)clamp_u8(gr) << 8) |
386 (uint32_t)clamp_u8(b);
387 }
388}
389
390void graph_spin(ACGraph *g, double angle_radians) {
391 ACFramebuffer *fb = g->fb;
392 if (!fb || fabs(angle_radians) < 1e-6) return;
393
394#ifdef USE_SDL
395 // GPU path: SDL3 texture rotation
396 if (g->gpu_display) {
397 ACDisplay *d = (ACDisplay *)g->gpu_display;
398 if (d->is_sdl && d->sdl_renderer) {
399 const int w = fb->width;
400 const int h = fb->height;
401 double angle_degrees = angle_radians * (180.0 / M_PI);
402
403 SDL_Texture *src = SDL_CreateTexture(d->sdl_renderer,
404 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, w, h);
405 SDL_Texture *dst = SDL_CreateTexture(d->sdl_renderer,
406 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, w, h);
407
408 if (src && dst) {
409 SDL_UpdateTexture(src, NULL, fb->pixels,
410 fb->stride * (int)sizeof(uint32_t));
411 SDL_SetTextureScaleMode(src, SDL_SCALEMODE_NEAREST);
412
413 SDL_FRect dstrect = {0, 0, (float)w, (float)h};
414 SDL_FPoint center = {w * 0.5f, h * 0.5f};
415
416 SDL_SetRenderTarget(d->sdl_renderer, dst);
417 SDL_SetRenderDrawColor(d->sdl_renderer, 0, 0, 0, 255);
418 SDL_RenderClear(d->sdl_renderer);
419 SDL_RenderTextureRotated(d->sdl_renderer, src, NULL, &dstrect,
420 angle_degrees, ¢er, SDL_FLIP_NONE);
421
422 // Read back
423 SDL_Rect full = {0, 0, w, h};
424 SDL_Surface *rsurf = SDL_RenderReadPixels(d->sdl_renderer, &full);
425 SDL_SetRenderTarget(d->sdl_renderer, NULL);
426 if (rsurf) {
427 const uint32_t *sp = (const uint32_t *)rsurf->pixels;
428 int spitch = rsurf->pitch / 4;
429 for (int y = 0; y < h && y < rsurf->h; y++) {
430 memcpy(&fb->pixels[y * fb->stride],
431 &sp[y * spitch],
432 (size_t)w * sizeof(uint32_t));
433 }
434 SDL_DestroySurface(rsurf);
435 }
436 }
437 if (src) SDL_DestroyTexture(src);
438 if (dst) SDL_DestroyTexture(dst);
439 return;
440 }
441 }
442#endif
443
444 // CPU fallback
445 size_t buf_size = (size_t)fb->width * fb->height * sizeof(uint32_t);
446 uint32_t *tmp = malloc(buf_size);
447 if (!tmp) return;
448 memcpy(tmp, fb->pixels, buf_size);
449
450 const int w = fb->width;
451 const int h = fb->height;
452 const double cx = (w - 1) * 0.5;
453 const double cy = (h - 1) * 0.5;
454 const double c = cos(angle_radians);
455 const double s = sin(angle_radians);
456
457 for (int y = 0; y < h; y++) {
458 for (int x = 0; x < w; x++) {
459 const double dx = x - cx;
460 const double dy = y - cy;
461 const double src_xf = cx + (dx * c + dy * s);
462 const double src_yf = cy + (-dx * s + dy * c);
463 int sx = (int)llround(src_xf) % w;
464 int sy = (int)llround(src_yf) % h;
465 if (sx < 0) sx += w;
466 if (sy < 0) sy += h;
467
468 fb->pixels[y * fb->stride + x] = tmp[sy * fb->stride + sx];
469 }
470 }
471 free(tmp);
472}
473
474ACFramebuffer *graph_painting(int w, int h) {
475 return fb_create(w, h);
476}
477
478void graph_paste(ACGraph *g, ACFramebuffer *src, int dx, int dy) {
479 for (int y = 0; y < src->height; y++) {
480 for (int x = 0; x < src->width; x++) {
481 uint32_t pixel = src->pixels[y * src->stride + x];
482 fb_blend_pixel(g->fb, dx + x, dy + y, pixel);
483 }
484 }
485}
486
487void graph_page(ACGraph *g, ACFramebuffer *target) {
488 g->fb = target ? target : g->screen;
489}
490
491void graph_qr(ACGraph *g, const char *text, int x, int y, int scale) {
492 if (!g || !text || !text[0]) return;
493 if (scale < 1) scale = 1;
494
495 uint8_t qr_buf[qrcodegen_BUFFER_LEN_FOR_VERSION(10)];
496 uint8_t tmp_buf[qrcodegen_BUFFER_LEN_FOR_VERSION(10)];
497
498 if (!qrcodegen_encodeText(text, tmp_buf, qr_buf,
499 qrcodegen_Ecc_LOW, qrcodegen_VERSION_MIN, 10,
500 qrcodegen_Mask_AUTO, true)) {
501 return; // encode failed (text too long for version 10)
502 }
503
504 int size = qrcodegen_getSize(qr_buf);
505 int margin = 2; // quiet zone
506
507 // Draw white background with margin
508 int total = (size + margin * 2) * scale;
509 ACColor saved = g->ink;
510 graph_ink(g, (ACColor){255, 255, 255, 255});
511 graph_box(g, x, y, total, total, 1);
512
513 // Draw black modules
514 graph_ink(g, (ACColor){0, 0, 0, 255});
515 for (int qy = 0; qy < size; qy++) {
516 for (int qx = 0; qx < size; qx++) {
517 if (qrcodegen_getModule(qr_buf, qx, qy)) {
518 graph_box(g, x + (qx + margin) * scale, y + (qy + margin) * scale,
519 scale, scale, 1);
520 }
521 }
522 }
523 g->ink = saved;
524}