img_comp 1.0.3

jpg, png 图片压缩
Documentation
use rimage::{
    config::{Codec, EncoderConfig},
    image::load_from_memory,
    Decoder, Encoder,
};
use std::{fs, fs::File, path::Path};
use uuid::Uuid;

use crate::utils::{create_directory_if_not_exists, resize_by_width};

/// 图片压缩配置
#[derive(Debug, Clone)]
pub struct ImgCompConfig {
    /// 图片类型,ImgType
    pub img_type: ImgType,
    /// 改变图片尺寸,便其宽度为多少。None 则表示不改变尺寸
    pub resize_width: Option<u32>,
    /// 压缩质量 1 ~ 100,建议 80。
    pub quality: u32,
}

#[derive(Debug, Clone)]
pub enum ImgType {
    Jpg,
    Png,
}

/// 通过图片路径,压缩图片。保存为指定路径
pub fn img_comp_with_path(
    input: impl AsRef<Path>,
    output: impl AsRef<Path>,
    config: &ImgCompConfig,
) -> anyhow::Result<()> {
    let decoder = Decoder::from_path(input)?;
    let image = decoder.decode()?;
    let image = if let Some(w) = config.resize_width {
        resize_by_width(image, w)?
    } else {
        image
    };
    let config = match config.img_type {
        ImgType::Jpg => EncoderConfig::new(Codec::MozJpeg).with_quality(config.quality as f32)?,
        ImgType::Png => EncoderConfig::new(Codec::OxiPng).with_quality(config.quality as f32)?,
    };
    let file = File::create(output)?;
    let encoder = Encoder::new(file, image).with_config(config);
    encoder.encode()?;
    Ok(())
}

/// 通过图片的buf数据压缩,并返回buf
pub fn img_comp_with_buf(buf: Vec<u8>, config: &ImgCompConfig) -> anyhow::Result<Vec<u8>> {
    let temp_dir = ".img_comp_temp".to_string();
    let image = load_from_memory(buf.as_slice())?;
    let image = if let Some(w) = config.resize_width {
        resize_by_width(image, w)?
    } else {
        image
    };
    let encode_config = match config.img_type {
        ImgType::Jpg => EncoderConfig::new(Codec::MozJpeg).with_quality(config.quality as f32)?,
        ImgType::Png => EncoderConfig::new(Codec::OxiPng).with_quality(config.quality as f32)?,
    };
    create_directory_if_not_exists(&temp_dir)?;
    let id_rand = Uuid::new_v4().to_string();
    let file_path = match config.img_type {
        ImgType::Jpg => temp_dir + "/" + &id_rand + ".jpg",
        ImgType::Png => temp_dir + "/" + &id_rand + ".png",
    };
    let file = match config.img_type {
        ImgType::Jpg => File::create(&file_path)?,
        ImgType::Png => File::create(&file_path)?,
    };
    let encoder = Encoder::new(file, image).with_config(encode_config);
    encoder.encode()?;
    let buffer = fs::read(&file_path)?;
    fs::remove_file(&file_path)?;
    Ok(buffer)
}

#[cfg(test)]
mod test {
    use super::*;
    use std::fs::File;
    use std::io::Read;

    #[test]
    fn test_img_comp_path() {
        let config = ImgCompConfig {
            img_type: ImgType::Jpg,
            resize_width: Some(200),
            quality: 80,
        };
        img_comp_with_path("w.png", "w_mini.png", &config).unwrap();
        let config = ImgCompConfig {
            img_type: ImgType::Jpg,
            resize_width: Some(200),
            quality: 80,
        };
        img_comp_with_path("2.png", "2_mini.png", &config).unwrap();
    }

    #[test]
    fn test_img_comp_buf() {
        let config = ImgCompConfig {
            img_type: ImgType::Jpg,
            resize_width: Some(200),
            quality: 80,
        };
        let mut file = File::open("1.jpg").unwrap();
        let mut buffer = Vec::new();
        file.read_to_end(&mut buffer).unwrap();
        let buf = img_comp_with_buf(buffer, &config).unwrap();
        println!("buf.l {}", buf.len());
    }
}