palette-bin 0.1.0

Apply an ACT palette to and optionally resize an image
Documentation
use crate::RatioMode;
use image::{imageops, RgbImage};

/// Resizes the image according to the specified RatioMode.
pub fn resize_image(img: RgbImage, ratio_mode: RatioMode, width: u32, height: u32) -> RgbImage {
    match ratio_mode {
        RatioMode::Maintain => {
            let mut new_img = RgbImage::new(width, height);

            let (img_width, img_height) = img.dimensions();
            let width_ratio = width as f32 / img_width as f32;
            let height_ratio = height as f32 / img_height as f32;

            let scale_factor = width_ratio.min(height_ratio);
            let new_width = (img_width as f32 * scale_factor) as u32;
            let new_height = (img_height as f32 * scale_factor) as u32;

            let resized = image::imageops::resize(
                &img,
                new_width,
                new_height,
                imageops::FilterType::Lanczos3,
            );

            let x_offset = (width - new_width) / 2;
            let y_offset = (height - new_height) / 2;

            imageops::overlay(&mut new_img, &resized, x_offset as i64, y_offset as i64);

            new_img
        }
        RatioMode::Stretch => imageops::resize(&img, width, height, imageops::FilterType::Lanczos3),
        RatioMode::Crop => {
            let (img_width, img_height) = img.dimensions();
            let aspect_ratio = img_width as f32 / img_height as f32;
            let display_aspect_ratio = width as f32 / height as f32;

            let (crop_width, crop_height) = if aspect_ratio > display_aspect_ratio {
                (img_height as f32 * display_aspect_ratio, img_height as f32)
            } else {
                (img_width as f32, img_width as f32 / display_aspect_ratio)
            };

            let x_offset = ((img_width as f32 - crop_width) / 2.0) as u32;
            let y_offset = ((img_height as f32 - crop_height) / 2.0) as u32;

            let cropped_img = imageops::crop_imm(
                &img,
                x_offset,
                y_offset,
                crop_width as u32,
                crop_height as u32,
            )
            .to_image();

            imageops::resize(&cropped_img, width, height, imageops::FilterType::Lanczos3)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_utils::create_test_image;
    use image::Rgb;

    #[test]
    fn test_resize_image_maintain() {
        let img = create_test_image(100, 50);

        let resized_img = resize_image(img, RatioMode::Maintain, 800, 480);

        assert_eq!(resized_img.dimensions(), (800, 480));
        assert_eq!(resized_img.get_pixel(0, 0), &Rgb([0, 0, 0]));
        assert_eq!(resized_img.get_pixel(799, 479), &Rgb([0, 0, 0]));
    }

    #[test]
    fn test_resize_image_stretch() {
        let img = create_test_image(100, 50);

        let resized_img = resize_image(img, RatioMode::Stretch, 800, 480);

        assert_eq!(resized_img.dimensions(), (800, 480));
        assert_eq!(resized_img.get_pixel(0, 0), &Rgb([0, 0, 0]));
        assert_eq!(resized_img.get_pixel(799, 479), &Rgb([255, 255, 255]));
    }

    #[test]
    fn test_resize_image_crop() {
        let img = create_test_image(100, 50);

        let resized_img = resize_image(img, RatioMode::Crop, 800, 800);

        assert_eq!(resized_img.dimensions(), (800, 800));
        // From the crop, the corners are _almost_ black and white but not quite
        assert_eq!(resized_img.get_pixel(0, 0), &Rgb([64, 0, 43]));
        assert_eq!(resized_img.get_pixel(799, 799), &Rgb([190, 255, 211]));
    }
}