From f8cc5b6f4e2b9fab98d2425c75abd23857cebac6 Mon Sep 17 00:00:00 2001 From: Slope Date: Fri, 6 Sep 2024 14:07:06 +0000 Subject: [PATCH 1/3] Replace anyhow with ImageLoaderError --- src/stitcher/image_loader.rs | 75 ++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/src/stitcher/image_loader.rs b/src/stitcher/image_loader.rs index 0c52796..607ba6a 100644 --- a/src/stitcher/image_loader.rs +++ b/src/stitcher/image_loader.rs @@ -1,17 +1,48 @@ //! This module is for all methods involved in getting selected images loaded into memory. -use anyhow::anyhow; use image::{ - image_dimensions, imageops::FilterType::Lanczos3, GenericImage, ImageReader, RgbImage, + error::ImageError, image_dimensions, imageops::FilterType::Lanczos3, GenericImage, ImageReader, + RgbImage, }; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use std::{fs::read_dir, path::PathBuf, time::Instant}; +use std::{ + fs::read_dir, + path::{Path, PathBuf}, + time::Instant, +}; use thiserror::Error; #[derive(Error, Debug)] +/// Errors related to loading images. pub enum ImageLoaderError { + #[error("Expected a directory at \"{0}\"")] + ExpectedDirectory(PathBuf), + #[error("Could not find a valid path at \"{0}\"")] + NotFound(PathBuf), + #[error("Permission denied while attempting to access \"{0}\"")] + PermissionDenied(PathBuf), #[error("No images were found in the selected directory")] NoImagesInDirectory, + // TODO: convert upstream errors to more specific errors + #[error("{0}")] + ImageError(ImageError), +} + +impl From for ImageLoaderError { + fn from(value: ImageError) -> Self { + Self::ImageError(value) + } +} + +impl ImageLoaderError { + fn from_io_error(err: std::io::Error, path: PathBuf) -> ImageLoaderError { + use std::io::ErrorKind as Kind; + match err.kind() { + Kind::NotFound => ImageLoaderError::NotFound(path), + Kind::PermissionDenied => ImageLoaderError::PermissionDenied(path), + _ => unimplemented!(), + } + } } /// Finds all `.jpg`, `.jpeg`, `.png` and `.webp` images within a directory. @@ -19,8 +50,16 @@ pub enum ImageLoaderError { /// Throws an error if: /// - The directory is invalid or does not contain any images. /// - The directory does not contain any jpg, jpeg, png, or webp images. -pub fn find_images(directory_path: &str) -> anyhow::Result> { - let mut images: Vec<_> = read_dir(directory_path)? +pub fn find_images(directory_path: impl AsRef) -> Result, ImageLoaderError> { + // create pathbuf, check if path is a directory + let path = PathBuf::from(directory_path.as_ref()); + if !path.is_dir() { + return Err(ImageLoaderError::ExpectedDirectory(path)); + } + + // get images + let mut images: Vec<_> = read_dir(directory_path) + .map_err(|e| ImageLoaderError::from_io_error(e, path))? .into_iter() .map(|file| file.unwrap().path()) .filter(|path| match path.extension() { @@ -31,15 +70,16 @@ pub fn find_images(directory_path: &str) -> anyhow::Result> { _ => false, }) .collect(); + + // if no images were found if images.is_empty() { return Err(ImageLoaderError::NoImagesInDirectory.into()); } - images.sort_by(|a, b| { - natord::compare( - a.file_name().unwrap().to_str().unwrap(), - b.file_name().unwrap().to_str().unwrap(), - ) - }); + + // sort images by natural order + images.sort_by(|a, b| natord::compare(&a.display().to_string(), &b.display().to_string())); + + // return images Ok(images) } @@ -52,11 +92,11 @@ pub fn find_images(directory_path: &str) -> anyhow::Result> { /// - The directory is invalid or does not contain any images. /// - The directory does not contain any jpg, jpeg, png, or webp images. /// - An image cannot be opened. -pub fn load_images(directory_path: &str, width: Option) -> anyhow::Result { +pub fn load_images(directory_path: &str, width: Option) -> Result { let dimensions = find_images(directory_path)? .into_iter() - .map(|image| image_dimensions(image).map_err(|e| anyhow!(e))) - .collect::>>()?; + .map(|image| image_dimensions(image).map_err(|e| ImageLoaderError::from(e))) + .collect::, ImageLoaderError>>()?; let width = match width { Some(v) => v, None => { @@ -72,16 +112,17 @@ pub fn load_images(directory_path: &str, width: Option) -> anyhow::Result = find_images(directory_path)? .into_par_iter() .map(|image_path| { - let image = ImageReader::open(image_path)? + let image = ImageReader::open(image_path.clone()) + .map_err(|e| ImageLoaderError::from_io_error(e, image_path))? .decode() - .map_err(|e| anyhow!(e))?; + .map_err(|e| ImageLoaderError::from(e))?; if image.width() == width { return Ok(image.into()); } // let height = width as f32 * image.height() as f32 / image.width() as f32; Ok(image.resize(width, height, Lanczos3).into()) }) - .collect::>>()?; + .collect::, ImageLoaderError>>()?; let mut combined_image = RgbImage::new(width, images.iter().map(|image| image.height()).sum()); let mut height_cursor = 0; for i in images { From 3a55d67d4642a83b73311c017dd51f147b350456 Mon Sep 17 00:00:00 2001 From: Slope Date: Fri, 6 Sep 2024 14:44:17 +0000 Subject: [PATCH 2/3] Clean up image loading code, add comments --- src/main.rs | 6 ++-- src/stitcher/image_loader.rs | 64 +++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/main.rs b/src/main.rs index fd57919..781ba21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,12 +5,12 @@ use quickstitch::gui; use quickstitch::stitcher::splitter::split_image; use quickstitch::stitcher::{image_loader, splitter}; -fn main() { +fn main() -> anyhow::Result<()> { // dbg!(image_loader::find_images("./testing/sample")); // let mut image = image_loader::load_images("./testing/sample", None).unwrap(); // let splitpoints = splitter::find_splitpoints_debug(&mut image, 5000, 5, 240); let now = Instant::now(); - let image = image_loader::load_images("./testing/sample", Some(800)).unwrap(); + let image = image_loader::load_images(image_loader::find_images("./testing/sample")?.as_ref(), Some(800)).unwrap(); println!("Images loaded in {:.2?}", now.elapsed()); let now = Instant::now(); let splitpoints = splitter::find_splitpoints(&image, 5000, 5, 242); @@ -18,4 +18,6 @@ fn main() { let now = Instant::now(); split_image(&image, &splitpoints, PathBuf::from("./testing/output"), 100); println!("Split image in {:.2?}", now.elapsed()); + + Ok(()) } diff --git a/src/stitcher/image_loader.rs b/src/stitcher/image_loader.rs index 607ba6a..85d7aec 100644 --- a/src/stitcher/image_loader.rs +++ b/src/stitcher/image_loader.rs @@ -4,8 +4,9 @@ use image::{ error::ImageError, image_dimensions, imageops::FilterType::Lanczos3, GenericImage, ImageReader, RgbImage, }; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; use std::{ + io, fs::read_dir, path::{Path, PathBuf}, time::Instant, @@ -15,17 +16,23 @@ use thiserror::Error; #[derive(Error, Debug)] /// Errors related to loading images. pub enum ImageLoaderError { - #[error("Expected a directory at \"{0}\"")] - ExpectedDirectory(PathBuf), + // IO Errors #[error("Could not find a valid path at \"{0}\"")] NotFound(PathBuf), #[error("Permission denied while attempting to access \"{0}\"")] PermissionDenied(PathBuf), + + // Logical Errors #[error("No images were found in the selected directory")] NoImagesInDirectory, - // TODO: convert upstream errors to more specific errors + #[error("Expected a directory at \"{0}\"")] + ExpectedDirectory(PathBuf), + + // upstream errors #[error("{0}")] ImageError(ImageError), + #[error("{0}")] + IoError(io::Error), } impl From for ImageLoaderError { @@ -35,12 +42,14 @@ impl From for ImageLoaderError { } impl ImageLoaderError { + // Convert a [io::Error] into a [ImageLoaderError]. fn from_io_error(err: std::io::Error, path: PathBuf) -> ImageLoaderError { use std::io::ErrorKind as Kind; match err.kind() { Kind::NotFound => ImageLoaderError::NotFound(path), Kind::PermissionDenied => ImageLoaderError::PermissionDenied(path), - _ => unimplemented!(), + // add more cases as required + _ => ImageLoaderError::IoError(err), } } } @@ -83,7 +92,7 @@ pub fn find_images(directory_path: impl AsRef) -> Result, Ima Ok(images) } -/// Loads any jpg, jpeg, png or webp images in the given directory into memory and resizing all images to a set width. +/// Loads the images at the provided paths into a single image strip. /// /// If the `width` parameter is set to `None`, the width of the image with the smallest width will be used. /// Otherwise, the given width will be used. @@ -92,11 +101,16 @@ pub fn find_images(directory_path: impl AsRef) -> Result, Ima /// - The directory is invalid or does not contain any images. /// - The directory does not contain any jpg, jpeg, png, or webp images. /// - An image cannot be opened. -pub fn load_images(directory_path: &str, width: Option) -> Result { - let dimensions = find_images(directory_path)? - .into_iter() - .map(|image| image_dimensions(image).map_err(|e| ImageLoaderError::from(e))) +pub fn load_images(paths: &[impl AsRef], width: Option) -> Result { + // get a vec of path refs from the generic parameter + let paths = paths.iter().map(|p| p.as_ref()).collect::>(); + + let dimensions = paths + .iter() + .map(|&image| image_dimensions(image).map_err(|e| ImageLoaderError::from(e))) .collect::, ImageLoaderError>>()?; + + // the width to resize images to let width = match width { Some(v) => v, None => { @@ -108,27 +122,39 @@ pub fn load_images(directory_path: &str, width: Option) -> Result = find_images(directory_path)? - .into_par_iter() - .map(|image_path| { - let image = ImageReader::open(image_path.clone()) - .map_err(|e| ImageLoaderError::from_io_error(e, image_path))? + + // load images + let images: Vec = paths + .par_iter() + .map(|&image_path| { + let image = ImageReader::open(image_path) + .map_err(|e| ImageLoaderError::from_io_error(e, image_path.to_path_buf()))? .decode() .map_err(|e| ImageLoaderError::from(e))?; + if image.width() == width { - return Ok(image.into()); + // noop if widths match + Ok(image.into()) + } else { + // resize image otherwise + // let height = width as f32 * image.height() as f32 / image.width() as f32; + Ok(image.resize(width, height, Lanczos3).into()) } - // let height = width as f32 * image.height() as f32 / image.width() as f32; - Ok(image.resize(width, height, Lanczos3).into()) }) .collect::, ImageLoaderError>>()?; + + // combine all images into one big strip let mut combined_image = RgbImage::new(width, images.iter().map(|image| image.height()).sum()); let mut height_cursor = 0; + for i in images { // This should never throw an error because the combined image height is set to the sum of all image heights. - combined_image.copy_from(&i, 0, height_cursor)?; + combined_image.copy_from(&i, 0, height_cursor).expect("all according to keikaku"); height_cursor += i.height(); } + Ok(combined_image) } From edff0a294dc377cacece55562e70cf9905475929 Mon Sep 17 00:00:00 2001 From: Slope Date: Sat, 7 Sep 2024 07:20:46 +0000 Subject: [PATCH 3/3] Removed unnecessary context in ImageLoaderError --- src/main.rs | 6 +++- src/stitcher/image_loader.rs | 53 +++++++++++++++++++----------------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/main.rs b/src/main.rs index 781ba21..e6378ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,11 @@ fn main() -> anyhow::Result<()> { // let mut image = image_loader::load_images("./testing/sample", None).unwrap(); // let splitpoints = splitter::find_splitpoints_debug(&mut image, 5000, 5, 240); let now = Instant::now(); - let image = image_loader::load_images(image_loader::find_images("./testing/sample")?.as_ref(), Some(800)).unwrap(); + let image = image_loader::load_images( + image_loader::find_images("./testing/sample")?.as_ref(), + Some(800), + ) + .unwrap(); println!("Images loaded in {:.2?}", now.elapsed()); let now = Instant::now(); let splitpoints = splitter::find_splitpoints(&image, 5000, 5, 242); diff --git a/src/stitcher/image_loader.rs b/src/stitcher/image_loader.rs index 85d7aec..29cf6e3 100644 --- a/src/stitcher/image_loader.rs +++ b/src/stitcher/image_loader.rs @@ -6,8 +6,8 @@ use image::{ }; use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; use std::{ - io, fs::read_dir, + io, path::{Path, PathBuf}, time::Instant, }; @@ -17,16 +17,16 @@ use thiserror::Error; /// Errors related to loading images. pub enum ImageLoaderError { // IO Errors - #[error("Could not find a valid path at \"{0}\"")] - NotFound(PathBuf), - #[error("Permission denied while attempting to access \"{0}\"")] - PermissionDenied(PathBuf), + #[error("Could not find the provided file or directory")] + NotFound, + #[error("Insufficient permissions to access the provided file or directory")] + PermissionDenied, // Logical Errors #[error("No images were found in the selected directory")] NoImagesInDirectory, - #[error("Expected a directory at \"{0}\"")] - ExpectedDirectory(PathBuf), + #[error("Expected a directory")] + ExpectedDirectory, // upstream errors #[error("{0}")] @@ -41,15 +41,15 @@ impl From for ImageLoaderError { } } -impl ImageLoaderError { - // Convert a [io::Error] into a [ImageLoaderError]. - fn from_io_error(err: std::io::Error, path: PathBuf) -> ImageLoaderError { - use std::io::ErrorKind as Kind; - match err.kind() { - Kind::NotFound => ImageLoaderError::NotFound(path), - Kind::PermissionDenied => ImageLoaderError::PermissionDenied(path), +impl From for ImageLoaderError { + fn from(value: io::Error) -> Self { + use io::ErrorKind as Kind; + + match value.kind() { + Kind::NotFound => ImageLoaderError::NotFound, + Kind::PermissionDenied => ImageLoaderError::PermissionDenied, // add more cases as required - _ => ImageLoaderError::IoError(err), + _ => ImageLoaderError::IoError(value), } } } @@ -61,14 +61,13 @@ impl ImageLoaderError { /// - The directory does not contain any jpg, jpeg, png, or webp images. pub fn find_images(directory_path: impl AsRef) -> Result, ImageLoaderError> { // create pathbuf, check if path is a directory - let path = PathBuf::from(directory_path.as_ref()); + let path = directory_path.as_ref(); if !path.is_dir() { - return Err(ImageLoaderError::ExpectedDirectory(path)); + return Err(ImageLoaderError::ExpectedDirectory); } // get images - let mut images: Vec<_> = read_dir(directory_path) - .map_err(|e| ImageLoaderError::from_io_error(e, path))? + let mut images: Vec<_> = read_dir(directory_path)? .into_iter() .map(|file| file.unwrap().path()) .filter(|path| match path.extension() { @@ -82,7 +81,7 @@ pub fn find_images(directory_path: impl AsRef) -> Result, Ima // if no images were found if images.is_empty() { - return Err(ImageLoaderError::NoImagesInDirectory.into()); + return Err(ImageLoaderError::NoImagesInDirectory); } // sort images by natural order @@ -101,7 +100,10 @@ pub fn find_images(directory_path: impl AsRef) -> Result, Ima /// - The directory is invalid or does not contain any images. /// - The directory does not contain any jpg, jpeg, png, or webp images. /// - An image cannot be opened. -pub fn load_images(paths: &[impl AsRef], width: Option) -> Result { +pub fn load_images( + paths: &[impl AsRef], + width: Option, +) -> Result { // get a vec of path refs from the generic parameter let paths = paths.iter().map(|p| p.as_ref()).collect::>(); @@ -130,15 +132,14 @@ pub fn load_images(paths: &[impl AsRef], width: Option) -> Result = paths .par_iter() .map(|&image_path| { - let image = ImageReader::open(image_path) - .map_err(|e| ImageLoaderError::from_io_error(e, image_path.to_path_buf()))? + let image = ImageReader::open(image_path)? .decode() .map_err(|e| ImageLoaderError::from(e))?; if image.width() == width { // noop if widths match Ok(image.into()) - } else { + } else { // resize image otherwise // let height = width as f32 * image.height() as f32 / image.width() as f32; Ok(image.resize(width, height, Lanczos3).into()) @@ -152,7 +153,9 @@ pub fn load_images(paths: &[impl AsRef], width: Option) -> Result