diff --git a/Cargo.lock b/Cargo.lock index 9190c60..8d65c3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1025,6 +1025,7 @@ name = "showbits-common" version = "0.0.0" dependencies = [ "anyhow", + "image", "palette", ] diff --git a/Cargo.toml b/Cargo.toml index 19591e4..fa02f40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [workspace.dependencies] anyhow = "1.0.80" +image = "0.24.9" palette = "0.7.5" showbits-common.path = "./showbits-common" diff --git a/showbits-common/Cargo.toml b/showbits-common/Cargo.toml index 06a1b6f..1f3e630 100644 --- a/showbits-common/Cargo.toml +++ b/showbits-common/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] anyhow.workspace = true +image.workspace = true palette.workspace = true [lints] diff --git a/showbits-common/src/buffer.rs b/showbits-common/src/buffer.rs deleted file mode 100644 index 72944f2..0000000 --- a/showbits-common/src/buffer.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::Vec2; - -#[derive(Clone)] -pub struct Buffer { - size: Vec2, - data: Vec, -} - -impl Buffer { - pub fn new(size: Vec2, color: C) -> Self - where - C: Copy, - { - assert!(size.x >= 0); - assert!(size.y >= 0); - - let len = (size.x * size.y) as usize; - let data = vec![color; len]; - - Self { size, data } - } - - pub fn size(&self) -> Vec2 { - self.size - } - - fn index(&self, pos: Vec2) -> Option { - let in_bounds_x = pos.x >= 0 || pos.x < self.size.x; - let in_bounds_y = pos.y >= 0 || pos.y < self.size.y; - let in_bounds = in_bounds_x && in_bounds_y; - - if in_bounds { - Some((pos.y * self.size.x + pos.x) as usize) - } else { - None - } - } - - pub fn at(&self, pos: Vec2) -> Option<&C> { - let index = self.index(pos)?; - let pixel = self.data.get(index)?; - Some(pixel) - } - - pub fn at_mut(&mut self, pos: Vec2) -> Option<&mut C> { - let index = self.index(pos)?; - let pixel = self.data.get_mut(index)?; - Some(pixel) - } -} diff --git a/showbits-common/src/color.rs b/showbits-common/src/color.rs deleted file mode 100644 index e62a83c..0000000 --- a/showbits-common/src/color.rs +++ /dev/null @@ -1,11 +0,0 @@ -use palette::Srgb; - -pub trait Color: Copy { - /// Convert to an sRGB color. - /// - /// Useful for debugging or dithering. - fn to_srgb(self) -> Srgb; - - /// Combine two colors by putting one "over" the other. - fn over(self, below: Self) -> Self; -} diff --git a/showbits-common/src/lib.rs b/showbits-common/src/lib.rs index ef8c027..fde4c1a 100644 --- a/showbits-common/src/lib.rs +++ b/showbits-common/src/lib.rs @@ -1,9 +1,5 @@ -pub use crate::{buffer::*, color::*, rect::*, vec2::*, view::*, widget::*}; +pub use crate::{rect::*, vec2::*, view::*}; -mod buffer; -mod color; mod rect; mod vec2; mod view; -mod widget; -pub mod widgets; diff --git a/showbits-common/src/vec2.rs b/showbits-common/src/vec2.rs index 26ca229..4e69c1d 100644 --- a/showbits-common/src/vec2.rs +++ b/showbits-common/src/vec2.rs @@ -20,6 +20,18 @@ impl Vec2 { Self { x, y } } + pub fn from_u32(x: u32, y: u32) -> Self { + let x: i32 = x.try_into().expect("x too large"); + let y: i32 = y.try_into().expect("y too large"); + Self::new(x, y) + } + + pub fn to_u32(self) -> (u32, u32) { + let x: u32 = self.x.try_into().expect("x too small"); + let y: u32 = self.y.try_into().expect("y too small"); + (x, y) + } + /// The vector pointing from `self` to `other`. /// /// ``` diff --git a/showbits-common/src/view.rs b/showbits-common/src/view.rs index 0e5e1c0..38dd52c 100644 --- a/showbits-common/src/view.rs +++ b/showbits-common/src/view.rs @@ -1,21 +1,21 @@ -use crate::{Buffer, Color, Rect, Vec2}; +use image::RgbImage; +use palette::Srgb; -// TODO Add Orientation (from inkfo) +use crate::{Rect, Vec2}; -pub struct View<'a, C> { +pub struct View<'a> { area: Rect, - buffer: &'a mut Buffer, + buffer: &'a mut RgbImage, } -impl<'a, C> View<'a, C> { - pub fn new(buffer: &'a mut Buffer) -> Self { - Self { - area: Rect::from_nw(Vec2::ZERO, buffer.size()), - buffer, - } +impl<'a> View<'a> { + pub fn new(buffer: &'a mut RgbImage) -> Self { + let size = Vec2::from_u32(buffer.width(), buffer.height()); + let area = Rect::from_nw(Vec2::ZERO, size); + Self { area, buffer } } - pub fn dup(&mut self) -> View<'_, C> { + pub fn dup(&mut self) -> View<'_> { View { area: self.area, buffer: self.buffer, @@ -35,15 +35,19 @@ impl<'a, C> View<'a, C> { pos + self.area.corner_nw() } - pub fn at(&self, pos: Vec2) -> Option<&C> { - self.buffer.at(self.pos_to_buffer_pos(pos)) + pub fn get(&self, pos: Vec2) -> Option { + let (x, y) = self.pos_to_buffer_pos(pos).to_u32(); + let pixel = self.buffer.get_pixel_checked(x, y)?; + let [r, g, b] = pixel.0; + let color = Srgb::new(r, g, b); + Some(color.into_format()) } -} -impl View<'_, C> { - pub fn set(&mut self, pos: Vec2, color: C) { - if let Some(pixel) = self.buffer.at_mut(self.pos_to_buffer_pos(pos)) { - *pixel = color.over(*pixel); + pub fn set(&mut self, pos: Vec2, color: Srgb) { + let (x, y) = self.pos_to_buffer_pos(pos).to_u32(); + if let Some(pixel) = self.buffer.get_pixel_mut_checked(x, y) { + let color = color.into_format::(); + pixel.0 = [color.red, color.green, color.blue]; } } } diff --git a/showbits-common/src/widget.rs b/showbits-common/src/widget.rs deleted file mode 100644 index bf0ac35..0000000 --- a/showbits-common/src/widget.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::{widgets::Background, Rect, Vec2, View}; - -pub trait Widget { - /// Size that the widget wants to be, given the width and height - /// constraints. - fn size(&self, max_width: Option, max_height: Option) -> Vec2; - - /// Recalculate the area (size and position) of all inner widgets given the - /// widget's own area. - /// - /// # Implement if... - /// - /// - There are inner widgets - fn set_area(&mut self, _area: Rect) {} - - /// Perform any updates (e.g. fetching map tiles) that require the widget's - /// area and may fail. - /// - /// # Implement if... - /// - /// - There are inner widgets - /// - This widget needs to perform updates - fn update(&mut self, _area: Rect) -> anyhow::Result<()> { - Ok(()) - } - - fn draw(self, view: &mut View<'_, C>); -} - -/// Extension trait for [`Widget`]s. -pub trait WidgetExt { - fn boxed(self) -> BoxedWidget; -} - -impl WidgetExt for W -where - W: Widget + 'static, -{ - fn boxed(self) -> BoxedWidget { - BoxedWidget::new(self) - } -} - -/// Wrapper trait around [`Widget`] that turns `Box` into a `Self` to get -/// around the "size cannot be statically determined" error with the naïve -/// approach of `Box`. -trait WidgetWrapper { - fn size(&self, max_width: Option, max_height: Option) -> Vec2; - fn set_area(&mut self, _area: Rect); - fn update(&mut self, _area: Rect) -> anyhow::Result<()>; - fn draw(self: Box, view: &mut View<'_, C>); -} - -impl> WidgetWrapper for W { - // These implementations explicitly use `Widget::*` to call the widget - // methods even though they have priority over the `WidgetWrapper::*` - // methods for some reason. Just a bit of rustc magic, I guess. - - fn size(&self, max_width: Option, max_height: Option) -> Vec2 { - Widget::size(self, max_width, max_height) - } - - fn set_area(&mut self, area: Rect) { - // Widget::set_area(self, area); - self.set_area(area); - } - - fn update(&mut self, area: Rect) -> anyhow::Result<()> { - Widget::update(self, area) - } - - fn draw(self: Box, view: &mut View<'_, C>) { - Widget::draw(*self, view); - } -} - -pub struct BoxedWidget { - area: Rect, - widget: Box>, -} - -impl BoxedWidget { - pub fn new(widget: W) -> Self - where - W: Widget + 'static, - { - Self { - area: Rect::ZERO, - widget: Box::new(widget), - } - } - - pub fn area(&self) -> Rect { - self.area - } - - // Widget-like functions - - pub fn size(&self, max_width: Option, max_height: Option) -> Vec2 { - self.widget.size(max_width, max_height) - } - - pub fn set_area(&mut self, area: Rect) { - self.area = area; - self.widget.set_area(area); - } - - pub fn update(&mut self) -> anyhow::Result<()> { - self.widget.update(self.area) - } - - pub fn draw(self, view: &mut View<'_, C>) { - let mut view = view.dup().zoom(self.area); - self.widget.draw(&mut view); - } - - // Widget constructors - - pub fn background(self, color: C) -> Background { - Background::new(self, color) - } -} diff --git a/showbits-common/src/widgets.rs b/showbits-common/src/widgets.rs deleted file mode 100644 index be2843c..0000000 --- a/showbits-common/src/widgets.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub use background::*; -pub use empty::*; - -mod background; -mod empty; diff --git a/showbits-common/src/widgets/background.rs b/showbits-common/src/widgets/background.rs deleted file mode 100644 index 47c48d7..0000000 --- a/showbits-common/src/widgets/background.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{BoxedWidget, Color, Vec2, Widget}; - -pub struct Background { - inner: BoxedWidget, - color: C, -} - -impl Background { - pub fn new(inner: BoxedWidget, color: C) -> Self { - Self { inner, color } - } -} - -impl Widget for Background { - fn size(&self, max_width: Option, max_height: Option) -> crate::Vec2 { - self.inner.size(max_width, max_height) - } - - fn set_area(&mut self, area: crate::Rect) { - self.inner.set_area(area); - } - - fn update(&mut self, _area: crate::Rect) -> anyhow::Result<()> { - Ok(()) - } - - fn draw(self, view: &mut crate::View<'_, C>) { - for y in 0..view.size().y { - for x in 0..view.size().x { - view.set(Vec2::new(x, y), self.color); - } - } - } -} diff --git a/showbits-common/src/widgets/empty.rs b/showbits-common/src/widgets/empty.rs deleted file mode 100644 index d397c65..0000000 --- a/showbits-common/src/widgets/empty.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::{Vec2, View, Widget}; - -pub struct Empty {} - -impl Empty { - pub fn new() -> Self { - Self {} - } -} - -impl Default for Empty { - fn default() -> Self { - Self::new() - } -} - -impl Widget for Empty { - fn size(&self, _max_width: Option, _max_height: Option) -> Vec2 { - Vec2::ZERO - } - - fn draw(self, _view: &mut View<'_, C>) {} -} diff --git a/showbits-thermal-printer/src/color.rs b/showbits-thermal-printer/src/color.rs deleted file mode 100644 index 5183c8b..0000000 --- a/showbits-thermal-printer/src/color.rs +++ /dev/null @@ -1,32 +0,0 @@ -use palette::Srgb; -use showbits_common::Color; - -#[derive(Clone, Copy)] -pub enum PixelBw { - Black, - White, - Transparent, - Invert, -} - -impl Color for PixelBw { - fn to_srgb(self) -> Srgb { - match self { - Self::Black => Srgb::new(0.0, 0.0, 0.0), - Self::White => Srgb::new(1.0, 1.0, 1.0), - _ => Srgb::new(1.0, 0.0, 1.0), - } - } - - fn over(self, other: Self) -> Self { - match (self, other) { - (Self::Black, _) => Self::Black, - (Self::White, _) => Self::White, - (Self::Transparent, p) => p, - (Self::Invert, Self::Black) => Self::White, - (Self::Invert, Self::White) => Self::Black, - (Self::Invert, Self::Invert) => Self::Transparent, - (Self::Invert, Self::Transparent) => Self::Invert, - } - } -}