Monorepo for Aesthetic.Computer aesthetic.computer
at main 524 lines 19 kB view raw
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, &center, 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}