mirror of
https://github.com/bend-n/fimg.git
synced 2024-12-22 10:28:21 -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]
|
[package]
|
||||||
name = "fimg"
|
name = "fimg"
|
||||||
version = "0.4.38"
|
version = "0.4.39"
|
||||||
authors = ["bend-n <bend.n@outlook.com>"]
|
authors = ["bend-n <bend.n@outlook.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -27,6 +27,11 @@ wgpu = { version = "0.19.1", default-features = false, optional = true }
|
||||||
atools = "0.1.0"
|
atools = "0.1.0"
|
||||||
qwant = { version = "1.0.0", optional = true }
|
qwant = { version = "1.0.0", optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
windows = { version = "0.53.0", features = [
|
||||||
|
"Win32_System_Console",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
iai = { git = "https://github.com/bend-n/iai.git" }
|
iai = { git = "https://github.com/bend-n/iai.git" }
|
||||||
|
|
||||||
|
@ -60,9 +65,9 @@ scale = ["fr"]
|
||||||
save = ["png"]
|
save = ["png"]
|
||||||
text = ["fontdue"]
|
text = ["fontdue"]
|
||||||
blur = ["slur"]
|
blur = ["slur"]
|
||||||
term = ["qwant", "save"]
|
term = ["qwant", "save", "scale", "windows"]
|
||||||
real-show = ["minifb", "text"]
|
real-show = ["minifb", "text"]
|
||||||
default = ["save", "scale"]
|
default = ["save", "scale", "term"]
|
||||||
wgpu-convert = ["dep:wgpu"]
|
wgpu-convert = ["dep:wgpu"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -40,6 +40,12 @@ macro_rules! e {
|
||||||
}
|
}
|
||||||
use 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> {
|
impl<T> DynImage<T> {
|
||||||
/// Get the width of this image.
|
/// Get the width of this image.
|
||||||
pub const fn width(&self) -> u32 {
|
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.
|
//! 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.
|
//! - `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`\].
|
//! - `default`: \[`save`, `scale`\].
|
||||||
|
#![cfg_attr(all(feature = "term", windows), windows_subsystem = "console")]
|
||||||
#![feature(
|
#![feature(
|
||||||
maybe_uninit_write_slice,
|
maybe_uninit_write_slice,
|
||||||
hint_assert_unchecked,
|
hint_assert_unchecked,
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
mod bloc;
|
mod bloc;
|
||||||
mod kitty;
|
mod kitty;
|
||||||
mod sixel;
|
mod sixel;
|
||||||
|
mod size;
|
||||||
pub use bloc::Bloc;
|
pub use bloc::Bloc;
|
||||||
pub use iterm2::Iterm2;
|
pub use iterm2::Iterm2;
|
||||||
pub use kitty::Kitty;
|
pub use kitty::Kitty;
|
||||||
|
@ -28,6 +28,7 @@ where
|
||||||
[u8; 3]: PFrom<N>,
|
[u8; 3]: PFrom<N>,
|
||||||
[u8; 4]: PFrom<N>,
|
[u8; 4]: PFrom<N>,
|
||||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||||
|
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||||
{
|
{
|
||||||
/// Display an image in the terminal.
|
/// Display an image in the terminal.
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
|
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
|
where
|
||||||
[u8; 3]: PFrom<N>,
|
[u8; 3]: PFrom<N>,
|
||||||
[u8; 4]: PFrom<N>,
|
[u8; 4]: PFrom<N>,
|
||||||
|
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||||
{
|
{
|
||||||
print!("{}", Display(i))
|
print!("{}", Display(i))
|
||||||
|
@ -64,6 +66,7 @@ impl<'a, const N: usize> std::fmt::Debug for Display<'a, N>
|
||||||
where
|
where
|
||||||
[u8; 3]: PFrom<N>,
|
[u8; 3]: PFrom<N>,
|
||||||
[u8; 4]: PFrom<N>,
|
[u8; 4]: PFrom<N>,
|
||||||
|
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
|
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>
|
impl<'a, const N: usize> std::fmt::Display for Display<'a, N>
|
||||||
where
|
where
|
||||||
|
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||||
[u8; 4]: PFrom<N>,
|
[u8; 4]: PFrom<N>,
|
||||||
[u8; 3]: PFrom<N>,
|
[u8; 3]: PFrom<N>,
|
||||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||||
|
@ -86,6 +90,7 @@ impl<'a, const N: usize> Display<'a, N>
|
||||||
where
|
where
|
||||||
[u8; 4]: PFrom<N>,
|
[u8; 4]: PFrom<N>,
|
||||||
[u8; 3]: PFrom<N>,
|
[u8; 3]: PFrom<N>,
|
||||||
|
Image<&'a [u8], N>: bloc::Scaled<N>,
|
||||||
Image<&'a [u8], N>: kitty::Data + WritePng,
|
Image<&'a [u8], N>: kitty::Data + WritePng,
|
||||||
{
|
{
|
||||||
/// Write $TERM protocol encoded image data.
|
/// 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};
|
use std::fmt::{Debug, Display, Formatter, Result, Write};
|
||||||
|
|
||||||
/// Colored `▀`s. The simple, stupid solution.
|
/// 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>
|
impl<T: AsRef<[u8]>, const N: usize> Display for Bloc<T, N>
|
||||||
where
|
where
|
||||||
|
Image<T, N>: Scaled<N>,
|
||||||
[u8; 3]: PFrom<N>,
|
[u8; 3]: PFrom<N>,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
|
@ -23,6 +24,7 @@ where
|
||||||
|
|
||||||
impl<T: AsRef<[u8]>, const N: usize> Debug for Bloc<T, N>
|
impl<T: AsRef<[u8]>, const N: usize> Debug for Bloc<T, N>
|
||||||
where
|
where
|
||||||
|
Image<T, N>: Scaled<N>,
|
||||||
[u8; 3]: PFrom<N>,
|
[u8; 3]: PFrom<N>,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
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>
|
impl<T: AsRef<[u8]>, const N: usize> Bloc<T, N>
|
||||||
where
|
where
|
||||||
[u8; 3]: PFrom<N>,
|
[u8; 3]: PFrom<N>,
|
||||||
|
Image<T, N>: Scaled<N>,
|
||||||
{
|
{
|
||||||
/// Write out halfblocks.
|
/// Write out halfblocks.
|
||||||
pub fn write(&self, to: &mut impl Write) -> Result {
|
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▀")?;
|
write!(to, "\x1b[38;2;{fr};{fg};{fb};48;2;{br};{bg};{bb}m▀")?;
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
// TODO: scale 2 fit
|
let buf;
|
||||||
for [a, b] in self
|
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()
|
.flatten()
|
||||||
.chunks_exact(self.width() as _)
|
.chunks_exact(i.width() as _)
|
||||||
.map(|x| x.iter().copied().map(<[u8; 3] as PFrom<N>>::pfrom))
|
.map(|x| x.iter().copied().map(<[u8; 3] as PFrom<N>>::pfrom))
|
||||||
.array_chunks::<2>()
|
.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