pride_overlay/effects/
ring.rs1use 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#[derive(Builder)]
8#[builder(
9 const,
10 builder_type(doc {
11 }),
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 pub const fn builder(flag: Flag<'a>) -> RingBuilder<'a> {
42 Self::_builder(flag)
43 }
44}
45
46fn 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 let circumference = 2.0 * PI * radius as f32;
56 let sides = (circumference / PIXELS_PER_SIDE).clamp(MIN_SIDES, MAX_SIZES) as usize;
57
58 let mut points = Vec::with_capacity(sides);
60 let mut angle = 0.;
61
62 for _ in 0..sides {
63 angle += 2.0 * PI / (sides as f32);
65
66 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}