Skip to content

Commit

Permalink
feat, refactor!: use text_colour from cirrus, move optimize logic a…
Browse files Browse the repository at this point in the history
…nd image buf to image decoder to separate function. Also make `ImageOptimization` clonable. #39
  • Loading branch information
THEGOLDENPRO committed Dec 17, 2024
1 parent 4fb43b4 commit 7974ad5
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 64 deletions.
2 changes: 1 addition & 1 deletion cirrus
Submodule cirrus updated 1 files
+15 −0 theming/src/v1/mod.rs
5 changes: 1 addition & 4 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);

Expand Down
3 changes: 2 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
@@ -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<String>;
pub type Result<T, E = Error> = StdResult<T, E>;

#[derive(Debug, Clone)]
pub enum Error {
Expand Down
134 changes: 77 additions & 57 deletions src/image/image.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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};

Expand All @@ -18,6 +18,7 @@ pub struct Image {
pub image_size: ImageSize,
pub image_format: ImageFormat,
pub image_path: Arc<PathBuf>,
pub applied_optimizations: HashSet<ImageOptimization>,
pub image_bytes: Arc<Mutex<Option<Arc<[u8]>>>>,
// Look! I know you see that type above me but just
// so you know, I'm NOT crazy... well not yet at least...
Expand All @@ -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<Self, Error> {
pub fn from_path(path: &Path) -> Result<Self> {
// 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();
Expand Down Expand Up @@ -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(());
}

Expand All @@ -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...");

Expand All @@ -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...");

Expand All @@ -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()
)
)
},
Expand All @@ -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<dyn ImageDecoder> = match self.image_format {
let image_decoder = self.get_image_decoder(image_buf_reader);

let mut optimized_image_buffer: Vec<u8> = 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<File>) -> Box<dyn ImageDecoder> {
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<dyn ImageDecoder>,
optimizations: &[ImageOptimization],
optimized_image_buffer: &mut Vec<u8>,
notifier: &mut NotifierAPI,
) -> ImageResult<()> {
let image_colour_type = image_decoder.color_type();

let mut optimized_image_buffer: Vec<u8> = 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
);
Expand All @@ -217,39 +260,39 @@ 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,
ExtendedColorType::Rgb8
)
},
ImageFormat::Jpeg => {
JpegEncoder::new(&mut optimized_image_buffer).write_image(
JpegEncoder::new(optimized_image_buffer).write_image(
&pixels,
actual_width,
actual_height,
image_colour_type.into()
)
},
ImageFormat::Svg => {
PngEncoder::new(&mut optimized_image_buffer).write_image(
PngEncoder::new(optimized_image_buffer).write_image(
&pixels,
actual_width,
actual_height,
image_colour_type.into()
)
},
ImageFormat::Gif => {
GifEncoder::new(&mut optimized_image_buffer).encode(
GifEncoder::new(optimized_image_buffer).encode(
&pixels,
actual_width,
actual_height,
image_colour_type.into()
)
},
ImageFormat::Webp => {
WebPEncoder::new_lossless(&mut optimized_image_buffer).write_image(
WebPEncoder::new_lossless(optimized_image_buffer).write_image(
&pixels,
actual_width,
actual_height,
Expand Down Expand Up @@ -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(())
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/image/optimization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down

0 comments on commit 7974ad5

Please sign in to comment.