showbits/showbits-common/src/widgets/image.rs
2025-02-22 21:54:54 +01:00

161 lines
4.3 KiB
Rust

use image::{
RgbaImage,
imageops::{self, FilterType},
};
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,
scale: u32,
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,
scale: 1,
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_scale(mut self, scale: u32) -> Self {
self.scale = scale.max(1);
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() * self.scale) as f32,
height: (self.image.height() * self.scale) 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 iwidth = width / self.scale;
let iheight = height / self.scale;
let image = imageops::resize(&self.image, iwidth, iheight, self.filter);
let image = if let Some(palette) = &self.dither_palette {
self.dither_algorithm.dither(image, palette)
} else {
image
};
let image = imageops::resize(&image, width, height, FilterType::Nearest);
view.image(&image);
Ok(())
}
}