#[cfg(not(target_arch = "wasm32"))]
use crate::flags::Flag;
#[cfg(target_arch = "wasm32")]
use crate::flags::wasm::CustomFlag;
use crate::{effects::overlay_flag, prelude::*};
use core::f32::consts::PI;
use image::{GenericImageView, Rgba, RgbaImage, imageops::overlay};
use imageproc::{drawing::draw_antialiased_polygon_mut, pixelops::interpolate, point::Point};
#[derive(bon::Builder)]
#[builder(
const,
builder_type(doc {
/// Builder for the [Ring] effect.
})
)]
pub struct Ring {
#[builder(default = Ring::DEFAULT_OPACITY, with = |percent: f32| percent.clamp(0., 1.))]
opacity: f32,
#[builder(default = Ring::DEFAULT_THICKNESS, with = |percent: f32| percent.clamp(0., 1.))]
thickness: f32,
}
impl Ring {
pub const DEFAULT_OPACITY: f32 = 1.;
pub const DEFAULT_THICKNESS: f32 = 0.1;
}
impl Effect for Ring {
fn apply<F>(&self, image: &mut image::DynamicImage, flag: F)
where
F: FlagData,
{
if self.opacity == 0. {
} else if self.thickness >= 0.99 {
let effect = Overlay::builder().opacity(self.opacity).build();
effect.apply(image, flag)
} else {
let (width, height) = image.dimensions();
#[cfg(not(target_arch = "wasm32"))]
let ring_flag = Flag {
name: flag.name(),
colours: flag.colours(),
..Default::default()
};
#[cfg(target_arch = "wasm32")]
let ring_flag = CustomFlag {
colours: flag.colours().into(),
..Default::default()
};
let mut ring_overlay = overlay_flag(ring_flag, width, height, self.opacity);
let center = ((width / 2) as i32, (height / 2) as i32);
let radius =
(width / 2).saturating_sub(((width / 2) as f32 * self.thickness) as u32) as i32;
draw_circle(&mut ring_overlay, center, radius as f32, Rgba([0, 0, 0, 0]));
overlay(image, &ring_overlay, 0, 0);
}
}
}
fn draw_circle(image: &mut RgbaImage, center: (i32, i32), radius: f32, color: Rgba<u8>) {
const MIN_SIDES: f32 = 32.;
const MAX_SIDES: f32 = 256.;
const PIXELS_PER_SIDE: f32 = 4.;
let circumference = 2.0 * PI * radius;
let sides = (circumference / PIXELS_PER_SIDE).clamp(MIN_SIDES, MAX_SIDES);
let points: Vec<Point<i32>> = (0..(sides as usize))
.map(|i| {
let theta = 2.0 * PI * (i as f32) / sides;
let (x, y) = center;
let dx = radius * theta.cos();
let dy = radius * theta.sin();
Point::new(x + dx.round() as i32, y + dy.round() as i32)
})
.collect();
draw_antialiased_polygon_mut(image, &points, color, interpolate);
}