From a6683fadc45331b1496d417dad285b1d62e79104 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 2 May 2023 23:50:03 +0800 Subject: [PATCH] =?UTF-8?q?Update=20version=20to=20v2.1.0=20=09=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=9A=20=20=20=20=20Cargo.toml=20=09=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=9A=20=20=20=20=20README.md=20=09=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=9A=20=20=20=20=20UpdateLog.md=20=09=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=9A=20=20=20=20=20src/app.rs=20=09=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=9A=20=20=20=20=20src/lib.rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 6 +- README.md | 5 +- UpdateLog.md | 10 +++ src/app.rs | 158 +++++++++++++++++++++++++++++------------ src/lib.rs | 194 +++++++++++++++++++++++++++++++++------------------ 5 files changed, 256 insertions(+), 117 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 65cea2f..e53eb5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "timg" -version = "2.0.1" +version = "2.1.0" edition = "2021" authors = ["A4-Tacks "] -keywords = ["terminal", "image", "term", "tui", "viewer"] -description = "Picture viewer for terminal (VT100)." +keywords = ["terminal", "image", "ansi_term", "tui", "viewer"] +description = "Interactive Image Viewer on Terminal (xterm)" categories = ["command-line-utilities"] license = "MIT" diff --git a/README.md b/README.md index f7cd8a1..81bcd89 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -The image viewer on the terminal is based on the VT100 standard. +The image viewer on the terminal is based on the xterm standard. +This is an interactive image viewer with very fast IO speed on the terminal, utilizing incremental output and other methods to significantly reduce IO data. It can perform well on low-speed terminals. # Functions - Zoom Picture @@ -20,4 +21,4 @@ The image viewer on the terminal is based on the VT100 standard. # Info crate: -Due to the current implementation of reading one character at a time being unavailable on Windows, it may result in the software being unavailable or not meeting expectations on Windows. +Due to the current implementation of the software reading one character at a time appearing to be unavailable on Windows systems, it may result in the software being unavailable or not meeting your expectations when running on Windows systems. diff --git a/UpdateLog.md b/UpdateLog.md index e7a5765..cc1e7f8 100644 --- a/UpdateLog.md +++ b/UpdateLog.md @@ -1,3 +1,13 @@ # v2.0.1 The library used `term_lattice` has fixed some minor issues and will use its updated version Optimized the background color display in the help interface, and the transparent background color in the test was re added as the bug was fixed + +# v2.1.0 +## Add Functions +- InitRatio +- UnitRatio +- RC ErrorCode + +Some optimizations have been made +Modified the position reference for zooming, no longer the top left corner but the center of the screen +Now the `Control+C` key can also be used to exit diff --git a/src/app.rs b/src/app.rs index c409742..e54b5fb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -25,10 +25,7 @@ use timg::{ Position, Float, }; -use image::{ - imageops::FilterType, - ColorType -}; +use image::imageops::FilterType; const FILTERS: &[FilterType] = &[ @@ -39,7 +36,6 @@ const FILTERS: &[FilterType] = &[ pub type Rgba = [u8; 4]; - /// RGBA color to RGB color /// # Examples /// ``` @@ -68,7 +64,7 @@ pub fn rgba_to_rgb(foreground: Rgba, background: Rgb) -> Rgb { } -#[macro_export] +/// 输出信息 macro_rules! log { (e:($code:expr) $( $x:expr ),* ) => {{ log!(e $($x),* ); @@ -79,6 +75,27 @@ macro_rules! log { ESC, format!($($x),*) )) } +/// 声明一个可变的变量, 并且在声明时声明一个宏用于之后的重复初始化 +/// 可同时声明多次 +macro_rules! new_and_init_macro { + ( + $( + let mut $name:ident $(: $ty:ty )? + => $init_macro:ident = $value:expr; + )* + ) => { + $( + let mut $name $(: $ty)?; + macro_rules! $init_macro { + () => { + $name = $value; + } + } + $init_macro!(); + )* + }; +} + pub fn run(matches: ArgMatches) { macro_rules! get_value { @@ -179,21 +196,11 @@ pub fn run(matches: ArgMatches) { let mut stdin = stdin().into_raw_mode().unwrap_or_else(|e| { log!(e:(2) "GetStdInError: {}", e); }); - let is_alpha: bool = match repr_img.color() { - ColorType::L8 | ColorType::L16 - | ColorType::Rgb8 | ColorType::Rgb16 - | ColorType::Rgb32F - => false, - ColorType::La8 - | ColorType::La16| ColorType::Rgba8 - | ColorType::Rgba16 | ColorType::Rgba32F - => true, - t => log!(e:(2) "ColorTypeError: {:?}", t), - }; + let is_alpha: bool = repr_img.color().has_alpha(); let mut is_start: bool = true; let mut readbuf: [u8; 1] = [0]; - 'main: loop { - let mut term_size: Position + 'main: loop { // 部分参数初始化将在这个头部进行 + let mut term_size: Position /* 终端的大小, 按像素算 */ = Position::from(if let Some(size) = set_term_size { [size.x, size.y * 2] } else { @@ -209,16 +216,21 @@ pub fn run(matches: ArgMatches) { if is_start { eprint!("\x1b[{}S", term_size.y >> 1); // 滚动一个屏幕, 以空出空间 } - clear_screen!(); term_size.y -= 2; // 缩小终端大小一文本行以留给状态行 - let mut scale: Float = get_scale(term_size, img_size); - let mut win_pos: Position = Position::default(); // 在图片中的绝对像素 + let full_scale: Float = get_scale(term_size, img_size); + clear_screen!(); + new_and_init_macro!{ + // scale alias ratio. + let mut scale: Float => init_scale = full_scale; + let mut back_ground_color_idx => init_back_ground_color_idx = 0; + let mut win_pos: Position => init_win_pos = Position::default(); // 在图片中的绝对像素 + } let mut screen_buf: ScreenBuffer = ScreenBuffer::new(term_size.into_array()); screen_buf.cfg.chromatic_aberration = default_opt_level; - let mut back_ground_color_idx = 0; let mut filter_idx = 4; let [mut grayscale, mut invert] = [false; 2]; + let mut error_buf: String = String::new(); loop { screen_buf.cfg.default_color = back_grounds[back_ground_color_idx]; @@ -278,17 +290,19 @@ pub fn run(matches: ArgMatches) { "\x1b[7m", "ImgSize[{}x{}] ", "Pos[{},{}] ", - "Scale[{:.2}] ", + "Ratio[{:.2}] ", "Opt[{}] ", "Fl[{}] ", "Help(H) ", "Quit(Q)", - "\x1b[0m"), + "\x1b[0m\x1b[s{}\x1b[K\x1b[u"), img_size.x, img_size.y, win_pos.x, win_pos.y, scale, screen_buf.cfg.chromatic_aberration, - filter_idx); + filter_idx, + error_buf); + error_buf.clear(); eprint!("\x1b[H{}{}", screen_buf.flush(false), status_line); is_start = false; macro_rules! read_char { @@ -309,10 +323,30 @@ pub fn run(matches: ArgMatches) { }; macro_rules! ctrl_err { ( $( $x:expr ),* ) => { - eprint!("\x07 \x1b[101m{}\x1b[0m", - format!( $( $x ),* )) + error_buf.extend( + format!("\x07 \x1b[101m{}\x1b[0m", + format!( $( $x ),* )).chars()) }; } + /// <: new < old + /// >: new > old + /// note 一轮中仅可运行一次, + /// 并且仅在 scale_term_size 未改变, scale 已改变时使用 + macro_rules! fix_pos { + (<) => {{ + win_pos += (scale_term_size + - term_size.mul_scale(scale)) + >> 1.into(); + }}; + (>) => {{ + let old: Position = win_pos; + win_pos -= (term_size.mul_scale(scale) + - scale_term_size) + >> 1.into(); + if win_pos.x > old.x { win_pos.x = 0 } + if win_pos.y > old.y { win_pos.y = 0 } + }}; + } let [moveb_wlen, moveb_hlen] = [ (scale_term_size.x as Float * short_move_ratio).ceil() as SizeType, (scale_term_size.y as Float * short_move_ratio).ceil() as SizeType @@ -321,13 +355,14 @@ pub fn run(matches: ArgMatches) { (scale_term_size.x as Float * long_move_ratio).ceil() as SizeType, (scale_term_size.y as Float * long_move_ratio).ceil() as SizeType ]; - match readbuf[0] as char { + // 将在此处阻塞等待输入 + match readbuf[0] as char { // 处理读入的单个字符 'r' => { screen_buf.init_bg_colors(); clear_screen!(); }, 'R' => continue 'main, - 'Q' => break, /* exit */ + 'Q' | '\x03' => break, /* exit */ 'h' => { let old = win_pos.x; win_pos.x -= move_len; @@ -383,22 +418,38 @@ pub fn run(matches: ArgMatches) { } }, 'D' => win_pos.x += movec_wlen, - '+' | 'c' => scale *= zoom_sub_ratio, - '-' | 'x' => scale *= zoom_add_ratio, - 'o' => { /* opt */ + + // 缩放 + // 放大(比例与视区缩小 new < old) p += (old - new) >> 1 + '+' | 'c' => { + scale *= zoom_sub_ratio; + fix_pos!(<); + }, + // 缩小(比例与视区放大 old < new) p -= (new - old) >> 1 + '-' | 'x' => { + scale *= zoom_add_ratio; + if scale > full_scale { + // 防止将图片缩的过小 + init_scale!(); + ctrl_err!("RC"); + } + fix_pos!(>); + }, + + 'o' => { /* opt add */ screen_buf.cfg.chromatic_aberration += 1; } - 'O' => { /* opt */ + 'O' => { /* opt add */ screen_buf.cfg.chromatic_aberration += 10; } - 'i' => { /* opt */ + 'i' => { /* opt sub */ if screen_buf.cfg.chromatic_aberration != 0 { screen_buf.cfg.chromatic_aberration -= 1 } else { ctrl_err!("FV") }; } - 'I' => { /* opt */ + 'I' => { /* opt sub */ if screen_buf.cfg.chromatic_aberration >= 10 { screen_buf.cfg.chromatic_aberration -= 10 } else { @@ -411,7 +462,7 @@ pub fn run(matches: ArgMatches) { back_ground_color_idx %= back_grounds.len(); }, 'Z' => { - back_ground_color_idx = 0; + init_back_ground_color_idx!(); }, 'f' => { filter_idx += 1; @@ -423,13 +474,26 @@ pub fn run(matches: ArgMatches) { 'Y' => repr_img = repr_img.rotate270(), 'm' => invert = ! invert, 'M' => grayscale = ! grayscale, + 'X' => { + init_scale!(); + init_win_pos!(); + }, + 'C' => { + let old_scale = scale; + scale = 1.0; + if scale < old_scale { + fix_pos!(<) + } else { + fix_pos!(>) + } + }, 'H' | '?' => { // help clear_screen!(); eprintln!("\x1b[H"); macro_rules! outlines { - ( $( $fmt:tt $( , $( $x:expr ),+ )? ; )* ) => { + ( $( $fmt:expr $( , $( $x:expr ),+ )? ; )* ) => { $( eprint!( concat!("\x1b[G", $fmt, "\n\x1b[G") @@ -441,22 +505,29 @@ pub fn run(matches: ArgMatches) { .map(|x| x.fmt_color()) .collect::>().join(", "); outlines!{ - "{0}Help{0}", "-".repeat(((term_size.x - 4) >> 1) as usize); - "Move: move px:`hjkl`, move 1/4 term: `aswd`, move 3/4 term: `ASWD`, s/l ratio: ({:.2},{:.2})", + "{0}Help{0}", "-".repeat( + ((term_size.x - 4) >> 1) as usize); + concat!( + "Move: move px:`hjkl`, move 1/4 term: `aswd`, ", + "move 3/4 term: `ASWD`, s/l ratio: ({:.2},{:.2})"), short_move_ratio, long_move_ratio; "Opt: add opt: `oO`, sub opt: `iI`"; - "Zoom: `cx` or `+-`, ratio: {:.4},{:.4}", zoom_add_ratio, zoom_sub_ratio; + "Zoom: `cx` or `+-`, ratio: {:.4},{:.4}", + zoom_add_ratio, zoom_sub_ratio; "ReDraw: `r`"; "ReInit: `R`"; "SwitchBackground: `z` [{}]", bgs_fmt; "InitBackground: `Z`"; - "SetFilter: `f`, ({:?}) {:?}", FILTERS[filter_idx], FILTERS; + "SetFilter: `f`, ({:?}) {:?}", + FILTERS[filter_idx], FILTERS; "FlipImage: `gG`"; "Rotate: `yY`"; "Invert: `m`"; "Grayscale: `M`"; "ThisHelpInfo: `H?`"; - "Quit: `Q`"; + "InitRatio: `X`"; + "UnitRatio: `C`"; + "Quit: `Q` or `Ctrl-C`"; }; eprint!("\x1b[{}H", (term_size.y >> 1) + 1); @@ -468,7 +539,6 @@ pub fn run(matches: ArgMatches) { ctrl_err!("EI:{:?}", c) }, } - eprint!("\x1b[K"); // 清除残留状态行 } break; } diff --git a/src/lib.rs b/src/lib.rs index e786675..f063676 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,44 @@ -use term_lattice::Color; - pub type SizeType = u32; pub type Float = f64; +mod traits { + use term_lattice::Color; + + pub trait FmtColor { + fn fmt_color(&self) -> String; + } + impl FmtColor for Color { + /// format color + /// # Examples + /// ``` + /// # use term_lattice::Color; + /// # use timg::FmtColor; + /// assert_eq!(Color::None.fmt_color(), "None"); + /// assert_eq!(Color::Rgb([0xff, 0x1b, 0x0a]).fmt_color(), + /// "\x1b[48;2;255;27;10m#\x1b[49mFF1B0A"); + /// assert_eq!(Color::C256(84).fmt_color(), "\x1b[48;5;84mC\x1b[49m084"); + /// ``` + fn fmt_color(&self) -> String { + match self { + Self::None => format!("None"), + Self::Rgb(x) + => format!("\x1b[48;2;{0};{1};{2}m#\x1b[49m{:02X}{:02X}{:02X}", + x[0], x[1], x[2]), + Self::C256(x) => format!("\x1b[48;5;{0}mC\x1b[49m{:03}", x), + } + } + } + + pub trait IsAlpha { + fn is_alpha(&self) -> bool; + } +} +pub use traits::*; + + /// as float +#[macro_export] macro_rules! asf { ( $($x:expr),* ) => { ( $($x as $crate::Float),* ) @@ -12,53 +46,103 @@ macro_rules! asf { } -/// 一个位置 (x, y) -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Position { - pub x: SizeType, - pub y: SizeType, -} -impl Position { - pub fn new(x: SizeType, y: SizeType) -> Self { - Self { x, y } +mod position { + use std::ops; + + use super::*; + + /// 一个位置 (x, y) + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct Position { + pub x: SizeType, + pub y: SizeType, + } + impl Position { + pub fn new(x: SizeType, y: SizeType) -> Self { + Self { x, y } + } + pub fn into_array(self) -> [SizeType; 2] { + [self.x, self.y] + } + /// 这不会改变原数据, 而是从栈上 copy 一份 + /// # Examples + /// ``` + /// use timg::Position; + /// let a = Position::new(2, 3); + /// let b = Position::new(6, 9); + /// assert_eq!(a.mul_scale(3.0), b); + /// assert_eq!(a.mul_scale(3.0), b); + /// ``` + pub fn mul_scale(mut self, num: Float) -> Self { + let (mut x, mut y) = asf!(self.x, self.y); + x *= num; + y *= num; + self.x = x as SizeType; + self.y = y as SizeType; + self + } } - pub fn into_array(self) -> [SizeType; 2] { - [self.x, self.y] + impl From<[SizeType; 2]> for Position { + fn from(value: [SizeType; 2]) -> Self { + Self::new(value[0], value[1]) + } } - /// 这不会改变原数据, 而是从栈上 copy 一份 - /// # Examples - /// ``` - /// use timg::Position; - /// let a = Position::new(2, 3); - /// let b = Position::new(6, 9); - /// assert_eq!(a.mul_scale(3.0), b); - /// assert_eq!(a.mul_scale(3.0), b); - /// ``` - pub fn mul_scale(mut self, num: Float) -> Self { - let (mut x, mut y) = asf!(self.x, self.y); - x *= num; - y *= num; - self.x = x as SizeType; - self.y = y as SizeType; - self + impl From<(SizeType, SizeType)> for Position { + fn from(value: (SizeType, SizeType)) -> Self { + Self::new(value.0, value.1) + } } -} -impl From<[SizeType; 2]> for Position { - fn from(value: [SizeType; 2]) -> Self { - Self::new(value[0], value[1]) + impl From for Position { + fn from(value: SizeType) -> Self { + Self::new(value, value) + } } -} -impl From<(SizeType, SizeType)> for Position { - fn from(value: (SizeType, SizeType)) -> Self { - Self::new(value.0, value.1) + impl Default for Position { + /// (0, 0) + fn default() -> Self { + Self::new(0, 0) + } } -} -impl Default for Position { - /// (0, 0) - fn default() -> Self { - Self::new(0, 0) + macro_rules! impl_ops { + (($oper:tt) => + [$tname:path, $fname:ident], + [$tname1:path, $fname1:ident]) => { + impl $tname for Position { + type Output = Self; + fn $fname(mut self, rhs: Self) -> Self::Output { + self.x $oper rhs.x; + self.y $oper rhs.y; + self + } + } + impl $tname1 for Position { + fn $fname1(&mut self, rhs: Self) { + self.x $oper rhs.x; + self.y $oper rhs.y; + } + } + }; + } + impl_ops!((+=) => [ops::Add, add], [ops::AddAssign, add_assign]); + impl_ops!((-=) => [ops::Sub, sub], [ops::SubAssign, sub_assign]); + impl_ops!((*=) => [ops::Mul, mul], [ops::MulAssign, mul_assign]); + impl_ops!((/=) => [ops::Div, div], [ops::DivAssign, div_assign]); + impl_ops!((%=) => [ops::Rem, rem], [ops::RemAssign, rem_assign]); + impl_ops!((>>=) => [ops::Shr, shr], [ops::ShrAssign, shr_assign]); + impl_ops!((<<=) => [ops::Shl, shl], [ops::ShlAssign, shl_assign]); + impl_ops!((&=) => [ops::BitAnd, bitand], [ops::BitAndAssign, bitand_assign]); + impl_ops!((|=) => [ops::BitOr, bitor], [ops::BitOrAssign, bitor_assign]); + impl_ops!((^=) => [ops::BitXor, bitxor], [ops::BitXorAssign, bitxor_assign]); + + #[test] + fn test() { + let mut a = Position::new(3, 8); + assert_eq!(a + Position::new(4, 1), Position::new(7, 9)); + a += Position::new(4, 1); + assert_eq!(a, Position::new(7, 9)); } } +pub use position::*; /// 获取要将图片大小缩到刚好放进终端大小时, 终端大小须乘的比例 /// 终端大小 * 比例 得到刚好包括整个图片的大小 @@ -170,29 +254,3 @@ pub fn num_to_rgb(num: u32) -> [u8; 3] { ((num >> 8) & 0xff) as u8, (num & 0xff) as u8] } - - -pub trait FmtColor { - fn fmt_color(&self) -> String; -} -impl FmtColor for Color { - /// format color - /// # Examples - /// ``` - /// # use term_lattice::Color; - /// # use timg::FmtColor; - /// assert_eq!(Color::None.fmt_color(), "None"); - /// assert_eq!(Color::Rgb([0xff, 0x1b, 0x0a]).fmt_color(), - /// "\x1b[48;2;255;27;10m#\x1b[49mFF1B0A"); - /// assert_eq!(Color::C256(84).fmt_color(), "\x1b[48;5;84mC\x1b[49m084"); - /// ``` - fn fmt_color(&self) -> String { - match self { - Self::None => format!("None"), - Self::Rgb(x) - => format!("\x1b[48;2;{0};{1};{2}m#\x1b[49m{:02X}{:02X}{:02X}", - x[0], x[1], x[2]), - Self::C256(x) => format!("\x1b[48;5;{0}mC\x1b[49m{:03}", x), - } - } -}