summaryrefslogtreecommitdiff
path: root/src/picture.rs
diff options
context:
space:
mode:
authorJulian T <julian@jtle.dk>2021-07-22 21:36:15 +0200
committerJulian T <julian@jtle.dk>2021-07-22 21:36:15 +0200
commitc3c69b160f4c5fd851fd1a49c01f633a56351f5d (patch)
tree55cdca5f052717d1c7f9abab97e864f28dc17534 /src/picture.rs
parent69bbd4ed3804833b15285cf8637e96e59135f223 (diff)
Add smart image conversion
Diffstat (limited to 'src/picture.rs')
-rw-r--r--src/picture.rs149
1 files changed, 149 insertions, 0 deletions
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<String>,
+ hash: md5::Digest,
+ path: PathBuf,
+}
+
+pub struct Converter<'a> {
+ imgdata: Option<image::DynamicImage>,
+ pic: &'a Picture,
+}
+
+fn hash_reader<R: Read>(reader: &mut R) -> Result<md5::Digest, io::Error> {
+ 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<Self, LoadError> {
+ 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<Converter, ConversionError> {
+ 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<PathBuf, ImageError> {
+ 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<io::Error> for LoadError {
+ fn from(error: io::Error) -> Self {
+ Self::Io(error)
+ }
+}
+
+impl From<exif::Error> for LoadError {
+ fn from(error: exif::Error) -> Self {
+ Self::ExifParser(error)
+ }
+}
+
+impl From<io::Error> for ConversionError {
+ fn from(error: io::Error) -> Self {
+ Self::Io(error)
+ }
+}
+
+impl From<ImageError> for ConversionError {
+ fn from(error: ImageError) -> Self {
+ Self::ImageError(error)
+ }
+}