plotters_backend/rasterizer/
circle.rs

1use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind};
2
3fn draw_part_a<
4    B: DrawingBackend,
5    Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>,
6>(
7    height: f64,
8    radius: u32,
9    mut draw: Draw,
10) -> Result<(), DrawingErrorKind<B::ErrorType>> {
11    let half_width = (radius as f64 * radius as f64
12        - (radius as f64 - height) * (radius as f64 - height))
13        .sqrt();
14
15    let x0 = (-half_width).ceil() as i32;
16    let x1 = half_width.floor() as i32;
17
18    let y0 = (radius as f64 - height).ceil();
19
20    for x in x0..=x1 {
21        let y1 = (radius as f64 * radius as f64 - x as f64 * x as f64).sqrt();
22        check_result!(draw(x, (y0, y1)));
23    }
24
25    Ok(())
26}
27
28fn draw_part_b<
29    B: DrawingBackend,
30    Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>,
31>(
32    from: f64,
33    size: f64,
34    mut draw: Draw,
35) -> Result<(), DrawingErrorKind<B::ErrorType>> {
36    let from = from.floor();
37    for x in (from - size).floor() as i32..=from as i32 {
38        check_result!(draw(x, (-x as f64, x as f64)));
39    }
40    Ok(())
41}
42
43fn draw_part_c<
44    B: DrawingBackend,
45    Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>,
46>(
47    r: i32,
48    r_limit: i32,
49    mut draw: Draw,
50) -> Result<(), DrawingErrorKind<B::ErrorType>> {
51    let half_size = r as f64 / (2f64).sqrt();
52
53    let (x0, x1) = ((-half_size).ceil() as i32, half_size.floor() as i32);
54
55    for x in x0..x1 {
56        let outter_y0 = ((r_limit as f64) * (r_limit as f64) - x as f64 * x as f64).sqrt();
57        let inner_y0 = r as f64 - 1.0;
58        let mut y1 = outter_y0.min(inner_y0);
59        let y0 = ((r as f64) * (r as f64) - x as f64 * x as f64).sqrt();
60
61        if y0 > y1 {
62            y1 = y0.ceil();
63            if y1 >= r as f64 {
64                continue;
65            }
66        }
67
68        check_result!(draw(x, (y0, y1)));
69    }
70
71    for x in x1 + 1..r {
72        let outter_y0 = ((r_limit as f64) * (r_limit as f64) - x as f64 * x as f64).sqrt();
73        let inner_y0 = r as f64 - 1.0;
74        let y0 = outter_y0.min(inner_y0);
75        let y1 = x as f64;
76
77        if y1 < y0 {
78            check_result!(draw(x, (y0, y1 + 1.0)));
79            check_result!(draw(-x, (y0, y1 + 1.0)));
80        }
81    }
82
83    Ok(())
84}
85
86fn draw_sweep_line<B: DrawingBackend, S: BackendStyle>(
87    b: &mut B,
88    style: &S,
89    (x0, y0): BackendCoord,
90    (dx, dy): (i32, i32),
91    p0: i32,
92    (s, e): (f64, f64),
93) -> Result<(), DrawingErrorKind<B::ErrorType>> {
94    let mut s = if dx < 0 || dy < 0 { -s } else { s };
95    let mut e = if dx < 0 || dy < 0 { -e } else { e };
96    if s > e {
97        std::mem::swap(&mut s, &mut e);
98    }
99
100    let vs = s.ceil() - s;
101    let ve = e - e.floor();
102
103    if dx == 0 {
104        check_result!(b.draw_line(
105            (p0 + x0, s.ceil() as i32 + y0),
106            (p0 + x0, e.floor() as i32 + y0),
107            &style.color()
108        ));
109        check_result!(b.draw_pixel((p0 + x0, s.ceil() as i32 + y0 - 1), style.color().mix(vs)));
110        check_result!(b.draw_pixel((p0 + x0, e.floor() as i32 + y0 + 1), style.color().mix(ve)));
111    } else {
112        check_result!(b.draw_line(
113            (s.ceil() as i32 + x0, p0 + y0),
114            (e.floor() as i32 + x0, p0 + y0),
115            &style.color()
116        ));
117        check_result!(b.draw_pixel((s.ceil() as i32 + x0 - 1, p0 + y0), style.color().mix(vs)));
118        check_result!(b.draw_pixel((e.floor() as i32 + x0 + 1, p0 + y0), style.color().mix(ve)));
119    }
120
121    Ok(())
122}
123
124fn draw_annulus<B: DrawingBackend, S: BackendStyle>(
125    b: &mut B,
126    center: BackendCoord,
127    radius: (u32, u32),
128    style: &S,
129) -> Result<(), DrawingErrorKind<B::ErrorType>> {
130    let a0 = ((radius.0 - radius.1) as f64).min(radius.0 as f64 * (1.0 - 1.0 / (2f64).sqrt()));
131    let a1 = (radius.0 as f64 - a0 - radius.1 as f64).max(0.0);
132
133    check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line(
134        b,
135        style,
136        center,
137        (0, 1),
138        p,
139        r
140    )));
141    check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line(
142        b,
143        style,
144        center,
145        (0, -1),
146        p,
147        r
148    )));
149    check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line(
150        b,
151        style,
152        center,
153        (1, 0),
154        p,
155        r
156    )));
157    check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line(
158        b,
159        style,
160        center,
161        (-1, 0),
162        p,
163        r
164    )));
165
166    if a1 > 0.0 {
167        check_result!(draw_part_b::<B, _>(
168            radius.0 as f64 - a0,
169            a1.floor(),
170            |h, (f, t)| {
171                let h = h as i32;
172                let f = f as i32;
173                let t = t as i32;
174                check_result!(b.draw_line(
175                    (center.0 + h, center.1 + f),
176                    (center.0 + h, center.1 + t),
177                    &style.color()
178                ));
179                check_result!(b.draw_line(
180                    (center.0 - h, center.1 + f),
181                    (center.0 - h, center.1 + t),
182                    &style.color()
183                ));
184
185                check_result!(b.draw_line(
186                    (center.0 + f + 1, center.1 + h),
187                    (center.0 + t - 1, center.1 + h),
188                    &style.color()
189                ));
190                check_result!(b.draw_line(
191                    (center.0 + f + 1, center.1 - h),
192                    (center.0 + t - 1, center.1 - h),
193                    &style.color()
194                ));
195
196                Ok(())
197            }
198        ));
199    }
200
201    check_result!(draw_part_c::<B, _>(
202        radius.1 as i32,
203        radius.0 as i32,
204        |p, r| draw_sweep_line(b, style, center, (0, 1), p, r)
205    ));
206    check_result!(draw_part_c::<B, _>(
207        radius.1 as i32,
208        radius.0 as i32,
209        |p, r| draw_sweep_line(b, style, center, (0, -1), p, r)
210    ));
211    check_result!(draw_part_c::<B, _>(
212        radius.1 as i32,
213        radius.0 as i32,
214        |p, r| draw_sweep_line(b, style, center, (1, 0), p, r)
215    ));
216    check_result!(draw_part_c::<B, _>(
217        radius.1 as i32,
218        radius.0 as i32,
219        |p, r| draw_sweep_line(b, style, center, (-1, 0), p, r)
220    ));
221
222    let d_inner = ((radius.1 as f64) / (2f64).sqrt()) as i32;
223    let d_outter = (((radius.0 as f64) / (2f64).sqrt()) as i32).min(radius.1 as i32 - 1);
224    let d_outter_actually = (radius.1 as i32).min(
225        (radius.0 as f64 * radius.0 as f64 - radius.1 as f64 * radius.1 as f64 / 2.0)
226            .sqrt()
227            .ceil() as i32,
228    );
229
230    check_result!(b.draw_line(
231        (center.0 - d_inner, center.1 - d_inner),
232        (center.0 - d_outter, center.1 - d_outter),
233        &style.color()
234    ));
235    check_result!(b.draw_line(
236        (center.0 + d_inner, center.1 - d_inner),
237        (center.0 + d_outter, center.1 - d_outter),
238        &style.color()
239    ));
240    check_result!(b.draw_line(
241        (center.0 - d_inner, center.1 + d_inner),
242        (center.0 - d_outter, center.1 + d_outter),
243        &style.color()
244    ));
245    check_result!(b.draw_line(
246        (center.0 + d_inner, center.1 + d_inner),
247        (center.0 + d_outter, center.1 + d_outter),
248        &style.color()
249    ));
250
251    check_result!(b.draw_line(
252        (center.0 - d_inner, center.1 + d_inner),
253        (center.0 - d_outter_actually, center.1 + d_inner),
254        &style.color()
255    ));
256    check_result!(b.draw_line(
257        (center.0 + d_inner, center.1 - d_inner),
258        (center.0 + d_inner, center.1 - d_outter_actually),
259        &style.color()
260    ));
261    check_result!(b.draw_line(
262        (center.0 + d_inner, center.1 + d_inner),
263        (center.0 + d_inner, center.1 + d_outter_actually),
264        &style.color()
265    ));
266    check_result!(b.draw_line(
267        (center.0 + d_inner, center.1 + d_inner),
268        (center.0 + d_outter_actually, center.1 + d_inner),
269        &style.color()
270    ));
271
272    Ok(())
273}
274
275pub fn draw_circle<B: DrawingBackend, S: BackendStyle>(
276    b: &mut B,
277    center: BackendCoord,
278    mut radius: u32,
279    style: &S,
280    mut fill: bool,
281) -> Result<(), DrawingErrorKind<B::ErrorType>> {
282    if style.color().alpha == 0.0 {
283        return Ok(());
284    }
285
286    if !fill && style.stroke_width() != 1 {
287        let inner_radius = radius - (style.stroke_width() / 2).min(radius);
288        radius += style.stroke_width() / 2;
289        if inner_radius > 0 {
290            return draw_annulus(b, center, (radius, inner_radius), style);
291        } else {
292            fill = true;
293        }
294    }
295
296    let min = (f64::from(radius) * (1.0 - (2f64).sqrt() / 2.0)).ceil() as i32;
297    let max = (f64::from(radius) * (1.0 + (2f64).sqrt() / 2.0)).floor() as i32;
298
299    let range = min..=max;
300
301    let (up, down) = (
302        range.start() + center.1 - radius as i32,
303        range.end() + center.1 - radius as i32,
304    );
305
306    for dy in range {
307        let dy = dy - radius as i32;
308        let y = center.1 + dy;
309
310        let lx = (f64::from(radius) * f64::from(radius)
311            - (f64::from(dy) * f64::from(dy)).max(1e-5))
312        .sqrt();
313
314        let left = center.0 - lx.floor() as i32;
315        let right = center.0 + lx.floor() as i32;
316
317        let v = lx - lx.floor();
318
319        let x = center.0 + dy;
320        let top = center.1 - lx.floor() as i32;
321        let bottom = center.1 + lx.floor() as i32;
322
323        if fill {
324            check_result!(b.draw_line((left, y), (right, y), &style.color()));
325            check_result!(b.draw_line((x, top), (x, up), &style.color()));
326            check_result!(b.draw_line((x, down), (x, bottom), &style.color()));
327        } else {
328            check_result!(b.draw_pixel((left, y), style.color().mix(1.0 - v)));
329            check_result!(b.draw_pixel((right, y), style.color().mix(1.0 - v)));
330
331            check_result!(b.draw_pixel((x, top), style.color().mix(1.0 - v)));
332            check_result!(b.draw_pixel((x, bottom), style.color().mix(1.0 - v)));
333        }
334
335        check_result!(b.draw_pixel((left - 1, y), style.color().mix(v)));
336        check_result!(b.draw_pixel((right + 1, y), style.color().mix(v)));
337        check_result!(b.draw_pixel((x, top - 1), style.color().mix(v)));
338        check_result!(b.draw_pixel((x, bottom + 1), style.color().mix(v)));
339    }
340
341    Ok(())
342}