diff --git a/Cargo.lock b/Cargo.lock index 21f8bf1..ee10ebb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -502,6 +502,17 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gif" version = "0.13.1" @@ -736,6 +747,16 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "mark" +version = "0.0.0" +source = "git+https://github.com/Garmelon/mark.git#243ec79f42adcc0676dfc7521fa3392c35573720" +dependencies = [ + "image", + "palette", + "rand", +] + [[package]] name = "matchit" version = "0.7.3" @@ -963,6 +984,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.78" @@ -996,6 +1023,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -1004,6 +1043,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rangemap" @@ -1172,6 +1214,7 @@ dependencies = [ "anyhow", "cosmic-text", "image", + "mark", "palette", "paste", "showbits-assets", diff --git a/showbits-common/Cargo.toml b/showbits-common/Cargo.toml index de68233..09d7356 100644 --- a/showbits-common/Cargo.toml +++ b/showbits-common/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true anyhow.workspace = true cosmic-text.workspace = true image.workspace = true +mark.git = "https://github.com/Garmelon/mark.git" palette.workspace = true paste = "1.0.14" showbits-assets.workspace = true diff --git a/showbits-common/src/view.rs b/showbits-common/src/view.rs index 762207b..359eabc 100644 --- a/showbits-common/src/view.rs +++ b/showbits-common/src/view.rs @@ -67,4 +67,13 @@ impl<'a> View<'a> { self.set(pos, color); } } + + pub fn image(&mut self, image: &RgbaImage) { + for y in 0..image.height() { + for x in 0..image.width() { + let color = color::from_image_color(*image.get_pixel(x, y)); + self.set(Vec2::from_u32(x, y), color); + } + } + } } diff --git a/showbits-common/src/widgets.rs b/showbits-common/src/widgets.rs index 6ed6399..211235b 100644 --- a/showbits-common/src/widgets.rs +++ b/showbits-common/src/widgets.rs @@ -1,5 +1,7 @@ pub use block::*; +pub use image::*; pub use text::*; mod block; +mod image; mod text; diff --git a/showbits-common/src/widgets/image.rs b/showbits-common/src/widgets/image.rs new file mode 100644 index 0000000..a3474ec --- /dev/null +++ b/showbits-common/src/widgets/image.rs @@ -0,0 +1,148 @@ +use image::{ + imageops::{self, FilterType}, + RgbaImage, +}; +use mark::dither::{AlgoFloydSteinberg, AlgoStucki, Algorithm, DiffEuclid, Palette}; +use palette::{IntoColor, LinSrgb, Srgba}; +use taffy::prelude::{AvailableSpace, Layout, Size}; + +use crate::Widget; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum DitherAlgorithm { + FloydSteinberg, + Stucki, +} + +impl DitherAlgorithm { + fn dither(self, image: RgbaImage, palette: &Palette) -> RgbaImage { + match self { + Self::FloydSteinberg => { + >::run(image, palette) + } + Self::Stucki => >::run(image, palette), + } + } +} + +pub struct Image { + image: RgbaImage, + shrink: bool, + grow: bool, + filter: FilterType, + + dither_palette: Option>, + dither_algorithm: DitherAlgorithm, +} + +impl Image { + pub fn new(image: RgbaImage) -> Self { + Self { + image, + shrink: true, + grow: false, + filter: FilterType::CatmullRom, + dither_palette: None, + dither_algorithm: DitherAlgorithm::FloydSteinberg, + } + } + + pub fn with_shrink(mut self, shrink: bool) -> Self { + self.shrink = shrink; + self + } + + pub fn with_grow(mut self, grow: bool) -> Self { + self.grow = grow; + self + } + + pub fn with_filter(mut self, filter: FilterType) -> Self { + self.filter = filter; + self + } + + pub fn with_dither_palette(mut self, palette: &[Srgba]) -> Self { + let palette = palette + .iter() + .map(|c| c.color.into_color()) + .collect::>(); + + self.dither_palette = Some(Palette::new(palette)); + self + } + + pub fn with_dither_algorithm(mut self, algorithm: DitherAlgorithm) -> Self { + self.dither_algorithm = algorithm; + self + } +} + +impl Widget for Image { + fn size( + &mut self, + _ctx: &mut C, + known: Size>, + available: Size, + ) -> Size { + if self.image.width() == 0 || self.image.height() == 0 { + // We don't want to divide by zero later on + return Size { + width: 0.0, + height: 0.0, + }; + } + + let size = Size { + width: self.image.width() as f32, + height: self.image.height() as f32, + }; + + let max_width = known.width.or(match available.width { + AvailableSpace::Definite(width) => Some(width), + AvailableSpace::MinContent => Some(0.0), + AvailableSpace::MaxContent => None, + }); + + let max_height = known.height.or(match available.height { + AvailableSpace::Definite(height) => Some(height), + AvailableSpace::MinContent => Some(0.0), + AvailableSpace::MaxContent => None, + }); + + let scale_factor = match (max_width, max_height) { + (None, None) => 1.0, + (None, Some(height)) => height / size.height, + (Some(width), None) => width / size.width, + (Some(width), Some(height)) => (width / size.width).min(height / size.height), + }; + + if (scale_factor < 1.0 && self.shrink) || (scale_factor > 1.0 && self.grow) { + Size { + width: size.width * scale_factor, + height: size.height * scale_factor, + } + } else { + size + } + } + + fn draw_below( + &mut self, + _ctx: &mut C, + view: &mut crate::View<'_>, + _layout: &Layout, + ) -> anyhow::Result<()> { + let (width, height) = view.size().to_u32(); + let image = imageops::resize(&self.image, width, height, self.filter); + + let image = if let Some(palette) = &self.dither_palette { + self.dither_algorithm.dither(image, palette) + } else { + image + }; + + view.image(&image); + Ok(()) + } +}