axum-image-endpoint 0.1.1

Image Endpoint to upload big images and receive rescaled ones
Documentation
use image::{
    DynamicImage, ImageDecoder, ImageEncoder, ImageFormat, ImageReader,
    codecs::{jpeg::JpegEncoder, png::PngEncoder},
    imageops::FilterType,
};
use sha2::{Digest, Sha256};

use crate::{error::Result, get_image_options::ImageType};
use std::{
    fs::{File, create_dir_all},
    io::{BufWriter, Write},
    path::PathBuf,
};

use crate::{error::ImageEndpointError, get_image_options::GetImageOptions};

pub fn get_image_path_hash(image_path: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(image_path);
    let hash = hasher.finalize();
    let hash = hex::encode(hash);

    hash
}

pub fn create_final_image(
    original_filepath: &PathBuf,
    target_filepath: PathBuf,
    options: &GetImageOptions,
) -> Result<()> {
    // load image and apply orientation because image-rs does not write exif data to the image
    let mut decoder = ImageReader::open(original_filepath)?.into_decoder()?;
    let orientation = decoder.orientation()?;

    let image = DynamicImage::from_decoder(decoder)?;

    // apply resize options
    let image = match (options.width, options.height) {
        (Some(width), Some(height)) => image.resize(width, height, FilterType::Lanczos3),
        (Some(width), None) => image.resize(width, u32::MAX, FilterType::Lanczos3),
        (None, Some(height)) => image.resize(u32::MAX, height, FilterType::Lanczos3),
        (None, None) => image,
    };

    // apply rotation
    let image = match options.rotate {
        Some(rotate) => match rotate {
            90 => image.rotate90(),
            180 => image.rotate180(),
            270 => image.rotate270(),
            _ => {
                return Err(ImageEndpointError::Generic(
                    "Possible rotation values are 90, 180 or 270".into(),
                ));
            }
        },
        None => image,
    };

    // apply grayscale
    let image = match options.grayscale {
        Some(true) => image.grayscale(),
        _ => image,
    };

    // apply orientation
    let mut image = image;
    image.apply_orientation(orientation);

    let image_format = match options.imgtype {
        Some(image_type) => match image_type {
            ImageType::Jpeg | ImageType::Jpg => ImageFormat::Jpeg,
            ImageType::Png => ImageFormat::Png,
            ImageType::Webp => ImageFormat::WebP,
        },
        None => ImageFormat::from_path(original_filepath)?,
    };

    // create the directory
    let Some(dir) = &target_filepath.parent() else {
        return Err(ImageEndpointError::Generic(
            "Was not able to read the directroy from the image path".into(),
        ));
    };
    create_dir_all(dir)?;

    // write the file to disk
    let mut file = BufWriter::new(File::create(target_filepath)?);
    match image_format {
        ImageFormat::Jpeg => {
            let encoder = match options.quality {
                Some(quality) => JpegEncoder::new_with_quality(file, quality),
                None => JpegEncoder::new(file),
            };
            encoder.write_image(
                image.as_bytes(),
                image.width(),
                image.height(),
                image.color().into(),
            )?;
        }
        ImageFormat::Png => {
            let encoder = PngEncoder::new(file);

            encoder.write_image(
                image.as_bytes(),
                image.width(),
                image.height(),
                image.color().into(),
            )?;
        }
        ImageFormat::WebP => {
            // this uses the webp crate, because the image crate has only a lossless webp encoder
            let encoder = webp::Encoder::from_image(&image).map_err(|_| {
                ImageEndpointError::Generic(
                    "Was not able to create an encoder from image data".into(),
                )
            })?;

            let webp = match options.quality {
                Some(quality) => encoder.encode(f32::from(quality)),
                None => encoder.encode(75f32),
            };
            file.write_all(&webp)?;
        }
        _ => {
            return Err(ImageEndpointError::Generic(
                "Unsupported image format".into(),
            ));
        }
    }

    Ok(())
}