@@ -4,24 +4,44 @@ use image::{imageops::FilterType, RgbaImage}; |
| 4 | 4 | |
| 5 | 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 | 8 | pub fn scale_image( |
| 9 | 9 | image: &RgbaImage, |
| 10 | 10 | target_width: u32, |
| 11 | 11 | target_height: u32, |
| 12 | 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 | 33 | ) -> RgbaImage { |
| 14 | 34 | match mode { |
| 15 | | - ScaleMode::Fill => scale_fill(image, target_width, target_height), |
| 16 | | - ScaleMode::Fit => scale_fit(image, target_width, target_height), |
| 17 | | - ScaleMode::Stretch => scale_stretch(image, target_width, target_height), |
| 35 | + ScaleMode::Fill => scale_fill(image, target_width, target_height, filter), |
| 36 | + ScaleMode::Fit => scale_fit(image, target_width, target_height, filter), |
| 37 | + ScaleMode::Stretch => scale_stretch(image, target_width, target_height, filter), |
| 18 | 38 | ScaleMode::Center => scale_center(image, target_width, target_height), |
| 19 | 39 | ScaleMode::Tile => scale_tile(image, target_width, target_height), |
| 20 | 40 | } |
| 21 | 41 | } |
| 22 | 42 | |
| 23 | 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 | 45 | let (src_width, src_height) = image.dimensions(); |
| 26 | 46 | |
| 27 | 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 | 53 | let scaled_height = (src_height as f64 * scale).round() as u32; |
| 34 | 54 | |
| 35 | 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 | 58 | // Crop to target size (center crop) |
| 39 | 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 | 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 | 67 | let (src_width, src_height) = image.dimensions(); |
| 48 | 68 | |
| 49 | 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 | 75 | let scaled_height = (src_height as f64 * scale).round() as u32; |
| 56 | 76 | |
| 57 | 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 | 80 | // Create output with black background |
| 61 | 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 | 92 | /// Stretch: Scale to exact target size, ignoring aspect ratio |
| 73 | | -fn scale_stretch(image: &RgbaImage, target_width: u32, target_height: u32) -> RgbaImage { |
| 74 | | - image::imageops::resize(image, target_width, target_height, FilterType::Lanczos3) |
| 93 | +fn scale_stretch(image: &RgbaImage, target_width: u32, target_height: u32, filter: FilterType) -> RgbaImage { |
| 94 | + image::imageops::resize(image, target_width, target_height, filter) |
| 75 | 95 | } |
| 76 | 96 | |
| 77 | 97 | /// Center: Display at original size, centered |
@@ -136,21 +156,21 @@ mod tests { |
| 136 | 156 | #[test] |
| 137 | 157 | fn test_scale_stretch() { |
| 138 | 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 | 160 | assert_eq!(scaled.dimensions(), (200, 150)); |
| 141 | 161 | } |
| 142 | 162 | |
| 143 | 163 | #[test] |
| 144 | 164 | fn test_scale_fill() { |
| 145 | 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 | 167 | assert_eq!(scaled.dimensions(), (200, 150)); |
| 148 | 168 | } |
| 149 | 169 | |
| 150 | 170 | #[test] |
| 151 | 171 | fn test_scale_fit() { |
| 152 | 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 | 174 | assert_eq!(scaled.dimensions(), (200, 150)); |
| 155 | 175 | } |
| 156 | 176 | |