diff --git a/cirrus b/cirrus index dabcd9b..b078194 160000 --- a/cirrus +++ b/cirrus @@ -1 +1 @@ -Subproject commit dabcd9bcc60fd9a0b4e07534c187226662d3ba7a +Subproject commit b078194e42b7eb1aefbfaab5ab2ee1326a4e9cd4 diff --git a/src/app.rs b/src/app.rs index f3d4252..69f801c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -87,10 +87,7 @@ impl<'a> Roseate<'a> { // Text styling. custom_style.visuals.override_text_color = Some( Color32::from_hex( - match self.theme.is_dark { - true => "#b5b5b5", - false => "#3b3b3b" - } + &self.theme.text_colour.hex_code ).unwrap() ); diff --git a/src/error.rs b/src/error.rs index b0d97b4..56eac79 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ -use std::{fmt::{self, Display, Formatter}, path::PathBuf}; +use std::{fmt::{self, Display, Formatter}, path::PathBuf, result::Result as StdResult}; type ActualError = Option; +pub type Result = StdResult; #[derive(Debug, Clone)] pub enum Error { diff --git a/src/image/image.rs b/src/image/image.rs index fad7817..6d7bf7c 100644 --- a/src/image/image.rs +++ b/src/image/image.rs @@ -1,4 +1,4 @@ -use std::{fs::{self, File}, io::{BufReader, Cursor, Read}, path::{Path, PathBuf}, sync::{Arc, Mutex}}; +use std::{collections::HashSet, fs::{self, File}, io::{BufReader, Cursor, Read}, path::{Path, PathBuf}, sync::{Arc, Mutex}}; use log::debug; use eframe::egui::Vec2; @@ -7,7 +7,7 @@ use svg_metadata::Metadata; use display_info::DisplayInfo; use image::{codecs::{gif::{GifDecoder, GifEncoder}, jpeg::{JpegDecoder, JpegEncoder}, png::{PngDecoder, PngEncoder}, webp::{WebPDecoder, WebPEncoder}}, DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageResult}; -use crate::{error::Error, notifier::NotifierAPI}; +use crate::{error::{Error, Result}, notifier::NotifierAPI}; use super::{backends::ImageProcessingBackend, image_formats::ImageFormat, optimization::ImageOptimization}; @@ -18,6 +18,7 @@ pub struct Image { pub image_size: ImageSize, pub image_format: ImageFormat, pub image_path: Arc, + pub applied_optimizations: HashSet, pub image_bytes: Arc>>>, // Look! I know you see that type above me but just // so you know, I'm NOT crazy... well not yet at least... @@ -40,7 +41,7 @@ pub struct Image { impl Image { // TODO: Return result instead of panicking (e.g. right now if you // open an unsupported file type roseate will crash because we panic at line 60). - pub fn from_path(path: &Path) -> Result { + pub fn from_path(path: &Path) -> Result { // Changed this to unwrap_or_default so it returns an empty // string ("") and doesn't panic if a file has no extension. I need to begin adding tests. let extension = path.extension().unwrap_or_default(); @@ -105,18 +106,19 @@ impl Image { image_size, image_format, image_path: Arc::new(path.to_owned()), - image_bytes: Arc::new(Mutex::new(None)) + image_bytes: Arc::new(Mutex::new(None)), + applied_optimizations: HashSet::new(), } ) } pub fn reload_image( &mut self, - optimizations: &[ImageOptimization], + optimizations_to_apply: &[ImageOptimization], notifier: &mut NotifierAPI, image_processing_backend: &ImageProcessingBackend - ) -> Result<(), Error> { - if optimizations.is_empty() { + ) -> Result<()> { + if optimizations_to_apply.is_empty() { return Ok(()); } @@ -128,7 +130,7 @@ impl Image { optimizations: &[ImageOptimization], notifier: &mut NotifierAPI, image_processing_backend: &ImageProcessingBackend - ) -> Result<(), Error> { + ) -> Result<()> { if optimizations.is_empty() { debug!("No optimizations were set so loading with fs::read instead..."); @@ -143,10 +145,6 @@ impl Image { // A LOT faster and no optimizations need to be done so we don't need image crate. } - let (mut actual_width, mut actual_height) = ( - self.image_size.width as u32, self.image_size.height as u32 - ); - notifier.set_loading(Some("Opening file...".into())); debug!("Opening file into buf reader for image crate to read..."); @@ -157,7 +155,9 @@ impl Image { Error::FileNotFound( Some(error.to_string()), self.image_path.to_path_buf(), - "The file we're trying to load does not exist any more!".to_string() + "The file we're trying to load does not exist any more! + This might suggest that the image got deleted between the + time you opened it and roseate was ready to load it.".to_string() ) ) }, @@ -169,42 +169,85 @@ impl Image { notifier.set_loading(Some("Passing image to image decoder...".into())); debug!("Loading image buf reader into image decoder so optimizations can be applied to pixels..."); - let image_decoder: Box = match self.image_format { + let image_decoder = self.get_image_decoder(image_buf_reader); + + let mut optimized_image_buffer: Vec = Vec::new(); + + notifier.set_loading(Some("Decoding image...".into())); + + let image_result = self.optimize_and_decode_image_to_buffer( + image_processing_backend, + image_decoder, + optimizations, + &mut optimized_image_buffer, + notifier + ); + + if let Err(image_error) = image_result { + let error = Error::FailedToApplyOptimizations( + Some(image_error.to_string()), + "Failed to decode and load image to apply optimizations!".to_string() + ); + + // warn the user that optimizations failed to apply. + notifier.toasts.lock().unwrap() + .toast_and_log(error.into(), egui_notify::ToastLevel::Error); + + // load image without optimizations + let result = self.load_image(&[], notifier, image_processing_backend); + + match result { + Ok(_) => return Ok(()), + Err(error) => return Err(error), + } + } + + // NOTE: At this point "optimized_image_buffer" should definitely have the image. + *self.image_bytes.lock().unwrap() = Some(Arc::from(optimized_image_buffer)); + + Ok(()) + } + + fn get_image_decoder(&self, image_buf_reader: BufReader) -> Box { + match self.image_format { ImageFormat::Png => Box::new(PngDecoder::new(image_buf_reader).unwrap()), ImageFormat::Jpeg => Box::new(JpegDecoder::new(image_buf_reader).unwrap()), ImageFormat::Svg => panic!("SVGs cannot be loaded with optimizations at the moment!"), ImageFormat::Gif => Box::new(GifDecoder::new(image_buf_reader).unwrap()), ImageFormat::Webp => Box::new(WebPDecoder::new(image_buf_reader).unwrap()), - }; + } + } + fn optimize_and_decode_image_to_buffer( + &self, + image_processing_backend: &ImageProcessingBackend, + image_decoder: Box, + optimizations: &[ImageOptimization], + optimized_image_buffer: &mut Vec, + notifier: &mut NotifierAPI, + ) -> ImageResult<()> { let image_colour_type = image_decoder.color_type(); - let mut optimized_image_buffer: Vec = Vec::new(); - - notifier.set_loading(Some("Decoding image...".into())); + // mutable width and height + let (mut actual_width, mut actual_height) = ( + self.image_size.width as u32, self.image_size.height as u32 + ); - let image_result: ImageResult<()> = match image_processing_backend { + match image_processing_backend { ImageProcessingBackend::Roseate => { let mut pixels = vec![0; image_decoder.total_bytes() as usize]; debug!("Decoding pixels from image using image decoder..."); image_decoder.read_image(&mut pixels).unwrap(); + let has_alpha = image_colour_type.has_alpha(); + for optimization in optimizations { notifier.set_loading( Some(format!("Applying {:#} optimization...", optimization)) ); debug!("Applying '{:?}' optimization to image...", optimization); - let has_alpha = match image_colour_type { - image::ColorType::La8 => true, - image::ColorType::Rgba8 => true, - image::ColorType::La16 => true, - image::ColorType::Rgba16 => true, - image::ColorType::Rgba32F => true, - _ => false, - }; - (pixels, (actual_width, actual_height)) = optimization.apply_roseate( pixels, &(self.image_size.width as u32, self.image_size.height as u32), has_alpha ); @@ -217,7 +260,7 @@ impl Image { match self.image_format { ImageFormat::Png => { - PngEncoder::new(&mut optimized_image_buffer).write_image( + PngEncoder::new(optimized_image_buffer).write_image( &pixels, actual_width, actual_height, @@ -225,7 +268,7 @@ impl Image { ) }, ImageFormat::Jpeg => { - JpegEncoder::new(&mut optimized_image_buffer).write_image( + JpegEncoder::new(optimized_image_buffer).write_image( &pixels, actual_width, actual_height, @@ -233,7 +276,7 @@ impl Image { ) }, ImageFormat::Svg => { - PngEncoder::new(&mut optimized_image_buffer).write_image( + PngEncoder::new(optimized_image_buffer).write_image( &pixels, actual_width, actual_height, @@ -241,7 +284,7 @@ impl Image { ) }, ImageFormat::Gif => { - GifEncoder::new(&mut optimized_image_buffer).encode( + GifEncoder::new(optimized_image_buffer).encode( &pixels, actual_width, actual_height, @@ -249,7 +292,7 @@ impl Image { ) }, ImageFormat::Webp => { - WebPEncoder::new_lossless(&mut optimized_image_buffer).write_image( + WebPEncoder::new_lossless(optimized_image_buffer).write_image( &pixels, actual_width, actual_height, @@ -280,37 +323,14 @@ impl Image { debug!("Encoding optimized dynamic image into image buffer..."); dynamic_image.write_to( - &mut Cursor::new(&mut optimized_image_buffer), + &mut Cursor::new(optimized_image_buffer), self.image_format.to_image_rs_format() ) }, Err(error) => Err(error) } } - }; - - if let Err(image_error) = image_result { - let error = Error::FailedToApplyOptimizations( - Some(image_error.to_string()), - "Failed to decode and load image to apply optimizations!".to_string() - ); - - // warn the user that optimizations failed to apply. - notifier.toasts.lock().unwrap() - .toast_and_log(error.into(), egui_notify::ToastLevel::Error); - - // load image without optimizations - let result = self.load_image(&[], notifier, image_processing_backend); - - match result { - Ok(_) => return Ok(()), - Err(error) => return Err(error), - } } - - *self.image_bytes.lock().unwrap() = Some(Arc::from(optimized_image_buffer)); - - Ok(()) } } diff --git a/src/image/optimization.rs b/src/image/optimization.rs index d612c58..82a5c13 100644 --- a/src/image/optimization.rs +++ b/src/image/optimization.rs @@ -7,7 +7,7 @@ use display_info::DisplayInfo; use super::{fast_downsample::fast_downsample, image::ImageSizeT}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ImageOptimization { /// Downsamples the image to this width and height. ///