1use crate::geometry::math::Vec2;
6use crate::geometry::rect::Rect;
7use crate::pixels::color::Color;
8use crate::pixels::image::Image;
9use crate::pixels::mask::Mask;
10
11const MIN_STROKE_RADIUS: f32 = 0.35;
13const EDGE_INSET: f32 = 0.001;
15const SECOND_CELL_MIDDLE: f32 = 1.5;
17
18#[derive(Debug, Clone)]
20pub struct Canvas {
21 w: usize,
22 h: usize,
23 px: Vec<[f32; 4]>,
24}
25
26impl Canvas {
27 pub fn new(w: u32, h: u32) -> Canvas {
29 Canvas {
30 w: w as usize,
31 h: h as usize,
32 px: vec![[0.0; 4]; (w * h) as usize],
33 }
34 }
35
36 pub fn width(&self) -> u32 {
37 self.w as u32
38 }
39
40 pub fn height(&self) -> u32 {
41 self.h as u32
42 }
43
44 pub fn fill(&mut self, contours: &[Vec<Vec2>], color: Color) {
47 let lines = contours.iter().flat_map(|c| {
48 c.iter()
49 .zip(c.iter().cycle().skip(1))
50 .map(|(a, b)| (*a, *b))
51 });
52 let cov = coverage(self.w, self.h, lines);
53 self.paint(&cov, color);
54 }
55
56 pub fn stroke(&mut self, polylines: &[Vec<Vec2>], width: f32, color: Color) {
58 let r = (width / 2.0).max(MIN_STROKE_RADIUS);
59 let mut pieces: Vec<Vec<Vec2>> = Vec::new();
60 for line in polylines {
61 for w in line.windows(2) {
62 pieces.extend(segment_quad(w[0], w[1], r));
63 }
64 for &p in line {
65 pieces.push(circle(p, r));
66 }
67 }
68 let lines = pieces.iter().flat_map(|c| {
69 c.iter()
70 .zip(c.iter().cycle().skip(1))
71 .map(|(a, b)| (*a, *b))
72 });
73 let cov = coverage(self.w, self.h, lines);
74 self.paint(&cov, color);
75 }
76
77 pub fn image(&mut self, image: &Image, dst: Rect, tint: Option<Color>) {
80 if image.is_empty() {
81 return;
82 }
83 let tint = tint.map(Color::to_f32);
84 self.sampled(image.width, image.height, dst, |u, v| {
85 let c = image.pixel(u, v).to_f32();
86 Some(match tint {
87 Some(t) => [c[0] * t[0], c[1] * t[1], c[2] * t[2], c[3] * t[3]],
88 None => c,
89 })
90 });
91 }
92
93 pub fn mask(&mut self, mask: &Mask, dst: Rect, color: Color) {
95 let c = color.to_f32();
96 self.sampled(mask.width, mask.height, dst, |u, v| {
97 mask.get(u, v).then_some(c)
98 });
99 }
100
101 fn sampled(
105 &mut self,
106 width: u32,
107 height: u32,
108 dst: Rect,
109 sample: impl Fn(u32, u32) -> Option<[f32; 4]>,
110 ) {
111 if width == 0 || height == 0 || dst.w <= 0.0 || dst.h <= 0.0 {
112 return;
113 }
114 for py in
115 (dst.y.floor().max(0.0) as usize)..(dst.bottom().ceil().min(self.h as f32) as usize)
116 {
117 for px in
118 (dst.x.floor().max(0.0) as usize)..(dst.right().ceil().min(self.w as f32) as usize)
119 {
120 let u = ((px as f32 + 0.5 - dst.x) / dst.w * width as f32).floor();
121 let v = ((py as f32 + 0.5 - dst.y) / dst.h * height as f32).floor();
122 if u < 0.0 || v < 0.0 || u >= width as f32 || v >= height as f32 {
123 continue;
124 }
125 if let Some(c) = sample(u as u32, v as u32) {
126 over(&mut self.px[py * self.w + px], c);
127 }
128 }
129 }
130 }
131
132 fn paint(&mut self, cov: &[f32], color: Color) {
134 let c = color.to_f32();
135 for (px, &k) in self.px.iter_mut().zip(cov) {
136 if k > 0.0 {
137 over(px, [c[0], c[1], c[2], c[3] * k]);
138 }
139 }
140 }
141
142 pub fn into_image(self) -> Image {
144 let mut rgba = Vec::with_capacity(self.w * self.h * 4);
145 for p in &self.px {
146 let a = p[3];
147 let un = |c: f32| if a > 0.0 { c / a } else { 0.0 };
148 rgba.extend(Color::from_f32([un(p[0]), un(p[1]), un(p[2]), a]).to_rgba8());
149 }
150 Image {
151 width: self.w as u32,
152 height: self.h as u32,
153 rgba,
154 }
155 }
156}
157
158fn over(dst: &mut [f32; 4], src: [f32; 4]) {
160 let a = src[3];
161 for i in 0..3 {
162 dst[i] = src[i] * a + dst[i] * (1.0 - a);
163 }
164 dst[3] = a + dst[3] * (1.0 - a);
165}
166
167fn segment_quad(a: Vec2, b: Vec2, r: f32) -> Option<Vec<Vec2>> {
169 let n = (b - a).normalize().perp() * r;
170 (a != b).then(|| oriented(vec![a + n, b + n, b - n, a - n]))
171}
172
173fn circle(c: Vec2, r: f32) -> Vec<Vec2> {
175 let n = ((r * 2.0).ceil() as usize * 4).clamp(8, 48);
176 oriented(
177 (0..n)
178 .map(|i| {
179 let t = i as f32 / n as f32 * std::f32::consts::TAU;
180 c + Vec2::new(t.cos(), t.sin()) * r
181 })
182 .collect(),
183 )
184}
185
186fn oriented(mut poly: Vec<Vec2>) -> Vec<Vec2> {
189 let area: f32 = poly
190 .iter()
191 .zip(poly.iter().cycle().skip(1))
192 .map(|(a, b)| a.x * b.y - b.x * a.y)
193 .sum();
194 if area < 0.0 {
195 poly.reverse();
196 }
197 poly
198}
199
200fn coverage(w: usize, h: usize, lines: impl Iterator<Item = (Vec2, Vec2)>) -> Vec<f32> {
203 let mut acc = vec![0.0f32; w * h + 4];
204 if w == 0 || h == 0 {
205 return Vec::new();
206 }
207 for (p0, p1) in lines {
208 accumulate(&mut acc, w, h, p0, p1);
209 }
210 let mut sum = 0.0;
211 acc.truncate(w * h);
212 for a in acc.iter_mut() {
213 sum += *a;
214 *a = sum.abs().min(1.0);
215 }
216 acc
217}
218
219fn accumulate(acc: &mut [f32], w: usize, h: usize, p0: Vec2, p1: Vec2) {
221 if (p0.y - p1.y).abs() <= f32::EPSILON {
222 return;
223 }
224 let (dir, p0, p1) = if p0.y < p1.y {
225 (1.0, p0, p1)
226 } else {
227 (-1.0, p1, p0)
228 };
229 let clamp_x = |x: f32| x.clamp(0.0, w as f32 - EDGE_INSET);
230 let dxdy = (p1.x - p0.x) / (p1.y - p0.y);
231 let mut x = p0.x;
232 if p0.y < 0.0 {
233 x -= p0.y * dxdy;
234 }
235 let y_start = p0.y.max(0.0) as usize;
236 let y_end = (p1.y.ceil().max(0.0) as usize).min(h);
237 for y in y_start..y_end {
238 let row = y * w;
239 let dy = ((y + 1) as f32).min(p1.y) - (y as f32).max(p0.y);
240 let xnext = x + dxdy * dy;
241 let d = dy * dir;
242 let (x0, x1) = if x < xnext {
243 (clamp_x(x), clamp_x(xnext))
244 } else {
245 (clamp_x(xnext), clamp_x(x))
246 };
247 let x0f = x0.floor();
248 let x0i = x0f as usize;
249 let x1i = x1.ceil() as usize;
250 if x1i <= x0i + 1 {
251 let xm = 0.5 * (x0 + x1) - x0f;
252 acc[row + x0i] += d - d * xm;
253 acc[row + x0i + 1] += d * xm;
254 } else {
255 let s = 1.0 / (x1 - x0);
256 let x0r = x0 - x0f;
257 let a0 = 0.5 * s * (1.0 - x0r) * (1.0 - x0r);
258 let x1r = x1 - x1.ceil() + 1.0;
259 let am = 0.5 * s * x1r * x1r;
260 acc[row + x0i] += d * a0;
261 if x1i == x0i + 2 {
262 acc[row + x0i + 1] += d * (1.0 - a0 - am);
263 } else {
264 let a1 = s * (SECOND_CELL_MIDDLE - x0r);
265 acc[row + x0i + 1] += d * (a1 - a0);
266 for xi in x0i + 2..x1i - 1 {
267 acc[row + xi] += d * s;
268 }
269 let a2 = a1 + (x1i - x0i - 3) as f32 * s;
270 acc[row + x1i - 1] += d * (1.0 - a2 - am);
271 }
272 acc[row + x1i] += d * am;
273 }
274 x = xnext;
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn shapes_fill_and_strokes_round() {
284 let mut c = Canvas::new(20, 20);
285 c.fill(
286 &[vec![
287 Vec2::new(2.0, 2.0),
288 Vec2::new(10.0, 2.0),
289 Vec2::new(10.0, 10.0),
290 Vec2::new(2.0, 10.0),
291 ]],
292 Color::WHITE,
293 );
294 c.stroke(
295 &[vec![Vec2::new(4.0, 15.0), Vec2::new(16.0, 15.0)]],
296 2.0,
297 Color::rgb(0xff0000),
298 );
299 let img = c.into_image();
300 assert_eq!(img.pixel(5, 5), Color::WHITE);
301 assert_eq!(img.pixel(11, 5).a(), 0);
302 assert_eq!(img.pixel(10, 15), Color::rgb(0xff0000));
303 assert_eq!(img.pixel(10, 17).a(), 0);
304 }
305
306 #[test]
307 fn a_square_with_a_hole_keeps_it() {
308 let mut c = Canvas::new(12, 12);
309 let outer = vec![
310 Vec2::new(1.0, 1.0),
311 Vec2::new(11.0, 1.0),
312 Vec2::new(11.0, 11.0),
313 Vec2::new(1.0, 11.0),
314 ];
315 let inner = vec![
316 Vec2::new(4.0, 4.0),
317 Vec2::new(4.0, 8.0),
318 Vec2::new(8.0, 8.0),
319 Vec2::new(8.0, 4.0),
320 ];
321 c.fill(&[outer, inner], Color::WHITE);
322 let img = c.into_image();
323 assert_eq!(img.pixel(2, 2), Color::WHITE);
324 assert_eq!(img.pixel(6, 6).a(), 0);
325 }
326}