1use super::font::{GLYPH_ADVANCE, GLYPH_WIDTH, glyph_or_box};
4use super::{Area, Shapes, clip_segment, unit};
5use crate::colour::Rgba;
6use crate::surface::{Canvas, Pixels, Surface};
7
8fn coverage(v: f32) -> u8 {
10 (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
11}
12
13fn rounded_box_distance(px: f32, py: f32, bx: f32, by: f32, r: f32) -> f32 {
16 let qx = libm::fabsf(px) - (bx - r);
17 let qy = libm::fabsf(py) - (by - r);
18 let outside = libm::sqrtf(qx.max(0.0) * qx.max(0.0) + qy.max(0.0) * qy.max(0.0));
19 outside + qx.max(qy).min(0.0) - r
20}
21
22fn shade(
25 canvas: &mut Canvas<'_>,
26 (x0, y0, x1, y1): (i64, i64, i64, i64),
27 colour: Rgba,
28 shade: impl Fn(f32, f32) -> f32,
29) {
30 let (left, right) = (x0.max(0), x1.min(i64::from(canvas.width)));
31 let (top, bottom) = (y0.max(0), y1.min(i64::from(canvas.height)));
32 for y in top..bottom {
33 for x in left..right {
34 let amount = coverage(shade(x as f32 + 0.5, y as f32 + 0.5));
35 if amount > 0 {
36 canvas.blend(x, y, colour, amount);
37 }
38 }
39 }
40 canvas.touch(left, top, right, bottom);
41}
42
43fn rounded(canvas: &mut Canvas<'_>, area: Area, radius: u32, colour: Rgba, fill: bool) {
44 if area.w == 0 || area.h == 0 {
45 return;
46 }
47 let (bx, by) = (area.w as f32 / 2.0, area.h as f32 / 2.0);
48 let (cx, cy) = (area.left() as f32 + bx, area.top() as f32 + by);
49 let r = (radius as f32).min(bx).min(by);
50 let bounds = (area.left(), area.top(), area.right(), area.bottom());
51 shade(canvas, bounds, colour, |px, py| {
52 let d = rounded_box_distance(px - cx, py - cy, bx, by, r);
53 if fill {
54 0.5 - d
55 } else {
56 (0.5 - d).clamp(0.0, 1.0) - (-0.5 - d).clamp(0.0, 1.0)
58 }
59 });
60}
61
62fn finite(v: f32) -> f32 {
64 if v.is_nan() {
65 0.0
66 } else {
67 v.clamp(-1.0e9, 1.0e9)
68 }
69}
70
71fn disc(canvas: &mut Canvas<'_>, centre: (f32, f32), radius: f32, colour: Rgba, fill: bool) {
72 let (cx, cy) = (finite(centre.0), finite(centre.1));
73 let r = finite(radius).clamp(0.0, 1.0e6);
74 let reach = libm::ceilf(r) as i64 + 2;
75 let (x, y) = (libm::floorf(cx) as i64, libm::floorf(cy) as i64);
76 let bounds = (x - reach, y - reach, x + reach + 1, y + reach + 1);
77 shade(canvas, bounds, colour, |px, py| {
78 let d = libm::sqrtf((px - cx) * (px - cx) + (py - cy) * (py - cy)) - r;
79 if fill { 0.5 - d } else { 1.0 - libm::fabsf(d) }
80 });
81}
82
83fn wu_line(canvas: &mut Canvas<'_>, from: (i64, i64), to: (i64, i64), colour: Rgba) {
85 let ((x0, y0), (x1, y1)) = (from, to);
86 let steep = (y1 - y0).abs() > (x1 - x0).abs();
87 let (mut a0, mut b0, mut a1, mut b1) = if steep {
88 (y0, x0, y1, x1)
89 } else {
90 (x0, y0, x1, y1)
91 };
92 if a0 > a1 {
93 core::mem::swap(&mut a0, &mut a1);
94 core::mem::swap(&mut b0, &mut b1);
95 }
96 let gradient = if a1 == a0 {
97 0.0
98 } else {
99 (b1 - b0) as f32 / (a1 - a0) as f32
100 };
101 let mut b = b0 as f32;
102 for a in a0..=a1 {
103 let base = libm::floorf(b);
104 let fraction = b - base;
105 let (near, far) = (coverage(1.0 - fraction), coverage(fraction));
106 let base = base as i64;
107 if steep {
108 canvas.blend(base, a, colour, near);
109 canvas.blend(base + 1, a, colour, far);
110 } else {
111 canvas.blend(a, base, colour, near);
112 canvas.blend(a, base + 1, colour, far);
113 }
114 b += gradient;
115 }
116 let (min_x, max_x) = (x0.min(x1), x0.max(x1));
117 let (min_y, max_y) = (y0.min(y1), y0.max(y1));
118 canvas.touch(min_x - 1, min_y - 1, max_x + 2, max_y + 2);
119}
120
121#[derive(Clone, Copy, Debug)]
124pub struct Image<'a> {
125 pub width: u32,
126 pub height: u32,
127 pub rgba: &'a [u8],
128}
129
130impl<'a> Image<'a> {
131 pub const fn new(width: u32, height: u32, rgba: &'a [u8]) -> Self {
132 Self {
133 width,
134 height,
135 rgba,
136 }
137 }
138}
139
140impl Surface<Pixels> {
141 pub fn blit_clipped(&mut self, x: i32, y: i32, image: Image<'_>, clip: Area, blend: bool) {
147 self.with_canvas(|canvas| {
148 let (x0, y0) = (i64::from(x), i64::from(y));
149 let left = x0.max(clip.left()).max(0);
150 let right = (x0 + i64::from(image.width))
151 .min(clip.right())
152 .min(i64::from(canvas.width));
153 let top = y0.max(clip.top()).max(0);
154 let bottom = (y0 + i64::from(image.height))
155 .min(clip.bottom())
156 .min(i64::from(canvas.height));
157 if left >= right || top >= bottom {
158 return;
159 }
160 let stride = image.width as usize * 4;
161 let columns = (right - left) as usize;
162 let skip = (left - x0) as usize;
163 let mut last_row = top;
164 for target_y in top..bottom {
165 let start = (target_y - y0) as usize * stride + skip * 4;
166 let Some(source) = image.rgba.get(start..start + columns * 4) else {
167 break;
168 };
169 let offset = target_y as usize * canvas.width as usize + left as usize;
170 let target = &mut canvas.slot[offset..offset + columns];
171 for (word, pixel) in target.iter_mut().zip(source.chunks_exact(4)) {
172 let colour = Rgba::new(pixel[0], pixel[1], pixel[2], pixel[3]);
173 *word = if blend {
174 colour.over(Rgba::from_word(*word), 255).to_word()
175 } else {
176 colour.to_word()
177 };
178 }
179 last_row = target_y + 1;
180 }
181 canvas.touch(left, top, right, last_row);
182 });
183 }
184
185 pub fn fill_rect_at(&mut self, x: f32, y: f32, w: f32, h: f32, colour: Rgba) {
189 let (x, y) = (finite(x), finite(y));
190 let (w, h) = (finite(w).max(0.0), finite(h).max(0.0));
191 if w == 0.0 || h == 0.0 {
192 return;
193 }
194 let (right, bottom) = (x + w, y + h);
195 let bounds = (
196 libm::floorf(x) as i64,
197 libm::floorf(y) as i64,
198 libm::ceilf(right) as i64,
199 libm::ceilf(bottom) as i64,
200 );
201 self.with_canvas(|canvas| {
202 shade(canvas, bounds, colour, |px, py| {
203 let across = (px + 0.5).min(right) - (px - 0.5).max(x);
205 let down = (py + 0.5).min(bottom) - (py - 0.5).max(y);
206 across.clamp(0.0, 1.0) * down.clamp(0.0, 1.0)
207 });
208 });
209 }
210
211 pub fn label_scaled(&mut self, x: i32, y: i32, text: &str, colour: Rgba, scale: u32) -> u32 {
214 let scale = scale.max(1);
215 let advance = i64::from(GLYPH_ADVANCE) * i64::from(scale);
216 self.with_canvas(|canvas| {
217 let mut pen = i64::from(x);
218 let top = i64::from(y);
219 let size = i64::from(scale);
220 for character in text.chars() {
221 if pen >= i64::from(canvas.width) {
222 break;
223 }
224 if pen + advance > 0 {
225 for (row, bits) in glyph_or_box(character).iter().enumerate() {
226 for column in 0..GLYPH_WIDTH {
227 if bits & (1 << (GLYPH_WIDTH - 1 - column)) != 0 {
228 let gx = pen + i64::from(column) * size;
229 let gy = top + row as i64 * size;
230 canvas.fill(gx, gy, gx + size, gy + size, colour);
231 }
232 }
233 }
234 }
235 pen += advance;
236 }
237 (pen - i64::from(x)).clamp(0, i64::from(u32::MAX)) as u32
238 })
239 .unwrap_or(0)
240 }
241}
242
243impl Shapes for Surface<Pixels> {
244 type Ink = Rgba;
245
246 fn stroke_rect(&mut self, area: Area, ink: Rgba) {
247 if area.w == 0 || area.h == 0 {
248 return;
249 }
250 self.with_canvas(|canvas| {
251 let (l, t, r, b) = (area.left(), area.top(), area.right(), area.bottom());
252 canvas.fill(l, t, r, t + 1, ink);
253 if b - t > 1 {
254 canvas.fill(l, b - 1, r, b, ink);
255 }
256 canvas.fill(l, t + 1, l + 1, b - 1, ink);
257 if r - l > 1 {
258 canvas.fill(r - 1, t + 1, r, b - 1, ink);
259 }
260 });
261 }
262
263 fn fill_rect(&mut self, area: Area, ink: Rgba) {
264 self.with_canvas(|canvas| {
265 canvas.fill(area.left(), area.top(), area.right(), area.bottom(), ink);
266 });
267 }
268
269 fn line(&mut self, from: (i32, i32), to: (i32, i32), ink: Rgba) {
270 self.with_canvas(|canvas| {
271 let from = (i64::from(from.0), i64::from(from.1));
272 let to = (i64::from(to.0), i64::from(to.1));
273 if let Some((a, b)) = clip_segment(from, to, canvas.width, canvas.height) {
274 wu_line(canvas, a, b, ink);
275 }
276 });
277 }
278
279 fn bar(&mut self, area: Area, fraction: f32, fill: Rgba, track: Rgba) {
280 self.with_canvas(|canvas| {
281 let (l, t, r, b) = (area.left(), area.top(), area.right(), area.bottom());
282 canvas.fill(l, t, r, b, track);
283 let filled = unit(fraction) * area.w as f32;
284 let whole = libm::floorf(filled) as i64;
285 canvas.fill(l, t, l + whole, b, fill);
286 let partial = coverage(filled - whole as f32);
287 if partial > 0 && whole < i64::from(area.w) {
288 for y in t..b {
289 canvas.blend(l + whole, y, fill, partial);
290 }
291 }
292 });
293 }
294
295 fn rounded_rect(&mut self, area: Area, radius: u32, ink: Rgba) {
296 self.with_canvas(|canvas| rounded(canvas, area, radius, ink, false));
297 }
298
299 fn fill_rounded_rect(&mut self, area: Area, radius: u32, ink: Rgba) {
300 self.with_canvas(|canvas| rounded(canvas, area, radius, ink, true));
301 }
302
303 fn circle(&mut self, centre: (f32, f32), radius: f32, ink: Rgba) {
304 self.with_canvas(|canvas| disc(canvas, centre, radius, ink, false));
305 }
306
307 fn fill_circle(&mut self, centre: (f32, f32), radius: f32, ink: Rgba) {
308 self.with_canvas(|canvas| disc(canvas, centre, radius, ink, true));
309 }
310
311 fn label(&mut self, x: i32, y: i32, text: &str, ink: Rgba) -> u32 {
312 self.label_scaled(x, y, text, ink, 1)
313 }
314}