1use crate::Error;
2use image::{DynamicImage, ImageFormat};
3use std::io::Cursor;
4
5#[derive(Debug, Clone, Copy)]
6#[cfg_attr(feature = "discord", derive(poise::ChoiceParameter))]
7pub enum ImageOrientation {
8 #[cfg_attr(feature = "discord", name = "horizontally")]
9 Horizontal,
10 #[cfg_attr(feature = "discord", name = "vertically")]
11 Vertical,
12}
13
14#[derive(Debug, Default)]
15pub struct ImageOperations {
16 pub blur: Option<f32>,
17 pub orientation: Option<ImageOrientation>,
18 pub grayscale: bool,
19}
20
21impl ImageOperations {
22 pub fn new() -> Self {
23 Self::default()
24 }
25
26 pub fn is_identity(&self) -> bool {
27 self.blur.is_none() && self.orientation.is_none() && !self.grayscale
28 }
29
30 pub fn apply_to(&self, image: DynamicImage) -> Result<DynamicImage, Error> {
31 let mut img = image;
32
33 if let Some(orientation) = self.orientation {
34 match orientation {
35 ImageOrientation::Horizontal => img = img.fliph(),
36 ImageOrientation::Vertical => img = img.flipv(),
37 }
38 }
39
40 if let Some(blur_amount) = self.blur {
41 img = img.fast_blur(blur_amount);
42 }
43
44 if self.grayscale {
45 img = img.grayscale();
46 }
47
48 Ok(img)
49 }
50}
51
52#[derive(Debug)]
53pub struct ImageProcessor {
54 url: String,
55 operations: ImageOperations,
56}
57
58impl ImageProcessor {
59 pub fn new(url: String) -> Self {
60 Self {
61 url,
62 operations: ImageOperations::new(),
63 }
64 }
65
66 pub fn blur(mut self, blur: Option<f32>) -> Self {
68 if let Some(amount) = blur {
69 self.operations.blur = Some(amount);
70 }
71 self
72 }
73
74 pub fn flip(mut self, orientation: Option<ImageOrientation>) -> Self {
76 if let Some(orient) = orientation {
77 self.operations.orientation = Some(orient);
78 }
79 self
80 }
81
82 pub fn grayscale(mut self, grayscale: Option<bool>) -> Self {
84 if grayscale.is_some_and(|g| g) {
85 self.operations.grayscale = true;
86 }
87 self
88 }
89
90 pub async fn process(self) -> Result<Vec<u8>, Error> {
102 if self.operations.is_identity() {
103 return Err("no operations specified".into());
104 }
105
106 let img_bytes = reqwest::get(&self.url).await?.bytes().await?;
107
108 let processed_image = tokio::task::spawn_blocking(move || {
109 let image = image::load_from_memory(&img_bytes)?;
110 self.operations.apply_to(image)
111 })
112 .await??;
113
114 tokio::task::spawn_blocking(move || -> Result<Vec<u8>, Error> {
116 let mut bytes = Vec::new();
117 processed_image.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png)?;
118 Ok(bytes)
119 })
120 .await?
121 }
122}