pride_overlay/effects/
ring.rs

1use crate::{effects::create_flag_overlay, prelude::*};
2use core::f32::consts::PI;
3use image::{GenericImageView, Rgba, RgbaImage, imageops::overlay};
4use imageproc::{drawing::draw_antialiased_polygon_mut, pixelops::interpolate, point::Point};
5
6/// Effect that draws a ring around an image using pride [Flag] colors.
7#[derive(Builder)]
8#[builder(
9    const,
10    builder_type(doc {
11        /// Builder for the [Ring] effect.
12    }),
13    start_fn(vis = "pub(crate)", name = "_builder")
14)]
15pub struct Ring<'a> {
16    #[builder(start_fn)]
17    flag: Flag<'a>,
18    #[builder(default = Opacity::OPAQUE)]
19    opacity: Opacity,
20    #[builder(default = 12)]
21    thickness: u32,
22}
23
24impl Effect for Ring<'_> {
25    fn apply(&self, image: &mut image::DynamicImage) {
26        let (width, height) = image.dimensions();
27
28        let ring_flag = Flag::builder("", self.flag.colours).build();
29        let mut ring_overlay = create_flag_overlay(&ring_flag, width, height, &self.opacity);
30
31        let center = ((width / 2) as i32, (height / 2) as i32);
32        let radius = (width / 2).saturating_sub(self.thickness) as i32;
33
34        draw_circle(&mut ring_overlay, center, radius, Rgba([0, 0, 0, 0]));
35        overlay(image, &ring_overlay, 0, 0);
36    }
37}
38
39impl<'a> Ring<'a> {
40    /// Create a new [Ring] [Effect] with a custom [Flag].
41    pub const fn builder(flag: Flag<'a>) -> RingBuilder<'a> {
42        Self::_builder(flag)
43    }
44}
45
46/// Draws a smooth circle on the image using anti-aliasing.
47fn draw_circle(image: &mut RgbaImage, center: (i32, i32), radius: i32, color: Rgba<u8>) {
48    const MIN_SIDES: f32 = 32.;
49    const MAX_SIZES: f32 = 256.;
50    const PIXELS_PER_SIDE: f32 = 4.;
51
52    // determine the number of sides to use for a the polygon
53    // that approximates the circle.
54    // circumference = 2 * pi * radius
55    let circumference = 2.0 * PI * radius as f32;
56    let sides = (circumference / PIXELS_PER_SIDE).clamp(MIN_SIDES, MAX_SIZES) as usize;
57
58    // compute the points of the polygon
59    let mut points = Vec::with_capacity(sides);
60    let mut angle = 0.;
61
62    for _ in 0..sides {
63        // angle_{i + 1} = angle_{i} + (2 * pi / sides)
64        angle += 2.0 * PI / (sides as f32);
65
66        // calculate the x and y coordinates of the point
67        let (x, y) = center;
68        let (dx, dy) = (radius as f32 * angle.cos(), radius as f32 * angle.sin());
69        points.push(Point::new(x + dx as i32, y + dy as i32));
70    }
71
72    draw_antialiased_polygon_mut(image, &points, color, interpolate);
73}