1,121 lines40 KBC
1/*
2gfx.c: software drawing on the canvas.
3*/
4#include <stdlib.h>
5#include <string.h>
6#include <math.h>
7#include "gfx.h"
8#include "gpu.h"
9
10#define STBI_ONLY_JPEG
11#define STBI_ONLY_PNG
12#define STBI_NO_SIMD
13#define STBI_NO_STDIO
14#define STBI_MAX_DIMENSIONS 2048 // artwork comes from the network (and is asked for at 400 px): refuse giant images
15#define STB_IMAGE_IMPLEMENTATION
16#include "thirdparty/stb_image.h"
17
18static uint32_t *cv;
19static int cw, ch;
20static int clx0, cly0, clx1, cly1; // clip, x1/y1 exclusive
21static int gpu_on; // the calls build the GPU's list (the end of this file)
22
23// the GPU path's versions (below)
24static void G_Rect(float x, float y, float w, float h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3);
25static void G_Round_Rect(int x, int y, int w, int h, int r, uint32_t c);
26static void G_Mask(const uint8_t *mask, int mw, int mh, int pitch, float x, float y, float dw, float dh, uint32_t c);
27static void G_Image(const image_t *img, int x, int y, int w, int h, int radius, int alpha);
28static void G_Image_Part(const image_t *img, int sx, int sy, int sw, int sh, int x, int y, int w, int h, uint32_t top, uint32_t bottom);
29static void G_Tint(const image_t *img, int sx, int sw, int x, int y, uint32_t c);
30static int G_Icon(int icon, int x, int y, int sz, uint32_t c);
31static void G_Noise_Gradient(int x, int y, int w, int h, int slot);
32
33void gfx_init(uint32_t *canvas, int w, int h)
34{
35 if (gpu_on && !canvas) { cw = w; ch = h; gfx_unclip(); return; }
36 cv = canvas;
37 cw = w;
38 ch = h;
39 gfx_unclip();
40}
41
42uint32_t *gfx_canvas(void) { return cv; }
43
44static void Raw_gfx_clip(int x, int y, int w, int h)
45{
46 clx0 = x < 0 ? 0 : x;
47 cly0 = y < 0 ? 0 : y;
48 clx1 = x + w > cw ? cw : x + w;
49 cly1 = y + h > ch ? ch : y + h;
50}
51
52static void Raw_gfx_clip_get(int *x, int *y, int *w, int *h)
53{
54 *x = clx0; *y = cly0; *w = clx1 - clx0; *h = cly1 - cly0;
55}
56
57void gfx_unclip(void)
58{
59 clx0 = cly0 = 0;
60 clx1 = cw;
61 cly1 = ch;
62}
63
64static inline uint32_t blend(uint32_t d, uint32_t s, int a)
65{
66 // a: 0..255
67 if (a >= 255) return s | 0xff000000;
68 if (a <= 0) return d;
69 uint32_t rb = (((s & 0xff00ff) * a) + ((d & 0xff00ff) * (255 - a))) >> 8;
70 uint32_t g = (((s & 0x00ff00) * a) + ((d & 0x00ff00) * (255 - a))) >> 8;
71 return 0xff000000 | (rb & 0xff00ff) | (g & 0x00ff00);
72}
73
74static inline void plot(int x, int y, uint32_t c, int a)
75{
76 if (x < clx0 || x >= clx1 || y < cly0 || y >= cly1 || a <= 0)
77 return;
78 uint32_t *p = &cv[y * cw + x];
79 *p = blend(*p, c, a);
80}
81
82// a row of one colour, two pixels a store
83static inline void Fill(uint32_t *p, int n, uint32_t c)
84{
85 if (((uintptr_t)p & 4) && n > 0) { *p++ = c; n--; }
86 uint64_t cc = ((uint64_t)c << 32) | c;
87 uint64_t *q = (uint64_t *)p;
88 int pairs = n >> 1;
89 for (int i = 0; i < pairs; i++) q[i] = cc;
90 if (n & 1) p[n - 1] = c;
91}
92
93void gfx_clear(uint32_t c)
94{
95 if (gpu_on) {
96 int x0 = clx0, y0 = cly0, x1 = clx1, y1 = cly1;
97 gfx_unclip(); // (a clear isn't clipped)
98 c |= 0xff000000;
99 G_Rect(0, 0, cw, ch, c, c, c, c);
100 clx0 = x0; cly0 = y0; clx1 = x1; cly1 = y1;
101 return;
102 }
103 Fill(cv, cw * ch, c | 0xff000000);
104}
105
106static void Raw_gfx_rect(int x, int y, int w, int h, uint32_t c)
107{
108 if (gpu_on) { if (c >> 24) G_Rect(x, y, w, h, c, c, c, c); return; }
109 int a = c >> 24;
110 int x0 = x < clx0 ? clx0 : x, y0 = y < cly0 ? cly0 : y;
111 int x1 = x + w > clx1 ? clx1 : x + w, y1 = y + h > cly1 ? cly1 : y + h;
112 if (x0 >= x1 || y0 >= y1 || !a)
113 return;
114 for (int j = y0; j < y1; j++) {
115 uint32_t *p = &cv[j * cw + x0];
116 if (a == 255) {
117 Fill(p, x1 - x0, c | 0xff000000);
118 } else {
119 for (int i = x0; i < x1; i++, p++) *p = blend(*p, c, a);
120 }
121 }
122}
123
124// coverage of a pixel in a rounded box's corner (0..255)
125static inline int corner_cov(float px, float py, float cx, float cy, float r)
126{
127 float dx = px - cx, dy = py - cy;
128 float d = sqrtf(dx * dx + dy * dy);
129 float cov = r + 0.5f - d;
130 if (cov >= 1.0f) return 255;
131 if (cov <= 0.0f) return 0;
132 return (int)(cov * 255.0f);
133}
134
135// corner coverage tables, one per radius: [j * r + i] for the top-left
136// corner's pixel (i, j). Built once: the PPU is slow at square roots.
137#define MAX_LUT_R 256
138static uint8_t *cov_lut[MAX_LUT_R + 1];
139
140static const uint8_t *Cov(int r)
141{
142 if (r <= 0 || r > MAX_LUT_R) return NULL;
143 if (!cov_lut[r]) {
144 uint8_t *t = malloc(r * r);
145 if (!t) return NULL;
146 for (int j = 0; j < r; j++)
147 for (int i = 0; i < r; i++)
148 t[j * r + i] = (uint8_t)corner_cov(i + 0.5f, j + 0.5f, r, r, r);
149 cov_lut[r] = t;
150 }
151 return cov_lut[r];
152}
153
154static void Raw_gfx_round_rect(int x, int y, int w, int h, int r, uint32_t c)
155{
156 int a = c >> 24;
157 if (r <= 0) { Raw_gfx_rect(x, y, w, h, c); return; }
158 if (gpu_on) { G_Round_Rect(x, y, w, h, r, c); return; }
159 if (r * 2 > w) r = w / 2;
160 if (r * 2 > h) r = h / 2;
161 // middle band and the side bands between the corners
162 Raw_gfx_rect(x, y + r, w, h - 2 * r, c);
163 Raw_gfx_rect(x + r, y, w - 2 * r, r, c);
164 Raw_gfx_rect(x + r, y + h - r, w - 2 * r, r, c);
165 const uint8_t *lut = Cov(r);
166 for (int j = 0; j < r; j++) {
167 for (int i = 0; i < r; i++) {
168 int cov = lut ? lut[j * r + i] : corner_cov(i + 0.5f, j + 0.5f, r, r, r);
169 if (!cov) continue;
170 int aa = a * cov / 255;
171 plot(x + i, y + j, c, aa);
172 plot(x + w - 1 - i, y + j, c, aa);
173 plot(x + i, y + h - 1 - j, c, aa);
174 plot(x + w - 1 - i, y + h - 1 - j, c, aa);
175 }
176 }
177}
178
179static inline uint32_t lerp_c(uint32_t a, uint32_t b, int t) // t 0..256
180{
181 int aa = a >> 24, ab = b >> 24;
182 int ra = (a >> 16) & 255, rb = (b >> 16) & 255;
183 int ga = (a >> 8) & 255, gb = (b >> 8) & 255;
184 int ba = a & 255, bb = b & 255;
185 return argb(aa + (ab - aa) * t / 256, ra + (rb - ra) * t / 256, ga + (gb - ga) * t / 256, ba + (bb - ba) * t / 256);
186}
187
188void gfx_vgradient(int x, int y, int w, int h, uint32_t top, uint32_t bottom)
189{
190 if (gpu_on) { if (w > 0 && h > 0) G_Rect(x, y, w, h, top, top, bottom, bottom); return; }
191 for (int j = 0; j < h; j++)
192 Raw_gfx_rect(x, y + j, w, 1, lerp_c(top, bottom, h > 1 ? j * 256 / (h - 1) : 0));
193}
194
195/*
196A big gradient with a fine grain over it: dark gradients across 720 rows
197step only every 30 rows or so and show bands; adding noise below one
198level before rounding hides the steps. Built once per size and colours
199(two cached), then copied in rows, so it costs a memcpy a frame.
200*/
201static uint8_t noise_tile[64 * 64];
202static int noise_ready;
203
204static void Noise_Init(void)
205{
206 uint32_t r = 0x9e3779b9;
207 for (int i = 0; i < 64 * 64; i++) {
208 // triangular: the sum of two uniforms, 0..510, so the grain has no hard edges
209 r ^= r << 13; r ^= r >> 17; r ^= r << 5;
210 int a = r & 255;
211 r ^= r << 13; r ^= r >> 17; r ^= r << 5;
212 noise_tile[i] = (uint8_t)((a + (int)(r & 255)) >> 1);
213 }
214 noise_ready = 1;
215}
216
217static struct { int w, h; uint32_t top, bottom, used; uint32_t *px; int fresh; } grad_cache[2];
218static uint32_t grad_clock;
219
220void gfx_vgradient_noise(int x, int y, int w, int h, uint32_t top, uint32_t bottom)
221{
222 if (w <= 0 || h <= 0) return;
223 if (!noise_ready) Noise_Init();
224 int k, slot = -1;
225 for (k = 0; k < 2; k++)
226 if (grad_cache[k].px && grad_cache[k].w == w && grad_cache[k].h == h
227 && grad_cache[k].top == top && grad_cache[k].bottom == bottom) slot = k;
228 if (slot < 0) {
229 slot = grad_cache[0].used <= grad_cache[1].used ? 0 : 1;
230 if (grad_cache[slot].w * grad_cache[slot].h < w * h || !grad_cache[slot].px) {
231 free(grad_cache[slot].px);
232 grad_cache[slot].px = malloc((size_t)w * h * 4);
233 if (!grad_cache[slot].px) { grad_cache[slot].w = 0; gfx_vgradient(x, y, w, h, top, bottom); return; }
234 }
235 grad_cache[slot].w = w; grad_cache[slot].h = h;
236 grad_cache[slot].fresh = 1;
237 grad_cache[slot].top = top; grad_cache[slot].bottom = bottom;
238 uint32_t *o = grad_cache[slot].px;
239 int r0 = (top >> 16) & 255, g0 = (top >> 8) & 255, b0 = top & 255;
240 int r1 = (bottom >> 16) & 255, g1 = (bottom >> 8) & 255, b1 = bottom & 255;
241 for (int j = 0; j < h; j++) {
242 int t = h > 1 ? j * 65536 / (h - 1) : 0;
243 // 8.8 fixed point
244 int R = (r0 << 8) + (int)(((int64_t)(r1 - r0) * t) >> 8);
245 int G = (g0 << 8) + (int)(((int64_t)(g1 - g0) * t) >> 8);
246 int B = (b0 << 8) + (int)(((int64_t)(b1 - b0) * t) >> 8);
247 const uint8_t *nrow = &noise_tile[(j & 63) * 64];
248 for (int i = 0; i < w; i++) {
249 // -1..+1 level: rounds the fraction away, plus a little grain
250 int n = ((int)nrow[i & 63] - 128) * 2;
251 int rr = (R + n + 128) >> 8, gg = (G + n + 128) >> 8, bb = (B + n + 128) >> 8;
252 rr = rr < 0 ? 0 : rr > 255 ? 255 : rr;
253 gg = gg < 0 ? 0 : gg > 255 ? 255 : gg;
254 bb = bb < 0 ? 0 : bb > 255 ? 255 : bb;
255 *o++ = 0xff000000 | (rr << 16) | (gg << 8) | bb;
256 }
257 }
258 }
259 grad_cache[slot].used = ++grad_clock;
260 if (gpu_on) { G_Noise_Gradient(x, y, w, h, slot); return; }
261 const uint32_t *src = grad_cache[slot].px;
262 for (int j = 0; j < h; j++) {
263 int yy = y + j;
264 if (yy < cly0 || yy >= cly1) continue;
265 int x0 = x < clx0 ? clx0 : x, x1 = x + w > clx1 ? clx1 : x + w;
266 if (x1 > x0) memcpy(&cv[yy * cw + x0], &src[j * w + (x0 - x)], (size_t)(x1 - x0) * 4);
267 }
268}
269
270void gfx_hgradient(int x, int y, int w, int h, uint32_t left, uint32_t right)
271{
272 if (gpu_on) { if (w > 0 && h > 0) G_Rect(x, y, w, h, left, right, left, right); return; }
273 for (int i = 0; i < w; i++)
274 Raw_gfx_rect(x + i, y, 1, h, lerp_c(left, right, w > 1 ? i * 256 / (w - 1) : 0));
275}
276
277static void Raw_gfx_circle(int cx, int cy, int r, uint32_t c)
278{
279 Raw_gfx_round_rect(cx - r, cy - r, 2 * r, 2 * r, r, c);
280}
281
282static void ring(float cx, float cy, float r, float thick, uint32_t c)
283{
284 int a = c >> 24;
285 int R = (int)(r + thick) + 2;
286 for (int j = -R; j <= R; j++)
287 for (int i = -R; i <= R; i++) {
288 float px = i + 0.5f, py = j + 0.5f;
289 float d = sqrtf(px * px + py * py);
290 float cov = thick * 0.5f + 0.5f - fabsf(d - r);
291 if (cov <= 0) continue;
292 if (cov > 1) cov = 1;
293 plot((int)cx + i, (int)cy + j, c, (int)(a * cov));
294 }
295}
296
297// a thick line with round ends, anti-aliased
298static void capsule(float x0, float y0, float x1, float y1, float thick, uint32_t c)
299{
300 int a = c >> 24;
301 float hr = thick * 0.5f;
302 int bx0 = (int)floorf(fminf(x0, x1) - hr - 1), bx1 = (int)ceilf(fmaxf(x0, x1) + hr + 1);
303 int by0 = (int)floorf(fminf(y0, y1) - hr - 1), by1 = (int)ceilf(fmaxf(y0, y1) + hr + 1);
304 float dx = x1 - x0, dy = y1 - y0, ll = dx * dx + dy * dy;
305 for (int j = by0; j <= by1; j++)
306 for (int i = bx0; i <= bx1; i++) {
307 float px = i + 0.5f - x0, py = j + 0.5f - y0;
308 float t = ll > 0 ? (px * dx + py * dy) / ll : 0;
309 if (t < 0) t = 0; else if (t > 1) t = 1;
310 float ex = px - t * dx, ey = py - t * dy;
311 float cov = hr + 0.5f - sqrtf(ex * ex + ey * ey);
312 if (cov <= 0) continue;
313 if (cov > 1) cov = 1;
314 plot(i, j, c, (int)(a * cov));
315 }
316}
317
318// a filled triangle, anti-aliased along its edges
319static void triangle(float x0, float y0, float x1, float y1, float x2, float y2, uint32_t c)
320{
321 int a = c >> 24;
322 int bx0 = (int)floorf(fminf(x0, fminf(x1, x2))) - 1, bx1 = (int)ceilf(fmaxf(x0, fmaxf(x1, x2))) + 1;
323 int by0 = (int)floorf(fminf(y0, fminf(y1, y2))) - 1, by1 = (int)ceilf(fmaxf(y0, fmaxf(y1, y2))) + 1;
324 float area = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0);
325 float s = area < 0 ? -1.0f : 1.0f;
326 float ex[3][3] = {{x0, y0, 0}, {x1, y1, 0}, {x2, y2, 0}};
327 for (int j = by0; j <= by1; j++)
328 for (int i = bx0; i <= bx1; i++) {
329 float px = i + 0.5f, py = j + 0.5f, cov = 1.0f;
330 for (int k = 0; k < 3; k++) {
331 float ax = ex[k][0], ay = ex[k][1], bx = ex[(k + 1) % 3][0], by = ex[(k + 1) % 3][1];
332 float nx = by - ay, ny = ax - bx, len = sqrtf(nx * nx + ny * ny);
333 if (len <= 0) continue;
334 // signed distance inside the edge, in pixels
335 float d = -s * ((px - ax) * nx + (py - ay) * ny) / len;
336 float cv_ = d + 0.5f;
337 if (cv_ < cov) cov = cv_;
338 }
339 if (cov <= 0) continue;
340 if (cov > 1) cov = 1;
341 plot(i, j, c, (int)(a * cov));
342 }
343}
344
345void gfx_mask(const uint8_t *mask, int mw, int mh, int pitch, int x, int y, uint32_t c)
346{
347 if (gpu_on) { G_Mask(mask, mw, mh, pitch, x, y, mw, mh, c); return; }
348 int a = c >> 24;
349 for (int j = 0; j < mh; j++) {
350 int yy = y + j;
351 if (yy < cly0 || yy >= cly1) continue;
352 const uint8_t *m = mask + j * pitch;
353 for (int i = 0; i < mw; i++) {
354 int xx = x + i;
355 if (!m[i] || xx < clx0 || xx >= clx1) continue;
356 uint32_t *p = &cv[yy * cw + xx];
357 *p = blend(*p, c, m[i] * a / 255);
358 }
359 }
360}
361
362static int img_flip; // (Raw_gfx_image: the picture turned round, left to right)
363
364static void Raw_gfx_image(const image_t *img, int x, int y, int w, int h, int radius, int alpha)
365{
366 if (!img || w <= 0 || h <= 0)
367 return;
368 if (gpu_on) { G_Image(img, x, y, w, h, radius, alpha); return; }
369 int same = (img->w == w && img->h == h), flip = img_flip;
370 const uint8_t *lut = radius > 0 ? Cov(radius) : NULL;
371 if (same && !flip && img->opaque && alpha >= 255) {
372 // cover art: whole rows copied; with rounded corners only the
373 // corner pixels are blended (a circle's edge, for artists)
374 int x0c = x < clx0 ? clx0 : x, x1c = x + w > clx1 ? clx1 : x + w;
375 for (int j = 0; j < h; j++) {
376 int yy = y + j;
377 if (yy < cly0 || yy >= cly1 || x0c >= x1c) continue;
378 const uint32_t *row = img->px + j * img->w;
379 uint32_t *drow = &cv[yy * cw];
380 int cj = (lut && j < radius) ? j : (lut && j >= h - radius) ? h - 1 - j : -1;
381 int inset = 0; // columns at each end handled one by one
382 if (cj >= 0) {
383 // past the first fully covered pixel of this corner row, it's a plain copy
384 inset = radius;
385 for (int i = 0; i < radius; i++)
386 if (lut[cj * radius + i] == 255) { inset = i; break; }
387 for (int i = 0; i < inset; i++) {
388 int cov = lut[cj * radius + i];
389 if (!cov) continue;
390 int xl = x + i, xr = x + w - 1 - i;
391 if (xl >= x0c && xl < x1c) drow[xl] = blend(drow[xl], row[i], cov);
392 if (xr >= x0c && xr < x1c) drow[xr] = blend(drow[xr], row[w - 1 - i], cov);
393 }
394 }
395 int a0 = x + inset > x0c ? x + inset : x0c, a1 = x + w - inset < x1c ? x + w - inset : x1c;
396 if (a1 > a0)
397 memcpy(&drow[a0], &row[a0 - x], (a1 - a0) * 4);
398 }
399 return;
400 }
401 if (same && !flip && radius <= 0 && alpha >= 255) {
402 // straight copy where the image is opaque
403 int x0 = x < clx0 ? clx0 : x, x1 = x + w > clx1 ? clx1 : x + w;
404 for (int j = 0; j < h; j++) {
405 int yy = y + j;
406 if (yy < cly0 || yy >= cly1 || x0 >= x1) continue;
407 const uint32_t *sp = img->px + j * img->w + (x0 - x);
408 uint32_t *dp = &cv[yy * cw + x0];
409 for (int i = x0; i < x1; i++, sp++, dp++) {
410 uint32_t sv = *sp;
411 int sa = sv >> 24;
412 *dp = sa == 255 ? sv : blend(*dp, sv, sa);
413 }
414 }
415 return;
416 }
417 // 16.16 steps for sampling
418 int sx = (img->w << 16) / w, sy = (img->h << 16) / h;
419 for (int j = 0; j < h; j++) {
420 int yy = y + j;
421 if (yy < cly0 || yy >= cly1) continue;
422 int fy = same ? (j << 16) : (j * sy + sy / 2 - 32768);
423 if (fy < 0) fy = 0;
424 int y0 = fy >> 16, y1 = y0 + 1 < img->h ? y0 + 1 : y0, ty = (fy >> 8) & 255;
425 for (int i = 0; i < w; i++) {
426 int xx = x + i;
427 if (xx < clx0 || xx >= clx1) continue;
428 int a = alpha;
429 if (radius > 0) {
430 int ci = i < radius ? i : (i >= w - radius ? w - 1 - i : -1);
431 int cj = j < radius ? j : (j >= h - radius ? h - 1 - j : -1);
432 if (ci >= 0 && cj >= 0) {
433 int cov = lut ? lut[cj * radius + ci] : corner_cov(ci + 0.5f, cj + 0.5f, radius, radius, radius);
434 a = a * cov / 255;
435 if (!a) continue;
436 }
437 }
438 uint32_t s;
439 int si = flip ? w - 1 - i : i; // (the source column)
440 if (same) {
441 s = img->px[j * img->w + si];
442 } else {
443 int fx = si * sx + sx / 2 - 32768;
444 if (fx < 0) fx = 0;
445 int x0 = fx >> 16, x1 = x0 + 1 < img->w ? x0 + 1 : x0, tx = (fx >> 8) & 255;
446 uint32_t top = lerp_c(img->px[y0 * img->w + x0], img->px[y0 * img->w + x1], tx);
447 uint32_t bot = lerp_c(img->px[y1 * img->w + x0], img->px[y1 * img->w + x1], tx);
448 s = lerp_c(top, bot, ty);
449 }
450 int sa = (s >> 24) * a / 255;
451 uint32_t *p = &cv[yy * cw + xx];
452 *p = blend(*p, s, sa);
453 }
454 }
455}
456
457void gfx_tint(const image_t *img, int sx, int sw, int x, int y, uint32_t c)
458{
459 // columns sx..sx+sw of img (a frame of a strip) at x, y
460 int a = c >> 24;
461 if (!img) return;
462 if (gpu_on) { G_Tint(img, sx, sw, x, y, c); return; }
463 for (int j = 0; j < img->h; j++) {
464 int yy = y + j;
465 if (yy < cly0 || yy >= cly1) continue;
466 const uint32_t *sp = img->px + j * img->w + sx;
467 for (int i = 0; i < sw; i++) {
468 int xx = x + i;
469 int m = sp[i] >> 24;
470 if (!m || xx < clx0 || xx >= clx1) continue;
471 uint32_t *p = &cv[yy * cw + xx];
472 *p = blend(*p, c, m * a / 255);
473 }
474 }
475}
476
477void gfx_icon(int icon, int x, int y, int sz, uint32_t c)
478{
479 if (icon == ICON_REPEAT_ONE) icon = ICON_REPEAT;
480 if (gpu_on && G_Icon(icon, x, y, sz, c)) return;
481 float s = sz, X = x, Y = y;
482 float t = s * 0.1f; // line weight
483 if (t < 1.6f) t = 1.6f;
484 switch (icon) {
485 case ICON_PLAY:
486 triangle(X + s * 0.25f, Y + s * 0.15f, X + s * 0.25f, Y + s * 0.85f, X + s * 0.85f, Y + s * 0.5f, c);
487 break;
488 case ICON_PAUSE:
489 Raw_gfx_round_rect(x + (int)(s * 0.22f), y + (int)(s * 0.15f), (int)(s * 0.2f), (int)(s * 0.7f), (int)(s * 0.04f), c);
490 Raw_gfx_round_rect(x + (int)(s * 0.58f), y + (int)(s * 0.15f), (int)(s * 0.2f), (int)(s * 0.7f), (int)(s * 0.04f), c);
491 break;
492 case ICON_NEXT:
493 triangle(X + s * 0.18f, Y + s * 0.2f, X + s * 0.18f, Y + s * 0.8f, X + s * 0.68f, Y + s * 0.5f, c);
494 Raw_gfx_rect(x + (int)(s * 0.7f), y + (int)(s * 0.2f), (int)(s * 0.12f) + 1, (int)(s * 0.6f), c);
495 break;
496 case ICON_PREV:
497 triangle(X + s * 0.82f, Y + s * 0.2f, X + s * 0.82f, Y + s * 0.8f, X + s * 0.32f, Y + s * 0.5f, c);
498 Raw_gfx_rect(x + (int)(s * 0.18f), y + (int)(s * 0.2f), (int)(s * 0.12f) + 1, (int)(s * 0.6f), c);
499 break;
500 case ICON_SHUFFLE:
501 capsule(X + s * 0.12f, Y + s * 0.3f, X + s * 0.35f, Y + s * 0.3f, t, c);
502 capsule(X + s * 0.35f, Y + s * 0.3f, X + s * 0.62f, Y + s * 0.7f, t, c);
503 capsule(X + s * 0.62f, Y + s * 0.7f, X + s * 0.78f, Y + s * 0.7f, t, c);
504 capsule(X + s * 0.12f, Y + s * 0.7f, X + s * 0.35f, Y + s * 0.7f, t, c);
505 capsule(X + s * 0.35f, Y + s * 0.7f, X + s * 0.62f, Y + s * 0.3f, t, c);
506 capsule(X + s * 0.62f, Y + s * 0.3f, X + s * 0.78f, Y + s * 0.3f, t, c);
507 triangle(X + s * 0.76f, Y + s * 0.18f, X + s * 0.76f, Y + s * 0.42f, X + s * 0.92f, Y + s * 0.3f, c);
508 triangle(X + s * 0.76f, Y + s * 0.58f, X + s * 0.76f, Y + s * 0.82f, X + s * 0.92f, Y + s * 0.7f, c);
509 break;
510 case ICON_REPEAT:
511 capsule(X + s * 0.2f, Y + s * 0.55f, X + s * 0.2f, Y + s * 0.35f, t, c);
512 capsule(X + s * 0.2f, Y + s * 0.35f, X + s * 0.72f, Y + s * 0.35f, t, c);
513 capsule(X + s * 0.8f, Y + s * 0.45f, X + s * 0.8f, Y + s * 0.65f, t, c);
514 capsule(X + s * 0.8f, Y + s * 0.65f, X + s * 0.28f, Y + s * 0.65f, t, c);
515 triangle(X + s * 0.7f, Y + s * 0.22f, X + s * 0.7f, Y + s * 0.48f, X + s * 0.86f, Y + s * 0.35f, c);
516 triangle(X + s * 0.3f, Y + s * 0.52f, X + s * 0.3f, Y + s * 0.78f, X + s * 0.14f, Y + s * 0.65f, c);
517 break;
518 case ICON_NOTE:
519 Raw_gfx_circle(x + (int)(s * 0.36f), y + (int)(s * 0.72f), (int)(s * 0.15f), c);
520 capsule(X + s * 0.49f, Y + s * 0.72f, X + s * 0.49f, Y + s * 0.15f, t, c);
521 capsule(X + s * 0.49f, Y + s * 0.15f, X + s * 0.75f, Y + s * 0.28f, t, c);
522 break;
523 case ICON_PERSON:
524 Raw_gfx_circle(x + sz / 2, y + (int)(s * 0.33f), (int)(s * 0.17f), c);
525 Raw_gfx_round_rect(x + (int)(s * 0.2f), y + (int)(s * 0.56f), (int)(s * 0.6f), (int)(s * 0.32f), (int)(s * 0.16f), c);
526 break;
527 case ICON_ALBUM:
528 ring(X + s * 0.5f, Y + s * 0.5f, s * 0.3f, t, c);
529 Raw_gfx_circle(x + sz / 2, y + sz / 2, (int)(s * 0.08f) + 1, c);
530 break;
531 case ICON_LIST:
532 for (int k = 0; k < 3; k++) {
533 float yy = Y + s * (0.28f + 0.22f * k);
534 Raw_gfx_circle(x + (int)(s * 0.18f), (int)yy, (int)(t * 0.6f) + 1, c);
535 capsule(X + s * 0.34f, yy, X + s * 0.84f, yy, t, c);
536 }
537 break;
538 case ICON_GEAR:
539 for (int k = 0; k < 8; k++) {
540 float an = k * 3.14159265f / 4;
541 capsule(X + s * 0.5f + cosf(an) * s * 0.26f, Y + s * 0.5f + sinf(an) * s * 0.26f,
542 X + s * 0.5f + cosf(an) * s * 0.38f, Y + s * 0.5f + sinf(an) * s * 0.38f, t * 1.4f, c);
543 }
544 ring(X + s * 0.5f, Y + s * 0.5f, s * 0.22f, t * 1.2f, c);
545 break;
546 case ICON_WAVE: {
547 static const float hgt[5] = {0.3f, 0.6f, 0.9f, 0.55f, 0.35f};
548 for (int k = 0; k < 5; k++) {
549 float xx = X + s * (0.18f + 0.16f * k), hh = s * hgt[k] * 0.38f;
550 capsule(xx, Y + s * 0.5f - hh, xx, Y + s * 0.5f + hh, t, c);
551 }
552 break;
553 }
554 case ICON_HOME:
555 triangle(X + s * 0.5f, Y + s * 0.14f, X + s * 0.1f, Y + s * 0.5f, X + s * 0.9f, Y + s * 0.5f, c);
556 Raw_gfx_round_rect(x + (int)(s * 0.22f), y + (int)(s * 0.46f), (int)(s * 0.56f), (int)(s * 0.4f), (int)(s * 0.05f), c);
557 break;
558 case ICON_GRID:
559 for (int k = 0; k < 4; k++)
560 Raw_gfx_round_rect(x + (int)(s * (0.16f + 0.38f * (k & 1))), y + (int)(s * (0.16f + 0.38f * (k >> 1))),
561 (int)(s * 0.3f), (int)(s * 0.3f), (int)(s * 0.06f), c);
562 break;
563 case ICON_QUEUE: // Up Next: a play mark and three lines
564 triangle(X + s * 0.1f, Y + s * 0.16f, X + s * 0.1f, Y + s * 0.44f, X + s * 0.34f, Y + s * 0.3f, c);
565 capsule(X + s * 0.46f, Y + s * 0.3f, X + s * 0.88f, Y + s * 0.3f, t, c);
566 capsule(X + s * 0.14f, Y + s * 0.53f, X + s * 0.88f, Y + s * 0.53f, t, c);
567 capsule(X + s * 0.14f, Y + s * 0.76f, X + s * 0.88f, Y + s * 0.76f, t, c);
568 break;
569 case ICON_SEARCH:
570 ring(X + s * 0.42f, Y + s * 0.42f, s * 0.22f, t, c);
571 capsule(X + s * 0.6f, Y + s * 0.6f, X + s * 0.82f, Y + s * 0.82f, t * 1.2f, c);
572 break;
573 }
574}
575
576image_t *image_new(int w, int h)
577{
578 image_t *img = malloc(sizeof(*img));
579 if (!img) return NULL;
580 img->w = w;
581 img->h = h;
582 img->px = malloc((size_t)w * h * 4);
583 img->opaque = 0;
584 img->rev = 0;
585 img->gpu = NULL;
586 if (!img->px) { free(img); return NULL; }
587 return img;
588}
589
590static void G_Image_Forget(image_t *img);
591
592void image_touch(image_t *img) { if (img) img->rev++; }
593
594void image_free(image_t *img)
595{
596 if (!img) return;
597 G_Image_Forget(img);
598 free(img->px);
599 free(img);
600}
601
602// for a w x h box (canvas units): at the screen's own resolution when the GPU draws
603// (1080p: 1.5 times), but never past the picture's own: an upscale is the GPU's to filter
604image_t *image_decode_screen(const void *data, int len, int w, int h)
605{
606 float s = gfx_scale();
607 int iw, ih, n;
608 if (s != 1.0f && w > 0 && h > 0 && stbi_info_from_memory(data, len, &iw, &ih, &n) && iw > 0 && ih > 0) {
609 double sc = fmax((double)w * s / iw, (double)h * s / ih); // the cover scale, at the screen's size
610 double k = sc > 1 ? s / sc : s;
611 w = (int)(w * k + 0.5);
612 h = (int)(h * k + 0.5);
613 }
614 return image_decode(data, len, w > 0 ? w : 1, h > 0 ? h : 1);
615}
616
617image_t *image_decode(const void *data, int len, int w, int h)
618{
619 int iw, ih, n;
620 unsigned char *rgba = stbi_load_from_memory(data, len, &iw, &ih, &n, 4);
621 if (!rgba)
622 return NULL;
623 image_t *img = image_new(w, h);
624 if (!img) { stbi_image_free(rgba); return NULL; }
625
626 // cover: the largest centred crop with the box's shape
627 double sc = fmax((double)w / iw, (double)h / ih);
628 double cw_ = w / sc, ch_ = h / sc;
629 double ox = (iw - cw_) / 2, oy = (ih - ch_) / 2;
630 for (int j = 0; j < h; j++) {
631 // box filter over the source pixels behind each output pixel
632 int sy0 = (int)(oy + j / sc), sy1 = (int)(oy + (j + 1) / sc);
633 if (sy1 <= sy0) sy1 = sy0 + 1;
634 if (sy1 > ih) sy1 = ih;
635 if (sy0 >= ih) sy0 = ih - 1;
636 for (int i = 0; i < w; i++) {
637 int sx0 = (int)(ox + i / sc), sx1 = (int)(ox + (i + 1) / sc);
638 if (sx1 <= sx0) sx1 = sx0 + 1;
639 if (sx1 > iw) sx1 = iw;
640 if (sx0 >= iw) sx0 = iw - 1;
641 unsigned r = 0, g = 0, b = 0, a = 0, cnt = 0;
642 for (int yy = sy0; yy < sy1; yy++) {
643 const unsigned char *p = rgba + ((size_t)yy * iw + sx0) * 4;
644 for (int xx = sx0; xx < sx1; xx++, p += 4) {
645 r += p[0]; g += p[1]; b += p[2]; a += p[3]; cnt++;
646 }
647 }
648 if (!cnt) cnt = 1;
649 img->px[j * w + i] = argb(a / cnt, r / cnt, g / cnt, b / cnt);
650 }
651 }
652 stbi_image_free(rgba);
653 img->opaque = 1;
654 for (int i = 0; i < w * h; i++)
655 if ((img->px[i] >> 24) != 255) { img->opaque = 0; break; }
656 return img;
657}
658
659uint32_t image_average(const image_t *img)
660{
661 unsigned long long r = 0, g = 0, b = 0, n = 0;
662 if (!img) return 0xff202020;
663 for (int i = 0; i < img->w * img->h; i += 7) {
664 uint32_t p = img->px[i];
665 r += (p >> 16) & 255; g += (p >> 8) & 255; b += p & 255; n++;
666 }
667 if (!n) return 0xff202020;
668 return argb(255, (int)(r / n), (int)(g / n), (int)(b / n));
669}
670
671/*
672==================
673gfx_image_part: part of an image, its colour multiplied by a vertical blend
674==================
675*/
676static inline uint32_t Mul_C(uint32_t a, uint32_t b) // per channel a * b / 255
677{
678 uint32_t r = 0;
679 for (int sh = 0; sh < 32; sh += 8) r |= ((((a >> sh) & 255) * ((b >> sh) & 255) + 127) / 255) << sh;
680 return r;
681}
682
683void gfx_image_part(const image_t *img, int sx, int sy, int sw, int sh, int x, int y, int w, int h,
684 uint32_t top, uint32_t bottom)
685{
686 if (!img || sw <= 0 || sh <= 0 || w <= 0 || h <= 0) return;
687 if (gpu_on) { G_Image_Part(img, sx, sy, sw, sh, x, y, w, h, top, bottom); return; }
688 int plain = top == 0xffffffff && bottom == 0xffffffff && sw == w && sh == h;
689 for (int j = 0; j < h; j++) {
690 int yy = y + j;
691 if (yy < cly0 || yy >= cly1) continue;
692 int sj = sy + (sh == h ? j : (int)(((long long)j * 2 + 1) * sh / (2 * h)));
693 if (sj < 0 || sj >= img->h) continue;
694 const uint32_t *row = img->px + sj * img->w;
695 int x0 = x < clx0 ? clx0 : x, x1 = x + w > clx1 ? clx1 : x + w;
696 if (x0 >= x1) continue;
697 if (plain && img->opaque && sx + (x0 - x) >= 0 && sx + (x1 - x) <= img->w) {
698 memcpy(&cv[yy * cw + x0], &row[sx + (x0 - x)], (size_t)(x1 - x0) * 4);
699 continue;
700 }
701 // the row's multiplier: top..bottom at pixel centres
702 uint32_t m = lerp_c(top, bottom, (int)(((long long)j * 2 + 1) * 256 / (2 * h)));
703 for (int i = x0; i < x1; i++) {
704 int si = sx + (sw == w ? i - x : (int)(((long long)(i - x) * 2 + 1) * sw / (2 * w)));
705 if (si < 0 || si >= img->w) continue;
706 uint32_t s = Mul_C(row[si], m);
707 uint32_t *p = &cv[yy * cw + i];
708 *p = blend(*p, s, s >> 24);
709 }
710 }
711}
712
713/*
714==================
715The GPU path: the calls above as a list of quads (gpu.h)
716
717Most of the UI uses one texture, the atlas: a white block (plain colour),
718then glyphs, icons and rounded corners, each made once on the CPU (at the
719display's scale) and kept. So text, panels and icons go in one batch;
720only cover art and other pictures add batches of their own.
721==================
722*/
723#define ATLAS_W 2048
724#define ATLAS_H 2048
725#define MAX_QUADS 24576
726#define MAX_BATCHES 4096
727#define MAP_SIZE 16384 // atlas entries (a power of two)
728
729static gquad_t gq[MAX_QUADS];
730static gbatch_t gb[MAX_BATCHES];
731static int nq, nb, g_dropped;
732static float g_scale = 1.0f;
733static gtex_t atlas;
734static int atlas_ok, shelf_x, shelf_y, shelf_h, atlas_reset_due, want_redraw;
735static float WU, WV; // the white block's middle
736static float g_ox, g_oy; // gfx_offset
737
738enum { K_MASK = 1, K_CORNER, K_ICON };
739typedef struct { const void *key; int kind, a, b; int x, y, w, h; } aslot_t;
740static aslot_t amap[MAP_SIZE];
741static int acount;
742
743typedef struct { gtex_t t; int rev; } gimg_t;
744static gtex_t *freeing; // textures let go of: freed once the GPU is done with the frame using them
745static int nfreeing, capfreeing;
746
747int gfx_gpu(void) { return gpu_on; }
748void gfx_offset(float dx, float dy) { g_ox = dx; g_oy = dy; }
749float gfx_scale(void) { return gpu_on ? g_scale : 1.0f; }
750int gfx_wants_redraw(void) { int w = want_redraw; want_redraw = 0; return w; }
751
752static void Atlas_Reset(void)
753{
754 memset(amap, 0, sizeof(amap));
755 acount = 0;
756 // the white block: 4x4 in the corner
757 uint32_t *px = atlas.px;
758 int stride = atlas.pitch / 4;
759 for (int j = 0; j < 4; j++)
760 for (int i = 0; i < 4; i++) px[j * stride + i] = 0xffffffff;
761 shelf_x = 5; shelf_y = 0; shelf_h = 5; // (after the block, and a pixel of space)
762 atlas_reset_due = 0;
763}
764
765void gfx_gpu_enable(float scale)
766{
767 if (!gpu_tex_alloc(&atlas, ATLAS_W, ATLAS_H)) return;
768 memset(atlas.px, 0, (size_t)atlas.pitch * ATLAS_H);
769 atlas_ok = 1;
770 WU = 2.0f / ATLAS_W;
771 WV = 2.0f / ATLAS_H;
772 Atlas_Reset();
773 g_scale = scale > 0 ? scale : 1.0f;
774 gpu_on = 1;
775}
776
777void gfx_masks_reset(void) { if (gpu_on) atlas_reset_due = 1; }
778
779void gfx_frame_begin(void)
780{
781 if (!gpu_on) return;
782 gpu_frame_begin(); // the last frame's done with everything
783 for (int i = 0; i < nfreeing; i++) gpu_tex_free(&freeing[i]);
784 nfreeing = 0;
785 if (atlas_reset_due) Atlas_Reset();
786 nq = nb = 0;
787 g_dropped = 0;
788 g_ox = g_oy = 0;
789}
790
791void gfx_frame_end(void)
792{
793 if (!gpu_on) return;
794 gpu_frame_submit(gq, nq, gb, nb);
795}
796
797static void Free_Later(const gtex_t *t)
798{
799 if (nfreeing == capfreeing) {
800 int nc = capfreeing ? capfreeing * 2 : 32;
801 gtex_t *n = realloc(freeing, nc * sizeof(gtex_t));
802 if (!n) { gpu_tex_free((gtex_t *)t); return; } // (better than a leak)
803 freeing = n;
804 capfreeing = nc;
805 }
806 freeing[nfreeing++] = *t;
807}
808
809static void G_Image_Forget(image_t *img)
810{
811 gimg_t *g = img->gpu;
812 if (!g) return;
813 Free_Later(&g->t);
814 free(g);
815 img->gpu = NULL;
816}
817
818// the GPU's copy of img, made or brought up to date
819static const gtex_t *Tex_Of(const image_t *img_c)
820{
821 image_t *img = (image_t *)img_c; // (the copy is a cache: drawing doesn't change the image)
822 gimg_t *g = img->gpu;
823 if (g && (g->t.w != img->w || g->t.h != img->h)) { G_Image_Forget(img); g = NULL; }
824 if (!g) {
825 g = calloc(1, sizeof(*g));
826 if (!g) return NULL;
827 if (!gpu_tex_alloc(&g->t, img->w, img->h)) { free(g); want_redraw = 1; return NULL; }
828 g->rev = img->rev - 1;
829 img->gpu = g;
830 }
831 if (g->rev != img->rev) {
832 if (g->t.pitch == img->w * 4) memcpy(g->t.px, img->px, (size_t)img->w * img->h * 4);
833 else for (int j = 0; j < img->h; j++) memcpy((uint8_t *)g->t.px + (size_t)j * g->t.pitch, img->px + (size_t)j * img->w, (size_t)img->w * 4);
834 g->rev = img->rev;
835 }
836 return &g->t;
837}
838
839// one quad; a new batch when the textures or the clip change
840static void Quad(float x0, float y0, float x1, float y1, const gtex_t *t0, float u0, float v0, float u1, float v1,
841 const gtex_t *t1, float s0, float tt0, float s1, float tt1, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3)
842{
843 if (x1 <= x0 || y1 <= y0) return;
844 x0 += g_ox; x1 += g_ox; y0 += g_oy; y1 += g_oy;
845 if (x1 <= clx0 || x0 >= clx1 || y1 <= cly0 || y0 >= cly1 || clx1 <= clx0 || cly1 <= cly0) return;
846 if (!((c0 | c1 | c2 | c3) >> 24)) return;
847 if (nq >= MAX_QUADS) { g_dropped++; want_redraw = 1; return; }
848 gbatch_t *b = nb ? &gb[nb - 1] : NULL;
849 if (!b || b->tex0 != t0 || b->tex1 != t1 || b->sx != clx0 || b->sy != cly0 || b->sw != clx1 - clx0 || b->sh != cly1 - cly0) {
850 if (nb >= MAX_BATCHES) { g_dropped++; want_redraw = 1; return; }
851 b = &gb[nb++];
852 b->first = nq;
853 b->count = 0;
854 b->tex0 = t0;
855 b->tex1 = t1;
856 b->sx = clx0; b->sy = cly0; b->sw = clx1 - clx0; b->sh = cly1 - cly0;
857 }
858 gquad_t *q = &gq[nq++];
859 q->x0 = x0; q->y0 = y0; q->x1 = x1; q->y1 = y1;
860 q->u0 = u0; q->v0 = v0; q->u1 = u1; q->v1 = v1;
861 q->s0 = s0; q->t0 = tt0; q->s1 = s1; q->t1 = tt1;
862 q->c[0] = c0; q->c[1] = c1; q->c[2] = c2; q->c[3] = c3;
863 b->count++;
864}
865
866static void G_Rect(float x, float y, float w, float h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3)
867{
868 if (!atlas_ok) return;
869 Quad(x, y, x + w, y + h, &atlas, WU, WV, WU, WV, &atlas, WU, WV, WU, WV, c0, c1, c2, c3);
870}
871
872/*
873The atlas: shelves left to right, a row at a time. Each entry has a
874border a pixel wide that repeats its edge, so filtering at the edge reads
875the entry's own pixels (as a texture of its own clamped at the edge would),
876never a neighbour or an empty gap: a corner's straight edges meet the band
877beside it without a seam, wherever the chip puts its pixel centres.
878*/
879static aslot_t *Find(const void *key, int kind, int a, int b, int *fresh)
880{
881 uint32_t h = (uint32_t)(uintptr_t)key * 2654435761u ^ (uint32_t)kind * 40503u ^ (uint32_t)a * 9176u ^ (uint32_t)b * 31u;
882 for (int probe = 0; probe < MAP_SIZE; probe++) {
883 aslot_t *s = &amap[(h + probe) & (MAP_SIZE - 1)];
884 if (!s->kind) {
885 if (acount >= MAP_SIZE * 3 / 4) { atlas_reset_due = 1; want_redraw = 1; return NULL; }
886 s->key = key; s->kind = kind; s->a = a; s->b = b;
887 s->w = 0;
888 acount++;
889 *fresh = 1;
890 return s;
891 }
892 if (s->key == key && s->kind == kind && s->a == a && s->b == b) { *fresh = 0; return s; }
893 }
894 return NULL;
895}
896
897// room for w x h; 0 when the atlas is full (it's emptied before the next frame)
898static int Place(aslot_t *s, int w, int h)
899{
900 int bw = w + 2, bh = h + 2; // with the border
901 if (shelf_x + bw > ATLAS_W) { shelf_y += shelf_h; shelf_x = 0; shelf_h = 0; }
902 if (shelf_y + bh > ATLAS_H || bw > ATLAS_W) {
903 atlas_reset_due = 1;
904 want_redraw = 1;
905 s->kind = 0; // (not kept: the slot stays free)
906 acount--;
907 return 0;
908 }
909 s->x = shelf_x + 1; s->y = shelf_y + 1; s->w = w; s->h = h;
910 shelf_x += bw;
911 if (bh > shelf_h) shelf_h = bh;
912 return 1;
913}
914
915// coverage (0..255) into the atlas as white with that alpha
916static void Put_Coverage(const aslot_t *s, const uint8_t *cov, int pitch)
917{
918 uint32_t *px = atlas.px;
919 int stride = atlas.pitch / 4;
920 // the entry and its border (the nearest edge pixel again)
921 for (int j = -1; j <= s->h; j++) {
922 uint32_t *d = &px[(s->y + j) * stride + s->x];
923 const uint8_t *m = cov + (j < 0 ? 0 : j >= s->h ? s->h - 1 : j) * pitch;
924 for (int i = -1; i <= s->w; i++) d[i] = ((uint32_t)m[i < 0 ? 0 : i >= s->w ? s->w - 1 : i] << 24) | 0xffffff;
925 }
926}
927
928static void Atlas_UV(const aslot_t *s, float *u0, float *v0, float *u1, float *v1)
929{
930 *u0 = (float)s->x / ATLAS_W; *v0 = (float)s->y / ATLAS_H;
931 *u1 = (float)(s->x + s->w) / ATLAS_W; *v1 = (float)(s->y + s->h) / ATLAS_H;
932}
933
934static void G_Mask(const uint8_t *mask, int mw, int mh, int pitch, float x, float y, float dw, float dh, uint32_t c)
935{
936 if (!atlas_ok || mw <= 0 || mh <= 0 || !(c >> 24)) return;
937 int fresh;
938 aslot_t *s = Find(mask, K_MASK, mw, mh, &fresh);
939 if (!s) return;
940 if (fresh) {
941 if (!Place(s, mw, mh)) return;
942 Put_Coverage(s, mask, pitch);
943 }
944 float u0, v0, u1, v1;
945 Atlas_UV(s, &u0, &v0, &u1, &v1);
946 Quad(x, y, x + dw, y + dh, &atlas, WU, WV, WU, WV, &atlas, u0, v0, u1, v1, c, c, c, c);
947}
948
949void gfx_mask_scaled(const uint8_t *mask, int mw, int mh, int pitch, float x, float y, uint32_t c)
950{
951 if (gpu_on) { G_Mask(mask, mw, mh, pitch, x, y, mw / g_scale, mh / g_scale, c); return; }
952 gfx_mask(mask, mw, mh, pitch, (int)floorf(x + 0.5f), (int)floorf(y + 0.5f), c);
953}
954
955// a rounded corner, radius r canvas units (made at the display's scale): the top-left one
956static aslot_t *Corner(int r)
957{
958 int rp = (int)ceilf(r * g_scale);
959 if (rp < 1) rp = 1;
960 int fresh;
961 aslot_t *s = Find(NULL, K_CORNER, rp, 0, &fresh);
962 if (!s) return NULL;
963 if (fresh) {
964 if (!Place(s, rp, rp)) return NULL;
965 uint8_t *cov = malloc(rp * rp);
966 if (!cov) { s->kind = 0; acount--; return NULL; }
967 for (int j = 0; j < rp; j++)
968 for (int i = 0; i < rp; i++) cov[j * rp + i] = (uint8_t)corner_cov(i + 0.5f, j + 0.5f, rp, rp, rp);
969 Put_Coverage(s, cov, rp);
970 free(cov);
971 }
972 return s;
973}
974
975// the four corners of a w x h box at x, y, radius r: tex0 (t0, its own coordinates across
976// the box) times the corner shape
977static void Corners(int x, int y, int w, int h, int r, const gtex_t *t0, float u0, float v0, float u1, float v1, uint32_t c)
978{
979 aslot_t *s = Corner(r);
980 if (!s) return;
981 float a0, b0, a1, b1;
982 Atlas_UV(s, &a0, &b0, &a1, &b1);
983 float du = (u1 - u0) * r / w, dv = (v1 - v0) * r / h;
984 // top left, top right (mirrored across), bottom left (mirrored down), bottom right
985 Quad(x, y, x + r, y + r, t0, u0, v0, u0 + du, v0 + dv, &atlas, a0, b0, a1, b1, c, c, c, c);
986 Quad(x + w - r, y, x + w, y + r, t0, u1 - du, v0, u1, v0 + dv, &atlas, a1, b0, a0, b1, c, c, c, c);
987 Quad(x, y + h - r, x + r, y + h, t0, u0, v1 - dv, u0 + du, v1, &atlas, a0, b1, a1, b0, c, c, c, c);
988 Quad(x + w - r, y + h - r, x + w, y + h, t0, u1 - du, v1 - dv, u1, v1, &atlas, a1, b1, a0, b0, c, c, c, c);
989}
990
991// a box with rounded corners, tex0 across it (u0..u1, v0..v1) times c: the bands between
992// the corners, then the corners
993static void Rounded(int x, int y, int w, int h, int r, const gtex_t *t0, float u0, float v0, float u1, float v1, uint32_t c)
994{
995 if (r * 2 > w) r = w / 2;
996 if (r * 2 > h) r = h / 2;
997 if (r <= 0) { Quad(x, y, x + w, y + h, t0, u0, v0, u1, v1, &atlas, WU, WV, WU, WV, c, c, c, c); return; }
998 float du = (u1 - u0) * r / w, dv = (v1 - v0) * r / h;
999 Quad(x, y + r, x + w, y + h - r, t0, u0, v0 + dv, u1, v1 - dv, &atlas, WU, WV, WU, WV, c, c, c, c);
1000 Quad(x + r, y, x + w - r, y + r, t0, u0 + du, v0, u1 - du, v0 + dv, &atlas, WU, WV, WU, WV, c, c, c, c);
1001 Quad(x + r, y + h - r, x + w - r, y + h, t0, u0 + du, v1 - dv, u1 - du, v1, &atlas, WU, WV, WU, WV, c, c, c, c);
1002 Corners(x, y, w, h, r, t0, u0, v0, u1, v1, c);
1003}
1004
1005static void G_Round_Rect(int x, int y, int w, int h, int r, uint32_t c)
1006{
1007 if (!atlas_ok || !(c >> 24) || w <= 0 || h <= 0) return;
1008 Rounded(x, y, w, h, r, &atlas, WU, WV, WU, WV, c);
1009}
1010
1011static void G_Image(const image_t *img, int x, int y, int w, int h, int radius, int alpha)
1012{
1013 if (!atlas_ok || alpha <= 0) return;
1014 const gtex_t *t = Tex_Of(img);
1015 if (!t) return;
1016 uint32_t c = ((uint32_t)(alpha > 255 ? 255 : alpha) << 24) | 0xffffff;
1017 if (img_flip) Rounded(x, y, w, h, radius, t, 1, 0, 0, 1, c); // (u from 1 to 0: turned round)
1018 else Rounded(x, y, w, h, radius, t, 0, 0, 1, 1, c);
1019}
1020
1021static void G_Image_Part(const image_t *img, int sx, int sy, int sw, int sh, int x, int y, int w, int h, uint32_t top, uint32_t bottom)
1022{
1023 if (!atlas_ok) return;
1024 const gtex_t *t = Tex_Of(img);
1025 if (!t) return;
1026 Quad(x, y, x + w, y + h, t, (float)sx / img->w, (float)sy / img->h, (float)(sx + sw) / img->w, (float)(sy + sh) / img->h,
1027 &atlas, WU, WV, WU, WV, top, top, bottom, bottom);
1028}
1029
1030// a strip's frame as a stencil (its alpha), in one colour
1031static void G_Tint(const image_t *img, int sx, int sw, int x, int y, uint32_t c)
1032{
1033 if (!atlas_ok) return;
1034 const gtex_t *t = Tex_Of(img);
1035 if (!t) return;
1036 Quad(x, y, x + sw, y + img->h, &atlas, WU, WV, WU, WV, t, (float)sx / img->w, 0, (float)(sx + sw) / img->w, 1, c, c, c, c);
1037}
1038
1039// an icon: drawn once in software (white, at the display's scale) and kept in the atlas
1040static int G_Icon(int icon, int x, int y, int sz, uint32_t c)
1041{
1042 if (!atlas_ok) return 0;
1043 if (!(c >> 24)) return 1;
1044 int sp = (int)ceilf(sz * g_scale), pad = 2, box = sp + pad * 2;
1045 int fresh;
1046 aslot_t *s = Find(NULL, K_ICON, icon, sp, &fresh);
1047 if (!s) return 1;
1048 if (fresh) {
1049 if (!Place(s, box, box)) return 1;
1050 uint32_t *tmp = calloc((size_t)box * box, 4);
1051 uint8_t *cov = malloc((size_t)box * box);
1052 if (!tmp || !cov) { free(tmp); free(cov); s->kind = 0; acount--; return 0; }
1053 // the software drawing, on a small black canvas of our own
1054 uint32_t *ocv = cv;
1055 int ocw = cw, och = ch, ox0 = clx0, oy0 = cly0, ox1 = clx1, oy1 = cly1;
1056 cv = tmp; cw = box; ch = box;
1057 clx0 = cly0 = 0; clx1 = cly1 = box;
1058 gpu_on = 0;
1059 gfx_icon(icon, pad, pad, sp, 0xffffffff);
1060 gpu_on = 1;
1061 cv = ocv; cw = ocw; ch = och; clx0 = ox0; cly0 = oy0; clx1 = ox1; cly1 = oy1;
1062 for (int i = 0; i < box * box; i++) cov[i] = (uint8_t)((tmp[i] >> 8) & 255); // white over black: any channel is the coverage
1063 Put_Coverage(s, cov, box);
1064 free(tmp);
1065 free(cov);
1066 }
1067 float u0, v0, u1, v1;
1068 Atlas_UV(s, &u0, &v0, &u1, &v1);
1069 float p = pad / g_scale;
1070 Quad(x - p, y - p, x - p + box / g_scale, y - p + box / g_scale, &atlas, WU, WV, WU, WV, &atlas, u0, v0, u1, v1, c, c, c, c);
1071 return 1;
1072}
1073
1074// the grained gradient behind pages: the cached picture, as a texture
1075static void G_Noise_Gradient(int x, int y, int w, int h, int slot)
1076{
1077 static image_t pics[2];
1078 image_t *p = &pics[slot];
1079 if (p->px != grad_cache[slot].px || p->w != w || p->h != h || grad_cache[slot].fresh) {
1080 if (p->px != grad_cache[slot].px || p->w != w || p->h != h) G_Image_Forget(p);
1081 p->px = grad_cache[slot].px;
1082 p->w = w; p->h = h;
1083 p->opaque = 1;
1084 p->rev++;
1085 grad_cache[slot].fresh = 0;
1086 }
1087 G_Image(p, x, y, w, h, 0, 255);
1088}
1089
1090/*
1091==================
1092Right to left (Arabic): with the mirror on, the calls above are drawn mirrored across the
1093canvas: a box at x is drawn at the canvas's width - x - its width. Text isn't mirrored here
1094(font.c places each line where it goes, and its letters read the right way); pictures keep
1095their way round unless drawn with gfx_image_flip (the UI's card art).
1096==================
1097*/
1098static int mirror;
1099void gfx_mirror(int on) { mirror = on; }
1100int gfx_mirrored(void) { return mirror; }
1101#define MX(x, w) (mirror ? cw - (x) - (w) : (x))
1102
1103void gfx_clip(int x, int y, int w, int h) { Raw_gfx_clip(MX(x, w), y, w, h); }
1104void gfx_clip_get(int *x, int *y, int *w, int *h)
1105{
1106 Raw_gfx_clip_get(x, y, w, h);
1107 if (mirror) *x = cw - *x - *w;
1108}
1109void gfx_rect(int x, int y, int w, int h, uint32_t c) { Raw_gfx_rect(MX(x, w), y, w, h, c); }
1110void gfx_round_rect(int x, int y, int w, int h, int r, uint32_t c) { Raw_gfx_round_rect(MX(x, w), y, w, h, r, c); }
1111void gfx_circle(int cx, int cy, int r, uint32_t c) { Raw_gfx_circle(mirror ? cw - cx : cx, cy, r, c); }
1112void gfx_image(const image_t *img, int x, int y, int w, int h, int radius, int alpha)
1113{
1114 Raw_gfx_image(img, MX(x, w), y, w, h, radius, alpha);
1115}
1116void gfx_image_flip(const image_t *img, int x, int y, int w, int h, int radius, int alpha)
1117{
1118 img_flip = mirror;
1119 Raw_gfx_image(img, MX(x, w), y, w, h, radius, alpha);
1120 img_flip = 0;
1121}