mirror of
https://github.com/bend-n/fimg.git
synced 2024-12-22 02:28:19 -06:00
scale to fit
This commit is contained in:
parent
6a26b785cd
commit
418c13be5d
11
Cargo.toml
11
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "fimg"
|
||||
version = "0.4.38"
|
||||
version = "0.4.39"
|
||||
authors = ["bend-n <bend.n@outlook.com>"]
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
|
@ -27,6 +27,11 @@ wgpu = { version = "0.19.1", default-features = false, optional = true }
|
|||
atools = "0.1.0"
|
||||
qwant = { version = "1.0.0", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.53.0", features = [
|
||||
"Win32_System_Console",
|
||||
], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
iai = { git = "https://github.com/bend-n/iai.git" }
|
||||
|
||||
|
@ -60,9 +65,9 @@ scale = ["fr"]
|
|||
save = ["png"]
|
||||
text = ["fontdue"]
|
||||
blur = ["slur"]
|
||||
term = ["qwant", "save"]
|
||||
term = ["qwant", "save", "scale", "windows"]
|
||||
real-show = ["minifb", "text"]
|
||||
default = ["save", "scale"]
|
||||
default = ["save", "scale", "term"]
|
||||
wgpu-convert = ["dep:wgpu"]
|
||||
|
||||
[profile.release]
|
||||
|
|
|
@ -40,6 +40,12 @@ macro_rules! e {
|
|||
}
|
||||
use e;
|
||||
|
||||
impl<'a> std::fmt::Display for DynImage<&'a [u8]> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
e!(self, |x| crate::term::Display(*x).write(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DynImage<T> {
|
||||
/// Get the width of this image.
|
||||
pub const fn width(&self) -> u32 {
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
//! without the `real-show` feature, [`Image::show`] will save itself to your temp directory, which you may not want.
|
||||
//! - `term`: [`term::print`]. this enables printing images directly to the terminal, if you don't want to open a window or something. supports `{iterm2, kitty, sixel, fallback}` graphics.
|
||||
//! - `default`: \[`save`, `scale`\].
|
||||
#![cfg_attr(all(feature = "term", windows), windows_subsystem = "console")]
|
||||
#![feature(
|
||||
maybe_uninit_write_slice,
|
||||
hint_assert_unchecked,
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
mod bloc;
|
||||
mod kitty;
|
||||
mod sixel;
|
||||
|
||||
mod size;
|
||||
pub use bloc::Bloc;
|
||||
pub use iterm2::Iterm2;
|
||||
pub use kitty::Kitty;
|
||||
|
@ -28,6 +28,7 @@ where
|
|||
[u8; 3]: PFrom<N>,
|
||||
[u8; 4]: PFrom<N>,
|
||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||
{
|
||||
/// Display an image in the terminal.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
|
||||
|
@ -42,6 +43,7 @@ pub fn print<'a, const N: usize>(i: Image<&'a [u8], N>)
|
|||
where
|
||||
[u8; 3]: PFrom<N>,
|
||||
[u8; 4]: PFrom<N>,
|
||||
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||
{
|
||||
print!("{}", Display(i))
|
||||
|
@ -64,6 +66,7 @@ impl<'a, const N: usize> std::fmt::Debug for Display<'a, N>
|
|||
where
|
||||
[u8; 3]: PFrom<N>,
|
||||
[u8; 4]: PFrom<N>,
|
||||
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
|
||||
|
@ -73,6 +76,7 @@ where
|
|||
|
||||
impl<'a, const N: usize> std::fmt::Display for Display<'a, N>
|
||||
where
|
||||
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||
[u8; 4]: PFrom<N>,
|
||||
[u8; 3]: PFrom<N>,
|
||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||
|
@ -86,6 +90,7 @@ impl<'a, const N: usize> Display<'a, N>
|
|||
where
|
||||
[u8; 4]: PFrom<N>,
|
||||
[u8; 3]: PFrom<N>,
|
||||
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||
{
|
||||
/// Write $TERM protocol encoded image data.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{pixels::convert::PFrom, Image};
|
||||
use crate::{pixels::convert::PFrom, scale, term::size::fit, Image};
|
||||
use std::fmt::{Debug, Display, Formatter, Result, Write};
|
||||
|
||||
/// Colored `▀`s. The simple, stupid solution.
|
||||
|
@ -14,6 +14,7 @@ impl<T: AsRef<[u8]>, const N: usize> std::ops::Deref for Bloc<T, N> {
|
|||
|
||||
impl<T: AsRef<[u8]>, const N: usize> Display for Bloc<T, N>
|
||||
where
|
||||
Image<T, N>: Scaled<N>,
|
||||
[u8; 3]: PFrom<N>,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
|
@ -23,6 +24,7 @@ where
|
|||
|
||||
impl<T: AsRef<[u8]>, const N: usize> Debug for Bloc<T, N>
|
||||
where
|
||||
Image<T, N>: Scaled<N>,
|
||||
[u8; 3]: PFrom<N>,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
|
@ -30,9 +32,36 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait Scaled<const N: usize> {
|
||||
fn scaled(&self, to: (u32, u32)) -> Image<Box<[u8]>, N>;
|
||||
}
|
||||
|
||||
macro_rules! n {
|
||||
($n:literal) => {
|
||||
impl<T: AsRef<[u8]>> Scaled<$n> for Image<T, $n> {
|
||||
fn scaled(&self, (w, h): (u32, u32)) -> Image<Box<[u8]>, $n> {
|
||||
self.scale::<scale::Nearest>(w, h)
|
||||
}
|
||||
}
|
||||
};
|
||||
(o $n:literal) => {
|
||||
impl<T: AsRef<[u8]>> Scaled<$n> for Image<T, $n> {
|
||||
fn scaled(&self, (w, h): (u32, u32)) -> Image<Box<[u8]>, $n> {
|
||||
self.as_ref().to_owned().scale::<scale::Nearest>(w, h)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
n!(1);
|
||||
n!(o 2);
|
||||
n!(3);
|
||||
n!(o 4);
|
||||
|
||||
impl<T: AsRef<[u8]>, const N: usize> Bloc<T, N>
|
||||
where
|
||||
[u8; 3]: PFrom<N>,
|
||||
Image<T, N>: Scaled<N>,
|
||||
{
|
||||
/// Write out halfblocks.
|
||||
pub fn write(&self, to: &mut impl Write) -> Result {
|
||||
|
@ -43,10 +72,17 @@ where
|
|||
write!(to, "\x1b[38;2;{fr};{fg};{fb};48;2;{br};{bg};{bb}m▀")?;
|
||||
}};
|
||||
}
|
||||
// TODO: scale 2 fit
|
||||
for [a, b] in self
|
||||
let buf;
|
||||
let i = if !cfg!(test) {
|
||||
buf = self.scaled(fit((self.width(), self.height())));
|
||||
buf.as_ref()
|
||||
} else {
|
||||
self.as_ref()
|
||||
};
|
||||
|
||||
for [a, b] in i
|
||||
.flatten()
|
||||
.chunks_exact(self.width() as _)
|
||||
.chunks_exact(i.width() as _)
|
||||
.map(|x| x.iter().copied().map(<[u8; 3] as PFrom<N>>::pfrom))
|
||||
.array_chunks::<2>()
|
||||
{
|
||||
|
|
28
src/term/size.rs
Normal file
28
src/term/size.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
#[cfg(unix)]
|
||||
mod unix;
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
use std::cmp::max;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use unix::size;
|
||||
#[cfg(windows)]
|
||||
pub use windows::size;
|
||||
#[cfg(all(not(unix), not(windows)))]
|
||||
pub fn size() -> Option<(u16, u16)> {
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("unable to get terminal size");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn fit((w, h): (u32, u32)) -> (u32, u32) {
|
||||
if let Some((mw, mh)) = size().map(|(a, b)| (a as u32, b as u32)) {
|
||||
match () {
|
||||
() if w <= mw && h <= 2 * mh => (w, 2 * max(1, h / 2 + h % 2) - h % 2),
|
||||
() if mw * h <= w * 2 * mh => (mw, 2 * max(1, h * mw / w / 2) - h % 2),
|
||||
() => (w * 2 * mh / h, 2 * max(1, 2 * mh / 2) - h % 2),
|
||||
}
|
||||
} else {
|
||||
(w, h)
|
||||
}
|
||||
}
|
67
src/term/size/unix.rs
Normal file
67
src/term/size/unix.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
extern crate libc;
|
||||
use std::fs::File;
|
||||
use std::mem::MaybeUninit as MU;
|
||||
use std::ops::Deref;
|
||||
use std::os::fd::IntoRawFd;
|
||||
|
||||
use libc::*;
|
||||
|
||||
struct Fd(i32, bool);
|
||||
impl Drop for Fd {
|
||||
fn drop(&mut self) {
|
||||
if self.1 {
|
||||
unsafe { close(self.0) };
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Fd {
|
||||
pub fn new(x: File) -> Self {
|
||||
Self(x.into_raw_fd(), true)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Fd {
|
||||
fn from(value: i32) -> Self {
|
||||
Self(value, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Fd {
|
||||
type Target = i32;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size() -> Option<(u16, u16)> {
|
||||
// SAFETY: SYS
|
||||
unsafe {
|
||||
let mut size = MU::<winsize>::uninit();
|
||||
|
||||
if ioctl(
|
||||
*File::open("/dev/tty")
|
||||
.map(Fd::new)
|
||||
.unwrap_or(Fd::from(STDIN_FILENO)),
|
||||
TIOCGWINSZ.into(),
|
||||
size.as_mut_ptr(),
|
||||
) != -1
|
||||
{
|
||||
let winsize { ws_col, ws_row, .. } = size.assume_init();
|
||||
return Some((ws_col as _, ws_row as _)).filter(|&(w, h)| w != 0 && h != 0);
|
||||
}
|
||||
tput("cols").and_then(|w| tput("lines").map(|h| (w, h)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tput(arg: &'static str) -> Option<u16> {
|
||||
let x = std::process::Command::new("tput").arg(arg).output().ok()?;
|
||||
String::from_utf8(x.stdout)
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u16>().ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t() {
|
||||
println!("{:?}", size().unwrap());
|
||||
}
|
25
src/term/size/windows.rs
Normal file
25
src/term/size/windows.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use std::mem::MaybeUninit as MU;
|
||||
use windows::Win32::System::Console::{
|
||||
GetConsoleScreenBufferInfo, GetStdHandle, CONSOLE_SCREEN_BUFFER_INFO as winsize,
|
||||
SMALL_RECT as rect, STD_OUTPUT_HANDLE,
|
||||
};
|
||||
|
||||
pub fn size() -> Option<(u16, u16)> {
|
||||
// SAFETY: SYS
|
||||
unsafe {
|
||||
let mut info = MU::uninit();
|
||||
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE).ok()?, info.as_mut_ptr())
|
||||
.ok()?;
|
||||
let winsize {
|
||||
srWindow:
|
||||
rect {
|
||||
Top,
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
},
|
||||
..
|
||||
} = info.assume_init();
|
||||
Some(((Bottom - Top - 1) as u16, (Right - Left - 1) as u16))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue