@@ -4,24 +4,44 @@ use image::{imageops::FilterType, RgbaImage}; |
| 4 | | 4 | |
| 5 | use crate::config::ScaleMode; | 5 | use crate::config::ScaleMode; |
| 6 | | 6 | |
| 7 | -/// Scale an image according to the specified mode | 7 | +/// Scale an image according to the specified mode (high quality, Lanczos3) |
| 8 | pub fn scale_image( | 8 | pub fn scale_image( |
| 9 | image: &RgbaImage, | 9 | image: &RgbaImage, |
| 10 | target_width: u32, | 10 | target_width: u32, |
| 11 | target_height: u32, | 11 | target_height: u32, |
| 12 | mode: ScaleMode, | 12 | mode: ScaleMode, |
| | 13 | +) -> RgbaImage { |
| | 14 | + scale_image_inner(image, target_width, target_height, mode, FilterType::Lanczos3) |
| | 15 | +} |
| | 16 | + |
| | 17 | +/// Scale an image using a fast filter (Triangle) — suited for animation frames |
| | 18 | +pub fn scale_image_fast( |
| | 19 | + image: &RgbaImage, |
| | 20 | + target_width: u32, |
| | 21 | + target_height: u32, |
| | 22 | + mode: ScaleMode, |
| | 23 | +) -> RgbaImage { |
| | 24 | + scale_image_inner(image, target_width, target_height, mode, FilterType::Triangle) |
| | 25 | +} |
| | 26 | + |
| | 27 | +fn scale_image_inner( |
| | 28 | + image: &RgbaImage, |
| | 29 | + target_width: u32, |
| | 30 | + target_height: u32, |
| | 31 | + mode: ScaleMode, |
| | 32 | + filter: FilterType, |
| 13 | ) -> RgbaImage { | 33 | ) -> RgbaImage { |
| 14 | match mode { | 34 | match mode { |
| 15 | - ScaleMode::Fill => scale_fill(image, target_width, target_height), | 35 | + ScaleMode::Fill => scale_fill(image, target_width, target_height, filter), |
| 16 | - ScaleMode::Fit => scale_fit(image, target_width, target_height), | 36 | + ScaleMode::Fit => scale_fit(image, target_width, target_height, filter), |
| 17 | - ScaleMode::Stretch => scale_stretch(image, target_width, target_height), | 37 | + ScaleMode::Stretch => scale_stretch(image, target_width, target_height, filter), |
| 18 | ScaleMode::Center => scale_center(image, target_width, target_height), | 38 | ScaleMode::Center => scale_center(image, target_width, target_height), |
| 19 | ScaleMode::Tile => scale_tile(image, target_width, target_height), | 39 | ScaleMode::Tile => scale_tile(image, target_width, target_height), |
| 20 | } | 40 | } |
| 21 | } | 41 | } |
| 22 | | 42 | |
| 23 | /// Fill: Scale to cover entire area, crop excess | 43 | /// Fill: Scale to cover entire area, crop excess |
| 24 | -fn scale_fill(image: &RgbaImage, target_width: u32, target_height: u32) -> RgbaImage { | 44 | +fn scale_fill(image: &RgbaImage, target_width: u32, target_height: u32, filter: FilterType) -> RgbaImage { |
| 25 | let (src_width, src_height) = image.dimensions(); | 45 | let (src_width, src_height) = image.dimensions(); |
| 26 | | 46 | |
| 27 | // Calculate scale factor to cover the entire target | 47 | // Calculate scale factor to cover the entire target |
@@ -33,7 +53,7 @@ fn scale_fill(image: &RgbaImage, target_width: u32, target_height: u32) -> RgbaI |
| 33 | let scaled_height = (src_height as f64 * scale).round() as u32; | 53 | let scaled_height = (src_height as f64 * scale).round() as u32; |
| 34 | | 54 | |
| 35 | // Scale image | 55 | // Scale image |
| 36 | - let scaled = image::imageops::resize(image, scaled_width, scaled_height, FilterType::Lanczos3); | 56 | + let scaled = image::imageops::resize(image, scaled_width, scaled_height, filter); |
| 37 | | 57 | |
| 38 | // Crop to target size (center crop) | 58 | // Crop to target size (center crop) |
| 39 | let crop_x = (scaled_width.saturating_sub(target_width)) / 2; | 59 | let crop_x = (scaled_width.saturating_sub(target_width)) / 2; |
@@ -43,7 +63,7 @@ fn scale_fill(image: &RgbaImage, target_width: u32, target_height: u32) -> RgbaI |
| 43 | } | 63 | } |
| 44 | | 64 | |
| 45 | /// Fit: Scale to fit within area, letterbox if needed | 65 | /// Fit: Scale to fit within area, letterbox if needed |
| 46 | -fn scale_fit(image: &RgbaImage, target_width: u32, target_height: u32) -> RgbaImage { | 66 | +fn scale_fit(image: &RgbaImage, target_width: u32, target_height: u32, filter: FilterType) -> RgbaImage { |
| 47 | let (src_width, src_height) = image.dimensions(); | 67 | let (src_width, src_height) = image.dimensions(); |
| 48 | | 68 | |
| 49 | // Calculate scale factor to fit within target | 69 | // Calculate scale factor to fit within target |
@@ -55,7 +75,7 @@ fn scale_fit(image: &RgbaImage, target_width: u32, target_height: u32) -> RgbaIm |
| 55 | let scaled_height = (src_height as f64 * scale).round() as u32; | 75 | let scaled_height = (src_height as f64 * scale).round() as u32; |
| 56 | | 76 | |
| 57 | // Scale image | 77 | // Scale image |
| 58 | - let scaled = image::imageops::resize(image, scaled_width, scaled_height, FilterType::Lanczos3); | 78 | + let scaled = image::imageops::resize(image, scaled_width, scaled_height, filter); |
| 59 | | 79 | |
| 60 | // Create output with black background | 80 | // Create output with black background |
| 61 | let mut output = RgbaImage::from_pixel(target_width, target_height, image::Rgba([0, 0, 0, 255])); | 81 | let mut output = RgbaImage::from_pixel(target_width, target_height, image::Rgba([0, 0, 0, 255])); |
@@ -70,8 +90,8 @@ fn scale_fit(image: &RgbaImage, target_width: u32, target_height: u32) -> RgbaIm |
| 70 | } | 90 | } |
| 71 | | 91 | |
| 72 | /// Stretch: Scale to exact target size, ignoring aspect ratio | 92 | /// Stretch: Scale to exact target size, ignoring aspect ratio |
| 73 | -fn scale_stretch(image: &RgbaImage, target_width: u32, target_height: u32) -> RgbaImage { | 93 | +fn scale_stretch(image: &RgbaImage, target_width: u32, target_height: u32, filter: FilterType) -> RgbaImage { |
| 74 | - image::imageops::resize(image, target_width, target_height, FilterType::Lanczos3) | 94 | + image::imageops::resize(image, target_width, target_height, filter) |
| 75 | } | 95 | } |
| 76 | | 96 | |
| 77 | /// Center: Display at original size, centered | 97 | /// Center: Display at original size, centered |
@@ -136,21 +156,21 @@ mod tests { |
| 136 | #[test] | 156 | #[test] |
| 137 | fn test_scale_stretch() { | 157 | fn test_scale_stretch() { |
| 138 | let img = test_image(100, 100); | 158 | let img = test_image(100, 100); |
| 139 | - let scaled = scale_stretch(&img, 200, 150); | 159 | + let scaled = scale_stretch(&img, 200, 150, FilterType::Lanczos3); |
| 140 | assert_eq!(scaled.dimensions(), (200, 150)); | 160 | assert_eq!(scaled.dimensions(), (200, 150)); |
| 141 | } | 161 | } |
| 142 | | 162 | |
| 143 | #[test] | 163 | #[test] |
| 144 | fn test_scale_fill() { | 164 | fn test_scale_fill() { |
| 145 | let img = test_image(100, 100); | 165 | let img = test_image(100, 100); |
| 146 | - let scaled = scale_fill(&img, 200, 150); | 166 | + let scaled = scale_fill(&img, 200, 150, FilterType::Lanczos3); |
| 147 | assert_eq!(scaled.dimensions(), (200, 150)); | 167 | assert_eq!(scaled.dimensions(), (200, 150)); |
| 148 | } | 168 | } |
| 149 | | 169 | |
| 150 | #[test] | 170 | #[test] |
| 151 | fn test_scale_fit() { | 171 | fn test_scale_fit() { |
| 152 | let img = test_image(100, 100); | 172 | let img = test_image(100, 100); |
| 153 | - let scaled = scale_fit(&img, 200, 150); | 173 | + let scaled = scale_fit(&img, 200, 150, FilterType::Lanczos3); |
| 154 | assert_eq!(scaled.dimensions(), (200, 150)); | 174 | assert_eq!(scaled.dimensions(), (200, 150)); |
| 155 | } | 175 | } |
| 156 | | 176 | |