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]
|
[package]
|
||||||
name = "fimg"
|
name = "fimg"
|
||||||
version = "0.4.23"
|
version = "0.4.24"
|
||||||
authors = ["bend-n <bend.n@outlook.com>"]
|
authors = ["bend-n <bend.n@outlook.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -57,7 +57,7 @@ scale = ["fr"]
|
||||||
save = ["png"]
|
save = ["png"]
|
||||||
text = ["fontdue"]
|
text = ["fontdue"]
|
||||||
blur = ["stackblur-iter"]
|
blur = ["stackblur-iter"]
|
||||||
real-show = ["minifb"]
|
real-show = ["minifb", "text"]
|
||||||
default = ["save", "scale"]
|
default = ["save", "scale"]
|
||||||
|
|
||||||
[profile.release]
|
[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
|
where
|
||||||
[u8; B]: PFrom<A>,
|
[u8; B]: PFrom<A>,
|
||||||
{
|
{
|
||||||
let buffer = image
|
// SAFETY: size unchanged, just change pixels
|
||||||
.chunked()
|
unsafe {
|
||||||
|
image.mapped(|buf| {
|
||||||
|
buf.array_chunks::<A>()
|
||||||
.copied()
|
.copied()
|
||||||
.flat_map(<[u8; B] as PFrom<A>>::pfrom)
|
.flat_map(<[u8; B] as PFrom<A>>::pfrom)
|
||||||
.collect::<Vec<_>>()
|
.collect()
|
||||||
.into();
|
})
|
||||||
// SAFETY: ctor
|
}
|
||||||
unsafe { Image::new(image.width, image.height, buffer) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! convert {
|
macro_rules! convert {
|
||||||
|
@ -64,12 +65,12 @@ boxconv!(4 => 2);
|
||||||
boxconv!(4 => 3);
|
boxconv!(4 => 3);
|
||||||
|
|
||||||
#[inline]
|
#[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)
|
((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn unpack(n: u32) -> [u8; 4] {
|
pub const fn unpack(n: u32) -> [u8; 4] {
|
||||||
[
|
[
|
||||||
((n >> 16) & 0xFF) as u8,
|
((n >> 16) & 0xFF) as u8,
|
||||||
((n >> 8) & 0xFF) as u8,
|
((n >> 8) & 0xFF) as u8,
|
||||||
|
|
|
@ -1,19 +1,60 @@
|
||||||
//! text raster
|
//! text raster
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
convert::{pack, unpack},
|
||||||
pixels::{float, Wam},
|
pixels::{float, Wam},
|
||||||
Image,
|
Image,
|
||||||
};
|
};
|
||||||
use fontdue::{layout::TextStyle, Font};
|
use fontdue::{layout::TextStyle, Font};
|
||||||
use umath::{generic_float::Constructors, FF32};
|
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> {
|
impl<const N: usize, T: AsMut<[u8]> + AsRef<[u8]>> Image<T, N> {
|
||||||
/// Draw text.
|
/// Draw text.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use fimg::Image;
|
/// # use fimg::Image;
|
||||||
/// let font = fontdue::Font::from_bytes(
|
/// let font = fontdue::Font::from_bytes(
|
||||||
/// &include_bytes!("../../tdata/CascadiaCode.ttf")[..],
|
/// &include_bytes!("../../data/CascadiaCode.ttf")[..],
|
||||||
/// fontdue::FontSettings {
|
/// fontdue::FontSettings {
|
||||||
/// scale: 200.0,
|
/// scale: 200.0,
|
||||||
/// ..Default::default()
|
/// ..Default::default()
|
||||||
|
|
82
src/lib.rs
82
src/lib.rs
|
@ -56,7 +56,8 @@
|
||||||
array_windows,
|
array_windows,
|
||||||
doc_auto_cfg,
|
doc_auto_cfg,
|
||||||
const_option,
|
const_option,
|
||||||
array_chunks
|
array_chunks,
|
||||||
|
let_chains
|
||||||
)]
|
)]
|
||||||
#![warn(
|
#![warn(
|
||||||
clippy::undocumented_unsafe_blocks,
|
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 {
|
pub unsafe fn buffer_mut(&mut self) -> &mut T {
|
||||||
&mut self.buffer
|
&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> {
|
impl<const CHANNELS: usize, T: Clone> Image<&[T], CHANNELS> {
|
||||||
/// Allocate a new `Image<Vec<T>>` from this imageref.
|
/// Allocate a new `Image<Vec<T>>` from this imageref.
|
||||||
pub fn to_owned(&self) -> Image<Vec<T>, CHANNELS> {
|
pub fn to_owned(&self) -> Image<Vec<T>, CHANNELS> {
|
||||||
// SAFETY: we have been constructed already, so must be valid
|
// SAFETY: size not changed
|
||||||
unsafe { Image::new(self.width, self.height, self.buffer.to_vec()) }
|
unsafe { self.map(|b| b.to_vec()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const CHANNELS: usize, T: Clone> Image<&mut [T], CHANNELS> {
|
impl<const CHANNELS: usize, T: Clone> Image<&mut [T], CHANNELS> {
|
||||||
/// Allocate a new `Image<Vec<T>>` from this mutable imageref.
|
/// Allocate a new `Image<Vec<T>>` from this mutable imageref.
|
||||||
pub fn to_owned(&self) -> Image<Vec<T>, CHANNELS> {
|
pub fn to_owned(&self) -> Image<Vec<T>, CHANNELS> {
|
||||||
// SAFETY: we have been constructed already, so must be valid
|
// SAFETY: size not changed
|
||||||
unsafe { Image::new(self.width, self.height, self.buffer.to_vec()) }
|
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> {
|
impl<const CHANNELS: usize, const N: usize> Image<[u8; N], CHANNELS> {
|
||||||
/// Box this array image.
|
/// Box this array image.
|
||||||
pub fn boxed(self) -> Image<Box<[u8]>, CHANNELS> {
|
pub fn boxed(self) -> Image<Box<[u8]>, CHANNELS> {
|
||||||
// SAFETY: ctor
|
// SAFETY: size not changed
|
||||||
unsafe { Image::new(self.width, self.height, Box::new(self.buffer)) }
|
unsafe { self.mapped(|b| b.into()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const CHANNELS: usize> Image<&[u8], CHANNELS> {
|
impl<const CHANNELS: usize> Image<&[u8], CHANNELS> {
|
||||||
/// Box this image.
|
/// Box this image.
|
||||||
pub fn boxed(self) -> Image<Box<[u8]>, CHANNELS> {
|
pub fn boxed(self) -> Image<Box<[u8]>, CHANNELS> {
|
||||||
// SAFETY: ctor
|
// SAFETY: size not changed
|
||||||
unsafe { Image::new(self.width, self.height, self.buffer.into()) }
|
unsafe { self.mapped(|b| b.into()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +350,7 @@ impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
|
||||||
/// Box this owned image.
|
/// Box this owned image.
|
||||||
pub fn boxed(self) -> Image<Box<[u8]>, CHANNELS> {
|
pub fn boxed(self) -> Image<Box<[u8]>, CHANNELS> {
|
||||||
// SAFETY: ctor
|
// 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]
|
#[inline]
|
||||||
fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> {
|
fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> {
|
||||||
let index = self.at(x, y);
|
let index = self.at(x, y);
|
||||||
|
debug_assert!(self.len() > index);
|
||||||
// SAFETY: as long as the buffer isnt wrong, this is 😄
|
// SAFETY: as long as the buffer isnt wrong, this is 😄
|
||||||
index..unsafe { index.unchecked_add(CHANNELS) }
|
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`].
|
/// Procure a [`ImageCloner`].
|
||||||
#[must_use = "function does not modify the original image"]
|
#[must_use = "function does not modify the original image"]
|
||||||
pub fn cloner(&self) -> ImageCloner<'_, CHANNELS> {
|
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.
|
/// Consumes and leaks this image, returning a reference to the image.
|
||||||
#[must_use = "not using the returned reference is a memory leak"]
|
#[must_use = "not using the returned reference is a memory leak"]
|
||||||
pub fn leak(self) -> Image<&'static mut [u8], CHANNELS> {
|
pub fn leak(self) -> Image<&'static mut [u8], CHANNELS> {
|
||||||
// SAFETY: ctor
|
// SAFETY: size unchanged
|
||||||
unsafe { Image::new(self.width, self.height, self.buffer.leak()) }
|
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.
|
/// Consumes and leaks this image, returning a reference to the image.
|
||||||
#[must_use = "not using the returned reference is a memory leak"]
|
#[must_use = "not using the returned reference is a memory leak"]
|
||||||
pub fn leak(self) -> Image<&'static mut T, CHANNELS> {
|
pub fn leak(self) -> Image<&'static mut T, CHANNELS> {
|
||||||
// SAFETY: ctor
|
// SAFETY: size unchanged
|
||||||
unsafe { Image::new(self.width, self.height, Box::leak(self.buffer)) }
|
unsafe { self.mapped(Box::leak) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
50
src/show.rs
50
src/show.rs
|
@ -2,10 +2,13 @@ use crate::Image;
|
||||||
|
|
||||||
#[cfg(feature = "real-show")]
|
#[cfg(feature = "real-show")]
|
||||||
mod real {
|
mod real {
|
||||||
use crate::Image;
|
use crate::{pixels::convert::PFrom, Image};
|
||||||
use minifb::{Key, Window};
|
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(
|
let mut win = Window::new(
|
||||||
"show",
|
"show",
|
||||||
i.width() as usize,
|
i.width() as usize,
|
||||||
|
@ -13,9 +16,44 @@ mod real {
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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) {
|
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");
|
.expect("window update fail");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +131,7 @@ macro_rules! show {
|
||||||
/// if the window is un creatable
|
/// if the window is un creatable
|
||||||
pub fn show(self) -> Self {
|
pub fn show(self) -> Self {
|
||||||
#[cfg(feature = "real-show")]
|
#[cfg(feature = "real-show")]
|
||||||
real::show(r(&self.as_ref().into()));
|
real::show(self.as_ref());
|
||||||
#[cfg(not(feature = "real-show"))]
|
#[cfg(not(feature = "real-show"))]
|
||||||
fake::show!(self);
|
fake::show!(self);
|
||||||
self
|
self
|
||||||
|
@ -117,7 +155,7 @@ impl Image<Box<[u32]>, 1> {
|
||||||
/// if the window is un creatable
|
/// if the window is un creatable
|
||||||
pub fn show(self) -> Self {
|
pub fn show(self) -> Self {
|
||||||
#[cfg(feature = "real-show")]
|
#[cfg(feature = "real-show")]
|
||||||
real::show(r(&self));
|
real::show(Image::<Box<[u8]>, 4>::from(r(&self)).as_ref());
|
||||||
#[cfg(not(feature = "real-show"))]
|
#[cfg(not(feature = "real-show"))]
|
||||||
fake::show!(Image::<Box<[u8]>, 4>::from(r(&self)));
|
fake::show!(Image::<Box<[u8]>, 4>::from(r(&self)));
|
||||||
self
|
self
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue