diff --git a/Cargo.toml b/Cargo.toml index 38baac3..6fd5c77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fimg" -version = "0.4.23" +version = "0.4.24" authors = ["bend-n "] license = "MIT" edition = "2021" @@ -57,7 +57,7 @@ scale = ["fr"] save = ["png"] text = ["fontdue"] blur = ["stackblur-iter"] -real-show = ["minifb"] +real-show = ["minifb", "text"] default = ["save", "scale"] [profile.release] diff --git a/data/CascadiaCode.ttf b/data/CascadiaCode.ttf new file mode 100644 index 0000000..09a679e Binary files /dev/null and b/data/CascadiaCode.ttf differ diff --git a/src/convert.rs b/src/convert.rs index 8a6d7a4..3c6c54f 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -6,14 +6,15 @@ fn map(image: Image<&[u8], A>) -> Image, { - let buffer = image - .chunked() - .copied() - .flat_map(<[u8; B] as PFrom>::pfrom) - .collect::>() - .into(); - // SAFETY: ctor - unsafe { Image::new(image.width, image.height, buffer) } + // SAFETY: size unchanged, just change pixels + unsafe { + image.mapped(|buf| { + buf.array_chunks::() + .copied() + .flat_map(<[u8; B] as PFrom>::pfrom) + .collect() + }) + } } macro_rules! convert { @@ -64,12 +65,12 @@ boxconv!(4 => 2); boxconv!(4 => 3); #[inline] -const fn pack([r, g, b, a]: [u8; 4]) -> u32 { +pub const fn pack([r, g, b, a]: [u8; 4]) -> u32 { ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) } #[inline] -const fn unpack(n: u32) -> [u8; 4] { +pub const fn unpack(n: u32) -> [u8; 4] { [ ((n >> 16) & 0xFF) as u8, ((n >> 8) & 0xFF) as u8, diff --git a/src/drawing/text.rs b/src/drawing/text.rs index 3ff0b8d..192230a 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -1,19 +1,60 @@ //! text raster use crate::{ + convert::{pack, unpack}, pixels::{float, Wam}, Image, }; use fontdue::{layout::TextStyle, Font}; use umath::{generic_float::Constructors, FF32}; +impl Image<&mut [u32], 1> { + pub(crate) fn text_u32( + &mut self, + x: u32, + y: u32, + size: f32, + font: &Font, + text: &str, + color: [u8; 4], + ) { + let mut lay = + fontdue::layout::Layout::new(fontdue::layout::CoordinateSystem::PositiveYDown); + lay.append(&[font], &TextStyle::new(text, size, 0)); + for glpyh in lay.glyphs() { + let (metrics, bitmap) = font.rasterize(glpyh.parent, size); + for i in 0..metrics.width { + for j in 0..metrics.height { + let x = x + i as u32 + glpyh.x as u32; + if x >= self.width() { + continue; + } + let y = y + j as u32 + glpyh.y as u32; + if y >= self.height() { + continue; + } + + // SAFETY: the rasterizer kinda promises that metrics width and height are in bounds + let fill = unsafe { float(*bitmap.get_unchecked(j * metrics.width + i)) }; + // SAFETY: we clampin + let bg = unsafe { unpack(*self.buffer.get_unchecked(self.at(x, y))) }; + // SAFETY: see above + *unsafe { self.buffer.get_unchecked_mut(self.at(x, y)) } = + // SAFETY: fill is 0..=1 + pack(unsafe { bg.wam(color, FF32::one() - fill, fill) }); + } + } + } + } +} + impl + AsRef<[u8]>> Image { /// Draw text. /// /// ``` /// # use fimg::Image; /// let font = fontdue::Font::from_bytes( - /// &include_bytes!("../../tdata/CascadiaCode.ttf")[..], + /// &include_bytes!("../../data/CascadiaCode.ttf")[..], /// fontdue::FontSettings { /// scale: 200.0, /// ..Default::default() diff --git a/src/lib.rs b/src/lib.rs index 1882368..f4317a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,8 @@ array_windows, doc_auto_cfg, const_option, - array_chunks + array_chunks, + let_chains )] #![warn( clippy::undocumented_unsafe_blocks, @@ -243,21 +244,52 @@ impl Image { pub unsafe fn buffer_mut(&mut self) -> &mut T { &mut self.buffer } + + /// # Safety + /// + /// the output index is not guranteed to be in bounds + #[inline] + fn at(&self, x: u32, y: u32) -> usize { + debug_assert!(x < self.width(), "x out of bounds"); + debug_assert!(y < self.height(), "y out of bounds"); + #[allow(clippy::multiple_unsafe_ops_per_block)] + // SAFETY: me when uncheck math: 😧 (FIXME) + let index = unsafe { + let w = self.width(); + // y * w + x + let tmp = (y as usize).unchecked_mul(w as usize); + tmp.unchecked_add(x as usize) + }; + // SAFETY: 🧐 is unsound? 😖 + unsafe { index.unchecked_mul(CHANNELS) } + } + + /// # Safety + /// keep the buffer size the same + unsafe fn map U>(&self, f: F) -> Image { + // SAFETY: we dont change anything, why check + unsafe { Image::new(self.width, self.height, f(self.buffer())) } + } + + unsafe fn mapped U>(self, f: F) -> Image { + // SAFETY: we dont change anything, why check + unsafe { Image::new(self.width, self.height, f(self.buffer)) } + } } impl Image<&[T], CHANNELS> { /// Allocate a new `Image>` from this imageref. pub fn to_owned(&self) -> Image, CHANNELS> { - // SAFETY: we have been constructed already, so must be valid - unsafe { Image::new(self.width, self.height, self.buffer.to_vec()) } + // SAFETY: size not changed + unsafe { self.map(|b| b.to_vec()) } } } impl Image<&mut [T], CHANNELS> { /// Allocate a new `Image>` from this mutable imageref. pub fn to_owned(&self) -> Image, CHANNELS> { - // SAFETY: we have been constructed already, so must be valid - unsafe { Image::new(self.width, self.height, self.buffer.to_vec()) } + // SAFETY: size not changed + unsafe { self.map(|b| b.to_vec()) } } } @@ -301,16 +333,16 @@ impl Image<&[u8], CHANNELS> { impl Image<[u8; N], CHANNELS> { /// Box this array image. pub fn boxed(self) -> Image, CHANNELS> { - // SAFETY: ctor - unsafe { Image::new(self.width, self.height, Box::new(self.buffer)) } + // SAFETY: size not changed + unsafe { self.mapped(|b| b.into()) } } } impl Image<&[u8], CHANNELS> { /// Box this image. pub fn boxed(self) -> Image, CHANNELS> { - // SAFETY: ctor - unsafe { Image::new(self.width, self.height, self.buffer.into()) } + // SAFETY: size not changed + unsafe { self.mapped(|b| b.into()) } } } @@ -318,7 +350,7 @@ impl Image, CHANNELS> { /// Box this owned image. pub fn boxed(self) -> Image, CHANNELS> { // SAFETY: ctor - unsafe { Image::new(self.width, self.height, self.buffer.into_boxed_slice()) } + unsafe { self.mapped(|b| b.into()) } } } @@ -367,31 +399,11 @@ impl, const CHANNELS: usize> Image { #[inline] fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> { let index = self.at(x, y); + debug_assert!(self.len() > index); // SAFETY: as long as the buffer isnt wrong, this is 😄 index..unsafe { index.unchecked_add(CHANNELS) } } - /// # Safety - /// - /// the output index is not guranteed to be in bounds - #[inline] - fn at(&self, x: u32, y: u32) -> usize { - debug_assert!(x < self.width(), "x out of bounds"); - debug_assert!(y < self.height(), "y out of bounds"); - #[allow(clippy::multiple_unsafe_ops_per_block)] - // SAFETY: me when uncheck math: 😧 (FIXME) - let index = unsafe { - let w = self.width(); - // y * w + x - let tmp = (y as usize).unchecked_mul(w as usize); - tmp.unchecked_add(x as usize) - }; - // SAFETY: 🧐 is unsound? 😖 - let index = unsafe { index.unchecked_mul(CHANNELS) }; - debug_assert!(self.len() > index); - index - } - /// Procure a [`ImageCloner`]. #[must_use = "function does not modify the original image"] pub fn cloner(&self) -> ImageCloner<'_, CHANNELS> { @@ -539,8 +551,8 @@ impl Image, CHANNELS> { /// Consumes and leaks this image, returning a reference to the image. #[must_use = "not using the returned reference is a memory leak"] pub fn leak(self) -> Image<&'static mut [u8], CHANNELS> { - // SAFETY: ctor - unsafe { Image::new(self.width, self.height, self.buffer.leak()) } + // SAFETY: size unchanged + unsafe { self.mapped(Vec::leak) } } } @@ -548,8 +560,8 @@ impl Image, CHANNELS> { /// Consumes and leaks this image, returning a reference to the image. #[must_use = "not using the returned reference is a memory leak"] pub fn leak(self) -> Image<&'static mut T, CHANNELS> { - // SAFETY: ctor - unsafe { Image::new(self.width, self.height, Box::leak(self.buffer)) } + // SAFETY: size unchanged + unsafe { self.mapped(Box::leak) } } } diff --git a/src/show.rs b/src/show.rs index a7a1220..25040cb 100644 --- a/src/show.rs +++ b/src/show.rs @@ -2,10 +2,13 @@ use crate::Image; #[cfg(feature = "real-show")] mod real { - use crate::Image; + use crate::{pixels::convert::PFrom, Image}; use minifb::{Key, Window}; - pub fn show(i: Image<&[u32], 1>) { + pub fn show(i: Image<&[u8], CHANNELS>) + where + [u8; 4]: PFrom, + { let mut win = Window::new( "show", i.width() as usize, @@ -13,9 +16,44 @@ mod real { Default::default(), ) .unwrap(); - win.limit_update_rate(Some(std::time::Duration::from_millis(100))); + let font = fontdue::Font::from_bytes( + &include_bytes!("../data/CascadiaCode.ttf")[..], + fontdue::FontSettings { + scale: 12.0, + ..Default::default() + }, + ) + .unwrap(); while win.is_open() && !win.is_key_down(Key::Q) && !win.is_key_down(Key::Escape) { - win.update_with_buffer(&i.buffer, i.width() as usize, i.height() as usize) + let mut buf = Image::, 1>::from(i.as_ref()); + + if !win.is_key_down(Key::H) + && let Some((x, y)) = win + .get_mouse_pos(minifb::MouseMode::Discard) + .map(|(x, y)| (x.round() as u32, y.round() as u32)) + .map(|(x, y)| (x.min(i.width()), y.min(i.height()))) + { + // SAFETY: ctor + unsafe { Image::new(buf.width, buf.height, &mut *buf.buffer) }.text_u32( + 5, + i.height() - 20, + 12.0, + &font, + &format!( + "P ({x}, {y}), {}", + // SAFETY: clampd + match unsafe { &i.pixel(x, y)[..] } { + [y] => format!("(Y {y})"), + [y, a] => format!("(Y {y} A {a})"), + [r, g, b] => format!("(R {r} G {g} B {b})"), + [r, g, b, a] => format!("(R {r} G {g} B {b} A {a})"), + _ => unreachable!(), + } + ), + [238, 232, 213, 255], + ) + } + win.update_with_buffer(&buf.buffer, i.width() as usize, i.height() as usize) .expect("window update fail"); } } @@ -93,7 +131,7 @@ macro_rules! show { /// if the window is un creatable pub fn show(self) -> Self { #[cfg(feature = "real-show")] - real::show(r(&self.as_ref().into())); + real::show(self.as_ref()); #[cfg(not(feature = "real-show"))] fake::show!(self); self @@ -117,7 +155,7 @@ impl Image, 1> { /// if the window is un creatable pub fn show(self) -> Self { #[cfg(feature = "real-show")] - real::show(r(&self)); + real::show(Image::, 4>::from(r(&self)).as_ref()); #[cfg(not(feature = "real-show"))] fake::show!(Image::, 4>::from(r(&self))); self diff --git a/tdata/CascadiaCode.ttf b/tdata/CascadiaCode.ttf deleted file mode 100644 index b47bf63..0000000 Binary files a/tdata/CascadiaCode.ttf and /dev/null differ diff --git a/tdata/blurred_pentagon.imgbuf b/tdata/blurred_pentagon.imgbuf index 9580840..a73a99c 100644 Binary files a/tdata/blurred_pentagon.imgbuf and b/tdata/blurred_pentagon.imgbuf differ