mirror of
https://github.com/bend-n/fimg.git
synced 2024-12-22 02:28:19 -06:00
add text to show()
This commit is contained in:
parent
8b0bb9f804
commit
f7b1c2100f
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "fimg"
|
||||
version = "0.4.23"
|
||||
version = "0.4.24"
|
||||
authors = ["bend-n <bend.n@outlook.com>"]
|
||||
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]
|
||||
|
|
BIN
data/CascadiaCode.ttf
Normal file
BIN
data/CascadiaCode.ttf
Normal file
Binary file not shown.
|
@ -6,14 +6,15 @@ fn map<const A: usize, const B: usize>(image: Image<&[u8], A>) -> Image<Box<[u8]
|
|||
where
|
||||
[u8; B]: PFrom<A>,
|
||||
{
|
||||
let buffer = image
|
||||
.chunked()
|
||||
// SAFETY: size unchanged, just change pixels
|
||||
unsafe {
|
||||
image.mapped(|buf| {
|
||||
buf.array_chunks::<A>()
|
||||
.copied()
|
||||
.flat_map(<[u8; B] as PFrom<A>>::pfrom)
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
// SAFETY: ctor
|
||||
unsafe { Image::new(image.width, image.height, buffer) }
|
||||
.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,
|
||||
|
|
|
@ -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<const N: usize, T: AsMut<[u8]> + AsRef<[u8]>> Image<T, N> {
|
||||
/// 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()
|
||||
|
|
82
src/lib.rs
82
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<T, const CHANNELS: usize> Image<T, CHANNELS> {
|
|||
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, const N: usize, F: FnOnce(&T) -> U>(&self, f: F) -> Image<U, N> {
|
||||
// SAFETY: we dont change anything, why check
|
||||
unsafe { Image::new(self.width, self.height, f(self.buffer())) }
|
||||
}
|
||||
|
||||
unsafe fn mapped<U, const N: usize, F: FnOnce(T) -> U>(self, f: F) -> Image<U, N> {
|
||||
// SAFETY: we dont change anything, why check
|
||||
unsafe { Image::new(self.width, self.height, f(self.buffer)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const CHANNELS: usize, T: Clone> Image<&[T], CHANNELS> {
|
||||
/// Allocate a new `Image<Vec<T>>` from this imageref.
|
||||
pub fn to_owned(&self) -> Image<Vec<T>, 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<const CHANNELS: usize, T: Clone> Image<&mut [T], CHANNELS> {
|
||||
/// Allocate a new `Image<Vec<T>>` from this mutable imageref.
|
||||
pub fn to_owned(&self) -> Image<Vec<T>, 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<const CHANNELS: usize> Image<&[u8], CHANNELS> {
|
|||
impl<const CHANNELS: usize, const N: usize> Image<[u8; N], CHANNELS> {
|
||||
/// Box this array image.
|
||||
pub fn boxed(self) -> Image<Box<[u8]>, 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<const CHANNELS: usize> Image<&[u8], CHANNELS> {
|
||||
/// Box this image.
|
||||
pub fn boxed(self) -> Image<Box<[u8]>, 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<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
|
|||
/// Box this owned image.
|
||||
pub fn boxed(self) -> Image<Box<[u8]>, 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<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
|||
#[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<const CHANNELS: usize> Image<Vec<u8>, 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<const CHANNELS: usize, T: ?Sized> Image<Box<T>, 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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
50
src/show.rs
50
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<const CHANNELS: usize>(i: Image<&[u8], CHANNELS>)
|
||||
where
|
||||
[u8; 4]: PFrom<CHANNELS>,
|
||||
{
|
||||
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::<Box<[u32]>, 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<Box<[u32]>, 1> {
|
|||
/// if the window is un creatable
|
||||
pub fn show(self) -> Self {
|
||||
#[cfg(feature = "real-show")]
|
||||
real::show(r(&self));
|
||||
real::show(Image::<Box<[u8]>, 4>::from(r(&self)).as_ref());
|
||||
#[cfg(not(feature = "real-show"))]
|
||||
fake::show!(Image::<Box<[u8]>, 4>::from(r(&self)));
|
||||
self
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue