pride_overlay/effects/
ring.rs1#[cfg(not(target_arch = "wasm32"))]
2use crate::flags::Flag;
3#[cfg(target_arch = "wasm32")]
4use crate::flags::wasm::CustomFlag;
5use crate::{effects::overlay_flag, prelude::*};
6use core::f32::consts::PI;
7use image::{GenericImageView, Rgba, RgbaImage, imageops::overlay};
8use imageproc::{drawing::draw_antialiased_polygon_mut, pixelops::interpolate, point::Point};
9
10#[derive(bon::Builder)]
12#[builder(
13 const,
14 builder_type(doc {
15 })
17)]
18pub struct Ring {
19 #[builder(default = Ring::DEFAULT_OPACITY, with = |percent: f32| percent.clamp(0., 1.))]
21 opacity: f32,
22 #[builder(default = Ring::DEFAULT_THICKNESS, with = |percent: f32| percent.clamp(0., 1.))]
26 thickness: f32,
27}
28
29impl Ring {
30 pub const DEFAULT_OPACITY: f32 = 1.;
32
33 pub const DEFAULT_THICKNESS: f32 = 0.1;
35}
36
37impl Effect for Ring {
38 fn apply<F>(&self, image: &mut image::DynamicImage, flag: F)
39 where
40 F: FlagData,
41 {
42 if self.opacity == 0. {
43 } else if self.thickness >= 0.99 {
45 let effect = Overlay::builder().opacity(self.opacity).build();
47 effect.apply(image, flag)
48 } else {
49 let (width, height) = image.dimensions();
50 #[cfg(not(target_arch = "wasm32"))]
51 let ring_flag = Flag {
52 name: flag.name(),
53 colours: flag.colours(),
54 ..Default::default()
55 };
56 #[cfg(target_arch = "wasm32")]
57 let ring_flag = CustomFlag {
58 colours: flag.colours().into(),
59 ..Default::default()
60 };
61 let mut ring_overlay = overlay_flag(ring_flag, width, height, self.opacity);
62
63 let center = ((width / 2) as i32, (height / 2) as i32);
64 let radius =
65 (width / 2).saturating_sub(((width / 2) as f32 * self.thickness) as u32) as i32;
66
67 draw_circle(&mut ring_overlay, center, radius as f32, Rgba([0, 0, 0, 0]));
68 overlay(image, &ring_overlay, 0, 0);
69 }
70 }
71}
72
73fn draw_circle(image: &mut RgbaImage, center: (i32, i32), radius: f32, color: Rgba<u8>) {
75 const MIN_SIDES: f32 = 32.;
76 const MAX_SIDES: f32 = 256.;
77 const PIXELS_PER_SIDE: f32 = 4.;
78
79 let circumference = 2.0 * PI * radius;
82 let sides = (circumference / PIXELS_PER_SIDE).clamp(MIN_SIDES, MAX_SIDES);
83
84 let points: Vec<Point<i32>> = (0..(sides as usize))
86 .map(|i| {
87 let theta = 2.0 * PI * (i as f32) / sides;
88 let (x, y) = center;
89 let dx = radius * theta.cos();
90 let dy = radius * theta.sin();
91 Point::new(x + dx.round() as i32, y + dy.round() as i32)
92 })
93 .collect();
94
95 draw_antialiased_polygon_mut(image, &points, color, interpolate);
96}