From c3c69b160f4c5fd851fd1a49c01f633a56351f5d Mon Sep 17 00:00:00 2001 From: Julian T Date: Thu, 22 Jul 2021 21:36:15 +0200 Subject: Add smart image conversion --- src/picture.rs | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/picture.rs (limited to 'src/picture.rs') diff --git a/src/picture.rs b/src/picture.rs new file mode 100644 index 0000000..f92876c --- /dev/null +++ b/src/picture.rs @@ -0,0 +1,149 @@ +use std::path::{PathBuf, Path}; +use std::fs; +use std::io; +use std::io::{Read, Seek}; +use image::io::Reader as ImageReader; +use image::error::ImageError; +use image::imageops; + +use crate::context::Context; + +#[derive(Debug)] +pub enum LoadError { + Io(io::Error), + ExifParser(exif::Error) +} + +#[derive(Debug)] +pub enum ConversionError { + Io(io::Error), + ImageError(ImageError), +} + +#[derive(Debug)] +pub struct Picture { + taken: Option, + hash: md5::Digest, + path: PathBuf, +} + +pub struct Converter<'a> { + imgdata: Option, + pic: &'a Picture, +} + +fn hash_reader(reader: &mut R) -> Result { + let mut hash = md5::Context::new(); + let mut buff = [0; 1024]; + + loop { + let count = reader.read(&mut buff)?; + if count == 0 { + // Reached end, stopping + break + } + + hash.consume(&buff[..count]) + } + + Ok(hash.compute()) +} + +impl Picture { + /// Hash file content and load exif data. + pub fn new_from_file(path: &Path) -> Result { + let file = fs::File::open(path)?; + let mut reader = io::BufReader::new(&file); + + let taken = match exif::Reader::new().read_from_container(&mut reader) { + Ok(exif) => exif.get_field(exif::Tag::DateTimeOriginal, exif::In::PRIMARY) + .map(|field| field.display_value().with_unit(&exif).to_string()), + Err(err) => { + println!("Could not load exif data for {}: {}", path.to_str().unwrap(), err); + None + } + }; + + // Move back to start of file for hashing + reader.seek(io::SeekFrom::Start(0))?; + + Ok(Picture { + taken, + hash: hash_reader(&mut reader)?, + path: path.to_path_buf(), + }) + } + + pub fn convert(&self) -> Result { + Ok(Converter { + imgdata: None, + pic: self, + }) + } +} + +impl Converter<'_> { + fn convert_image(&mut self, size: u32, dest: &Path) -> Result<(), ImageError> { + let scaled = self.get_imgdata()?.resize( + size, + std::u32::MAX, + imageops::FilterType::Lanczos3); + + scaled.save(dest) + } + + fn get_imgdata(&mut self) -> Result<&image::DynamicImage, ImageError> { + let picpath = &self.pic.path; + + match self.imgdata { + None => self.imgdata = Some( + ImageReader::open(picpath)?.decode()? + ), + _ => () + } + + Ok(self.imgdata.as_ref().unwrap()) + } + + pub fn get_size(&mut self, ctx: &Context, size: u32) -> Result { + let hash = md5::compute(format!("{},{},{:?}", size, ctx.options.ext, self.pic.hash)); + let name = format!("{:?}.{}", hash, ctx.options.ext); + let path = ctx.imgdir.join(name); + + match path.exists() { + true => { + println!("Image of size {} already exists", size); + Ok(path) + }, + false => { + println!("Scaling image to size {}", size); + self.convert_image(size, &path)?; + Ok(path) + } + } + } +} + +impl From for LoadError { + fn from(error: io::Error) -> Self { + Self::Io(error) + } +} + +impl From for LoadError { + fn from(error: exif::Error) -> Self { + Self::ExifParser(error) + } +} + +impl From for ConversionError { + fn from(error: io::Error) -> Self { + Self::Io(error) + } +} + +impl From for ConversionError { + fn from(error: ImageError) -> Self { + Self::ImageError(error) + } +} -- cgit v1.2.3