jellyfin_rpc/external/
image_utils.rs1use image::{DynamicImage, GenericImageView, ImageFormat, imageops};
2use std::io::{Cursor};
3
4#[derive(Debug, Clone)]
5pub struct ImageProcessingOptions {
6 pub size: Option<u32>,
7 pub background: bool,
8 pub background_blur: f32,
9 pub corner_radius: Option<f32>,
10}
11
12impl Default for ImageProcessingOptions {
13 fn default() -> Self {
14 Self {
15 size: None,
16 background: true,
17 background_blur: 3.0,
18 corner_radius: Some(4.0),
19 }
20 }
21}
22
23pub fn make_square_with_blur(input_bytes: &[u8], options: &ImageProcessingOptions) -> Result<Vec<u8>, image::ImageError> {
24 let img = image::load_from_memory(input_bytes)?;
25 let (width, height) = img.dimensions();
26 let size = options.size.unwrap_or_else(|| width.max(height));
27
28 let fg_buf = if width > height {
29 let new_height = (size as f32 * (height as f32 / width as f32)) as u32;
30 imageops::resize(&img, size, new_height, imageops::FilterType::Lanczos3)
31 } else {
32 let new_width = (size as f32 * (width as f32 / height as f32)) as u32;
33 imageops::resize(&img, new_width, size, imageops::FilterType::Lanczos3)
34 };
35
36 let fg_dyn = DynamicImage::ImageRgba8(fg_buf);
37 let (fg_w, fg_h) = fg_dyn.dimensions();
38 let mut canvas = DynamicImage::new_rgba8(size, size);
39
40 if options.background {
41 let bg_buf = imageops::resize(&img, size, size, imageops::FilterType::Gaussian);
42 let mut bg_dyn = DynamicImage::ImageRgba8(bg_buf);
43 if options.background_blur > 0.0 {
44 let blur_radius = (size as f32) * (options.background_blur / 100.0);
45 bg_dyn = DynamicImage::ImageRgba8(imageops::blur(&bg_dyn, blur_radius));
46 }
47 imageops::overlay(&mut canvas, &bg_dyn, 0, 0);
48 imageops::overlay(&mut canvas, &fg_dyn, ((size - fg_w) / 2) as i64, ((size - fg_h) / 2) as i64);
49 } else {
50 let mut fg_rounded = fg_dyn;
51
52 if let Some(radius_percent) = options.corner_radius {
53 let radius = ((size as f32) * (radius_percent / 100.0)) as u32;
54 if radius > 0 {
55 apply_rounded_corners(&mut fg_rounded, radius);
56 }
57 }
58
59 imageops::overlay(&mut canvas, &fg_rounded, ((size - fg_w) / 2) as i64, ((size - fg_h) / 2) as i64);
60 }
61
62 let mut buf = Vec::new();
63 canvas.write_to(&mut Cursor::new(&mut buf), ImageFormat::Png)?;
64 Ok(buf)
65}
66
67fn apply_rounded_corners(img: &mut DynamicImage, radius: u32) {
68 let (width, height) = img.dimensions();
69 let rgba = img.as_mut_rgba8().unwrap();
70 let radius_f = radius as f32;
71
72 for y in 0..height {
73 for x in 0..width {
74 let corner_center = if x < radius && y < radius {
75 Some((radius - 1, radius - 1))
76 } else if x >= width - radius && y < radius {
77 Some((width - radius, radius - 1))
78 } else if x < radius && y >= height - radius {
79 Some((radius - 1, height - radius))
80 } else if x >= width - radius && y >= height - radius {
81 Some((width - radius, height - radius))
82 } else {
83 None
84 };
85
86 if let Some((cx, cy)) = corner_center {
87 let dx = x as f32 - cx as f32;
88 let dy = y as f32 - cy as f32;
89 let distance = (dx * dx + dy * dy).sqrt();
90
91 if distance > radius_f {
92 let pixel = rgba.get_pixel_mut(x, y);
93 pixel[3] = 0;
94 } else if distance > radius_f - 1.0 {
95 let alpha_factor = radius_f - distance;
96 let pixel = rgba.get_pixel_mut(x, y);
97 pixel[3] = (pixel[3] as f32 * alpha_factor).round() as u8;
98 }
99 }
100 }
101 }
102}