1use crate::chart::{Prim, Rgba};
6
7const MAX_PIXELS: u64 = 64 * 1024 * 1024;
9const SUB: usize = 4;
10
11type Path = Vec<Vec<[f32; 2]>>;
12
13struct Canvas {
14 w: usize,
15 h: usize,
16 buf: Vec<u8>,
17 acc: Vec<f32>,
18}
19
20impl Canvas {
21 fn blend(&mut self, x: usize, y: usize, c: Rgba, cov: f32) {
22 let a = (cov.clamp(0.0, 1.0) * c[3] as f32 / 255.0).clamp(0.0, 1.0);
23 if a <= 0.0 {
24 return;
25 }
26 let i = (y * self.w + x) * 4;
27 if let Some(px) = self.buf.get_mut(i..i + 4) {
28 let da = px[3] as f32 / 255.0;
29 let oa = a + da * (1.0 - a);
30 if oa <= 0.0 {
31 return;
32 }
33 for k in 0..3 {
34 let s = c[k] as f32;
35 let d = px[k] as f32;
36 px[k] = ((s * a + d * da * (1.0 - a)) / oa).round().clamp(0.0, 255.0) as u8;
37 }
38 px[3] = (oa * 255.0).round().clamp(0.0, 255.0) as u8;
39 }
40 }
41
42 fn fill(&mut self, path: &Path, color: Rgba) {
44 let mut edges: Vec<[f32; 4]> = Vec::new();
45 let (mut minx, mut miny, mut maxx, mut maxy) = (f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY);
46 for c in path {
47 if c.len() < 3 || !c.iter().all(|p| p[0].is_finite() && p[1].is_finite()) {
48 continue;
49 }
50 for (k, p) in c.iter().enumerate() {
51 let q = c.get((k + 1) % c.len()).copied().unwrap_or(*p);
52 minx = minx.min(p[0]);
53 maxx = maxx.max(p[0]);
54 miny = miny.min(p[1]);
55 maxy = maxy.max(p[1]);
56 if p[1] != q[1] {
57 edges.push([p[0], p[1], q[0], q[1]]);
58 }
59 }
60 }
61 if edges.is_empty() {
62 return;
63 }
64 let x0 = minx.floor().max(0.0) as usize;
65 let x1 = (maxx.ceil().max(0.0) as usize).min(self.w);
66 let y0 = miny.floor().max(0.0) as usize;
67 let y1 = (maxy.ceil().max(0.0) as usize).min(self.h);
68 if x0 >= x1 || y0 >= y1 {
69 return;
70 }
71 let bw = x1 - x0;
72 if self.acc.len() < bw + 1 {
73 self.acc.resize(bw + 1, 0.0);
74 }
75 let mut xs: Vec<f32> = Vec::new();
76 for py in y0..y1 {
77 for v in self.acc.iter_mut().take(bw) {
78 *v = 0.0;
79 }
80 let mut any = false;
81 for s in 0..SUB {
82 let sy = py as f32 + (s as f32 + 0.5) / SUB as f32;
83 xs.clear();
84 for e in &edges {
85 let (ya, yb) = if e[1] < e[3] { (e[1], e[3]) } else { (e[3], e[1]) };
86 if sy >= ya && sy < yb {
87 xs.push(e[0] + (sy - e[1]) * (e[2] - e[0]) / (e[3] - e[1]));
88 }
89 }
90 xs.sort_by(f32::total_cmp);
91 for pair in xs.as_chunks::<2>().0 {
92 let (a, b) = (pair[0].max(x0 as f32), pair[1].min(x1 as f32));
93 if b <= a {
94 continue;
95 }
96 any = true;
97 let ia = a.floor() as usize;
98 let ib = (b.ceil() as usize).min(x1);
99 for px in ia..ib {
100 let l = a.max(px as f32);
101 let r = b.min(px as f32 + 1.0);
102 if r > l
103 && let Some(v) = self.acc.get_mut(px - x0)
104 {
105 *v += (r - l) / SUB as f32;
106 }
107 }
108 }
109 }
110 if !any {
111 continue;
112 }
113 for px in x0..x1 {
114 let cov = self.acc.get(px - x0).copied().unwrap_or(0.0);
115 if cov > 0.0 {
116 self.blend(px, py, color, cov);
117 }
118 }
119 }
120 }
121}
122
123fn arc(cx: f32, cy: f32, r: f32, a0: f32, a1: f32) -> Vec<[f32; 2]> {
124 let sweep = a1 - a0;
125 let n = ((r.abs() * sweep.abs() / 2.0).ceil() as usize).clamp(4, 512);
126 (0..=n)
127 .map(|k| {
128 let a = a0 + sweep * k as f32 / n as f32;
129 [cx + r * a.sin(), cy - r * a.cos()]
130 })
131 .collect()
132}
133
134fn circle(cx: f32, cy: f32, r: f32) -> Vec<[f32; 2]> {
135 let mut c = arc(cx, cy, r, 0.0, std::f32::consts::TAU);
136 c.pop();
137 c
138}
139
140fn rounded_rect(x: f32, y: f32, w: f32, h: f32, r: f32) -> Vec<[f32; 2]> {
141 let r = r.clamp(0.0, (w.min(h) / 2.0).max(0.0));
143 if r < 0.5 {
144 return vec![[x, y], [x + w, y], [x + w, y + h], [x, y + h]];
145 }
146 use std::f32::consts::{FRAC_PI_2, PI};
147 let mut v = Vec::new();
148 v.extend(arc(x + w - r, y + r, r, 0.0, FRAC_PI_2));
149 v.extend(arc(x + w - r, y + h - r, r, FRAC_PI_2, PI));
150 v.extend(arc(x + r, y + h - r, r, PI, PI + FRAC_PI_2));
151 v.extend(arc(x + r, y + r, r, PI + FRAC_PI_2, 2.0 * PI));
152 v
153}
154
155fn stroke_paths(pts: &[[f32; 2]], width: f32, dash: bool, closed: bool) -> Vec<Path> {
157 let width = width.clamp(1.0, 50.0);
158 let mut segs: Vec<([f32; 2], [f32; 2])> = pts.windows(2).map(|w| (w[0], w[1])).collect();
159 if closed && let (Some(a), Some(b)) = (pts.last(), pts.first()) {
160 segs.push((*a, *b));
161 }
162 if dash {
163 let (on, off) = (width * 4.0, width * 3.0);
168 let mut out = Vec::new();
169 let mut budget = 65_536usize;
170 for (a, b) in segs {
171 if budget == 0 {
172 return vec![vec![pts.to_vec()]];
173 }
174 let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
175 let len = (dx * dx + dy * dy).sqrt();
176 if !(len.is_finite() && len > 0.0) {
177 continue;
178 }
179 let mut t = 0.0;
180 while t < len && budget > 0 {
181 let e = (t + on).min(len);
182 out.push(([a[0] + dx * t / len, a[1] + dy * t / len], [a[0] + dx * e / len, a[1] + dy * e / len]));
183 t += on + off;
184 budget -= 1;
185 }
186 }
187 segs = out;
188 }
189 let hw = width / 2.0;
190 segs.into_iter()
191 .filter_map(|(a, b)| {
192 let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
193 let len = (dx * dx + dy * dy).sqrt();
194 if !(len.is_finite() && len > 1e-6) {
195 return None;
196 }
197 let (nx, ny) = (-dy / len * hw, dx / len * hw);
198 Some(vec![vec![[a[0] + nx, a[1] + ny], [b[0] + nx, b[1] + ny], [b[0] - nx, b[1] - ny], [a[0] - nx, a[1] - ny]]])
199 })
200 .collect()
201}
202
203pub fn rasterize(prims: &[Prim], w: u32, h: u32, background: Rgba) -> Vec<u8> {
206 let px = (w as u64).saturating_mul(h as u64);
207 if w == 0 || h == 0 || px > MAX_PIXELS {
208 return vec![];
209 }
210 let (w, h) = (w as usize, h as usize);
211 let mut buf = Vec::with_capacity(w * h * 4);
212 for _ in 0..w * h {
213 buf.extend_from_slice(&background);
214 }
215 let mut cv = Canvas { w, h, buf, acc: Vec::new() };
216 for p in prims {
217 match p {
218 Prim::Rect { x, y, w, h, fill, stroke, radius } => {
219 if let Some(f) = fill {
220 cv.fill(&vec![rounded_rect(*x, *y, *w, *h, *radius)], *f);
221 }
222 if let Some((c, sw)) = stroke {
223 let sw = sw.clamp(0.5, 50.0);
224 let outer = rounded_rect(x - sw / 2.0, y - sw / 2.0, w + sw, h + sw, radius + sw / 2.0);
225 let inner = rounded_rect(x + sw / 2.0, y + sw / 2.0, (w - sw).max(0.0), (h - sw).max(0.0), (radius - sw / 2.0).max(0.0));
226 cv.fill(&vec![outer, inner], *c);
227 }
228 }
229 Prim::Line { pts, color, width, dash } => {
230 for path in stroke_paths(pts, *width, *dash, false) {
231 cv.fill(&path, *color);
232 }
233 }
234 Prim::Polygon { pts, fill, stroke } => {
235 cv.fill(&vec![pts.clone()], *fill);
236 if let Some((c, sw)) = stroke {
237 for path in stroke_paths(pts, *sw, false, true) {
238 cv.fill(&path, *c);
239 }
240 }
241 }
242 Prim::Wedge { cx, cy, r_outer, r_inner, a0, a1, fill, stroke } => {
243 let mut c = arc(*cx, *cy, *r_outer, *a0, *a1);
244 if *r_inner > 0.0 {
245 let mut inner = arc(*cx, *cy, *r_inner, *a0, *a1);
246 inner.reverse();
247 c.extend(inner);
248 } else {
249 c.push([*cx, *cy]);
250 }
251 cv.fill(&vec![c.clone()], *fill);
252 if let Some((sc, sw)) = stroke {
253 for path in stroke_paths(&c, *sw, false, true) {
254 cv.fill(&path, *sc);
255 }
256 }
257 }
258 Prim::Circle { cx, cy, r, fill, stroke } => {
259 if let Some(f) = fill {
260 cv.fill(&vec![circle(*cx, *cy, *r)], *f);
261 }
262 if let Some((c, sw)) = stroke {
263 let sw = sw.clamp(0.5, 50.0);
264 cv.fill(&vec![circle(*cx, *cy, r + sw / 2.0), circle(*cx, *cy, (r - sw / 2.0).max(0.0))], *c);
265 }
266 }
267 Prim::Text { .. } => {}
268 }
269 }
270 cv.buf
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn dashed_geometry_stays_within_the_dash_budget() {
279 let prims: Vec<Prim> =
283 (0..40).map(|i| Prim::Line { pts: vec![[0.0, i as f32], [900.0, i as f32]], color: [0, 0, 0, 255], width: 1.0, dash: true }).collect();
284 let img = rasterize(&prims, 2, 2, [255, 255, 255, 255]);
285 assert_eq!(img.len(), 2 * 2 * 4);
286 assert!(stroke_paths(&[[0.0, 0.0], [1e6, 0.0]], 1.0, true, false).len() <= 65_536);
288 let prim = [Prim::Line { pts: vec![[0.0, 0.0], [10.0, 0.0]], color: [0, 0, 0, 255], width: 1.0, dash: true }];
289 assert_eq!(rasterize(&prim, 1, 1, [0, 0, 0, 255]).len(), 4);
290 }
291}