Add Image widget
This commit is contained in:
parent
a0c94c1884
commit
ef5f0e3af4
5 changed files with 203 additions and 0 deletions
43
Cargo.lock
generated
43
Cargo.lock
generated
|
|
@ -502,6 +502,17 @@ dependencies = [
|
||||||
"pin-utils",
|
"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]]
|
[[package]]
|
||||||
name = "gif"
|
name = "gif"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
|
|
@ -736,6 +747,16 @@ version = "0.4.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mark"
|
||||||
|
version = "0.0.0"
|
||||||
|
source = "git+https://github.com/Garmelon/mark.git#243ec79f42adcc0676dfc7521fa3392c35573720"
|
||||||
|
dependencies = [
|
||||||
|
"image",
|
||||||
|
"palette",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
|
|
@ -963,6 +984,12 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.78"
|
version = "1.0.78"
|
||||||
|
|
@ -996,6 +1023,18 @@ version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
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",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1004,6 +1043,9 @@ name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rangemap"
|
name = "rangemap"
|
||||||
|
|
@ -1172,6 +1214,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
"image",
|
"image",
|
||||||
|
"mark",
|
||||||
"palette",
|
"palette",
|
||||||
"paste",
|
"paste",
|
||||||
"showbits-assets",
|
"showbits-assets",
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ edition.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
cosmic-text.workspace = true
|
cosmic-text.workspace = true
|
||||||
image.workspace = true
|
image.workspace = true
|
||||||
|
mark.git = "https://github.com/Garmelon/mark.git"
|
||||||
palette.workspace = true
|
palette.workspace = true
|
||||||
paste = "1.0.14"
|
paste = "1.0.14"
|
||||||
showbits-assets.workspace = true
|
showbits-assets.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -67,4 +67,13 @@ impl<'a> View<'a> {
|
||||||
self.set(pos, color);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
pub use block::*;
|
pub use block::*;
|
||||||
|
pub use image::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|
||||||
mod block;
|
mod block;
|
||||||
|
mod image;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
|
||||||
148
showbits-common/src/widgets/image.rs
Normal file
148
showbits-common/src/widgets/image.rs
Normal file
|
|
@ -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<LinSrgb>) -> RgbaImage {
|
||||||
|
match self {
|
||||||
|
Self::FloydSteinberg => {
|
||||||
|
<AlgoFloydSteinberg as Algorithm<LinSrgb, DiffEuclid>>::run(image, palette)
|
||||||
|
}
|
||||||
|
Self::Stucki => <AlgoStucki as Algorithm<LinSrgb, DiffEuclid>>::run(image, palette),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Image {
|
||||||
|
image: RgbaImage,
|
||||||
|
shrink: bool,
|
||||||
|
grow: bool,
|
||||||
|
filter: FilterType,
|
||||||
|
|
||||||
|
dither_palette: Option<Palette<LinSrgb>>,
|
||||||
|
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::<Vec<LinSrgb>>();
|
||||||
|
|
||||||
|
self.dither_palette = Some(Palette::new(palette));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_dither_algorithm(mut self, algorithm: DitherAlgorithm) -> Self {
|
||||||
|
self.dither_algorithm = algorithm;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Widget<C> for Image {
|
||||||
|
fn size(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut C,
|
||||||
|
known: Size<Option<f32>>,
|
||||||
|
available: Size<AvailableSpace>,
|
||||||
|
) -> Size<f32> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue