From 44ba065d054738956d27bcd9ebb1bde81471067f Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 18 Apr 2023 06:37:45 +0800 Subject: [PATCH] =?UTF-8?q?Update=20Version=20To=202.0.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=20src/app.rs=20=09=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=9A=20=20=20=20=20src/lib.rs=20=09=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=9A=20=20=20=20=20src/main.rs=20=09.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 25 +- README.md | 68 ++--- src/app.rs | 734 ++++++++++++++++++++++++++++++---------------------- src/lib.rs | 554 +++++++++------------------------------ src/main.rs | 152 +++++------ 5 files changed, 649 insertions(+), 884 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a21a1e5..69689ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,32 @@ [package] name = "timg" -version = "1.2.0" +version = "2.0.0" +edition = "2021" + authors = ["A4-Tacks "] -keywords = ["terminal", "image", "term"] -description = "Display image for terminal (VT100)" +keywords = ["terminal", "image", "term", "tui", "viewer"] +description = "Picture viewer for terminal (VT100)." categories = ["command-line-utilities"] -license-file = "LICENSE" + +license = "MIT" repository = "https://github.com/A4-Tacks/timg" homepage = "https://github.com/A4-Tacks/timg" + readme = "README.md" -edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -image = "0.24.*" -#imageproc = "0.23.*" +image = "0.24.6" clap = "2.27.0" term_size = "0.3.2" +term_lattice = "0.4.1" +raw_tty = "0.1.0" + + +[profile.dev] +opt-level = 1 + +[profile.release] +strip = true diff --git a/README.md b/README.md index 465662f..59fca6b 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,16 @@ -# Display image for terminal (VT100) -``` -A4-Tacks - -USAGE: - timg [FLAGS] [OPTIONS] [FILE] - -FLAGS: - --disable-default-colors - --disable-empty-char - -H, --help Prints help information - -s, --no-split-edge No split edge - --output-colors - -V, --version Prints version information - -OPTIONS: - -b, --background-color Display image background color - (default:000000) - --colors Define color output. - format: 38;2;0;0;0:30,48;2;0;0;0:40 - -c, --crop-image crop image - (format:-c 'x,y-w,h') (0f-100f) - --empty-char Set empty char\n(default:\x20) - -t, --filter Type of filter used - 0: Nearest (quick) - 1: Triangle - 2: CatmullRom - 3: Gaussian - 4: Lanczos3 (default) - -f, --foreground Set display foreground char - -h, --height Set display image height - -d, --optimization-level Set optimization level - (default:1) [0, 255] - -w, --width Set display image width - -ARGS: - Target file -``` - -# Update Log - -## v1.0.1 -- Fixed bug: extra empty lines at the end - -## v1.1.0 -- Added function: output specified characters when the foreground and background colors are similar to reduce the rendering pressure and space occupation of half-height characters -- - Add Flag: --disable-empty-char -- - Add Option: --empty-char - -## v1.2.0 -- Add crop picture options - +The image viewer on the terminal is based on the VT100 standard. + +# Function +- Zoom Picture +- Rotate Picture +- Mirror image +- Inverted image +- Grayscale image +- Change interpolation algorithm +- Change background color +- Adjusting the output color difference threshold to improve output speed + +# 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. diff --git a/src/app.rs b/src/app.rs index d196186..545ce64 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,355 +1,465 @@ +/// terminal default size. +/// (width, height) +/// note: lines is not text line, it is pixel line +pub const DEFAULT_TERM_SIZE: [SizeType; 2] = [80, 80]; + use std::{ - process::exit, - collections::HashMap, - iter::Iterator, + io::{ + stdin, + Read + } }; -use image::{ - io::Reader as ImgReader, - imageops::FilterType, - DynamicImage, - ImageError, + +use raw_tty::IntoRawMode; +use term_lattice::{ + ScreenBuffer, + Color, + types::Rgb }; +use timg::{ESC, base16_to_unum, num_to_rgb}; use clap::ArgMatches; +use term_size::dimensions; use timg::{ - image_size, - base16_to_unum, - num_to_rgb, - Rgb, - pass, - DEFAULT_COLORS, - ESC, - CR, + get_scale, + SizeType, + Position, + Float, +}; +use image::{ + imageops::FilterType, + ColorType }; + +const FILTERS: &[FilterType] = &[ + FilterType::Nearest, FilterType::Triangle, + FilterType::CatmullRom, FilterType::Gaussian, + FilterType::Lanczos3 +]; + +pub type Rgba = [u8; 4]; + + +/// RGBA color to RGB color +/// # Examples +/// ``` +/// use timg::rgba_to_rgb; +/// assert_eq!(rgba_to_rgb([100, 149, 237, 200], [255; 3]), [164, 195, 240]); +/// ``` +pub fn rgba_to_rgb(foreground: Rgba, background: Rgb) -> Rgb { + macro_rules! int { + ( $x:expr ) => { + ($x) as u8 + }; + } + macro_rules! float { + ( $x:expr ) => { + ($x) as Float + }; + } + let [r1, g1, b1, a1] = foreground; + let [r2, g2, b2] = background; + let alpha = a1 as Float / 255.0; + let [r, g, b]: [u8; 3]; + r = int!(float!(r1) * alpha + float!(r2) * (1.0 - alpha)); + g = int!(float!(g1) * alpha + float!(g2) * (1.0 - alpha)); + b = int!(float!(b1) * alpha + float!(b2) * (1.0 - alpha)); + [r, g, b] +} + + #[macro_export] macro_rules! log { - (e:($code:expr) $( $x:expr ),* ) => { + (e:($code:expr) $( $x:expr ),* ) => {{ log!(e $($x),* ); - exit($code); - }; + ::std::process::exit($code); + }}; (e $( $x:expr ),* ) => ( eprintln!( "{}[1;91m{}{0}[0m", ESC, format!($($x),*) )) } -fn read_img(path: &str) -> Result { - ImgReader::open(path)?.decode() -} pub fn run(matches: ArgMatches) { - macro_rules! parse { - ( $x:expr ) => { - match $x.parse() { - Ok(n) => n, - Err(_) => { - log!(e:(2) - "ParseError: {:?}", $x); - }, + macro_rules! get_value { + ( $name:expr ) => { matches.value_of($name) }; + ( $name:expr, $default:expr ) => { + if let Some(value) = matches.value_of($name) { + value + } else { + $default } }; } - #[doc = r#" - $name - $default_value - $type_to_target_macro"#] - macro_rules! get_val { - ( $name:expr , $default:expr , $macro:tt ) - => (match matches.value_of($name) { - None => $default, - Some(w) => $macro!(w)});} - macro_rules! get_filter { - ( $id:expr ) => (match $id { - "0" => FilterType::Nearest, - "1" => FilterType::Triangle, - "2" => FilterType::CatmullRom, - "3" => FilterType::Gaussian, - "4" => FilterType::Lanczos3, - &_ => { - log!(e:(2) - "FilterTypeError: {}", - $id);}, - }); - } - // env - let (term_width, term_height) - : (u32, u32) - = if let Some((w, h)) = term_size::dimensions() { - (w as u32, h as u32 * 2) - } else { - (80, 40) - }; - - // get args - let (mut width, mut height): (u32, u32) - = (get_val!( "width", 0, parse), - get_val!( "height", 0, parse)); - let abs_size: bool = width != 0 && height != 0; - let has_set_size: bool = width != 0 || height != 0; - let fg_char: &str = get_val!( - "foreground", "▄", pass); - let empty_char: &str = get_val!( - "empty_char", "\x20", pass); - let enable_empty_char: bool = ! matches.is_present("disable_empty_char"); - let split_edge: bool = ! matches.is_present("no_split_edge"); - let path: &str = match matches.value_of("FILE") { - Some(x) => x, - None => { - log!(e:(1) "NoFile: append '-H' or '--help'"); - }, + let back_grounds: &[Rgb] = &get_value!("bgs", "000000,888888,ffffff") + .split(",").map(|s| { + if s.len() != 6 { + log!(e:(3) "StrLenError: {:?} length is {}, need 6.", s, s.len()) + } + num_to_rgb(base16_to_unum(s).unwrap_or_else(|| { + log!(e:(3) "StrToHexError: {:?} is not a base16 string.", s) + })) + }).collect::>()[..]; + let zoom_sub_ratio = { + let s = get_value!("zoom_ratio", "0.8"); + let num: Float = s.parse().unwrap_or_else( + |e| log!(e:(3) "StrToFloatError: {}", e)); + if num <= 0.0 || num >= 1.0 { + log!(e:(3) "NumberOutOfRange: {} not in (0,1)", num) + } + num }; - let mut colors: HashMap = HashMap::new(); - { - macro_rules! push { - ( $x:expr ) => ( - macro_rules! err { - () => (log!(e:(2) "ColorsFormatError: {:?}", $x)); - } - $x.split(',').map(|x| { - let mut kv = x.split(':'); - let k: &str = if let Some(x) = kv.next() {x} - else {err!{}}; - let v: &str = if let Some(x) = kv.next() {x} - else {err!{}}; - if let Some(_) = kv.next() {err!{}} - colors.insert(k.to_string(), v.to_string()); - }).last(); - ); + let zoom_add_ratio = 1.0 / zoom_sub_ratio; + let short_move_ratio = { + let s = get_value!("short_move_ratio", "0.25"); + let num: Float = s.parse().unwrap_or_else( + |e| log!(e:(3) "StrToFloatError: {}", e)); + if num <= 0.0 { + log!(e:(3) "NumberOutOfRange: {} not in (0,inf)", num) } - if ! matches.is_present("disable_default_colors") { - push!(DEFAULT_COLORS); + num + }; + let long_move_ratio = { + let s = get_value!("long_move_ratio", "0.75"); + let num: Float = s.parse().unwrap_or_else( + |e| log!(e:(3) "StrToFloatError: {}", e)); + if num <= 0.0 { + log!(e:(3) "NumberOutOfRange: {} not in (0,inf)", num) } - match matches.value_of("colors") { - Some(x) => { - push!(x); - }, - None => (), - }; - } - if matches.is_present("output_colors") { - println!("{}", colors.keys().map(|x| format!("{}:{}", x, match colors.get(x) { - Some(x) => x, - None => {log!(e:(4) "MemError");}, - })).collect::>().join(",")); - exit(0); - } + num + }; + let default_opt_level = { + let s = get_value!("opt_level", "60"); + let num: SizeType = s.parse().unwrap_or_else( + |e| log!(e:(3) "StrToIntError: {}", e)); + if num <= 0 { + log!(e:(3) "NumberOutOfRange: {} not in [0,inf)", num) + } + num + }; + let set_term_size: Option = { + if let Some(size) = get_value!("term_size") { + let nums = size.split(",") + .map(|n| n.parse::() + .unwrap_or_else( + |e| + log!(e:(3) "StrToIntError: {}", e))) + .collect::>(); + if nums.len() != 2 { + log!(e:(3) "need length is 2, found {}", nums.len()) + } + Some(Position::new(nums[0], nums[1])) + } else { + None + } + }; - let opt_level: u8 = get_val!("opt_level", 1, parse); - // background color - let background_color: (u8, u8, u8) - = match matches.value_of("background_color") { - Some(x) => { - if x.len() != 6 { - log!(e:(2) "HexColorLenError: len {} is not 6", x.len()); - } - if let Some(n) = base16_to_unum(x) { - num_to_rgb(n) - } else { - log!(e:(2) "HexColorFormatError: {}", x); - } - }, - _ => (0, 0, 0), // default value + macro_rules! clear_screen { + () => { + eprint!("\x1b[2J"); // 清空屏幕 }; - - let filter: FilterType - = get_val!("filter", - FilterType::Lanczos3, - get_filter); - - type ImageCropType = [[f32; 2]; 2]; - let crop_image_range: Option - = match matches.value_of("crop_image") { - Some(x) => { - fn get(x: &str) -> Option { - let mut result: ImageCropType = [[0.0; 2]; 2]; - let mut tgt - = x.split("-") .map(|x: &str| x.split(",")); - for i in 0..2 { - let mut tgt2: std::str::Split<&str> = tgt.next()?; - for j in 0..2 { - result[i][j] = tgt2.next()?.parse().ok()?; + } + let path = matches + .value_of_os("FILE") + .unwrap_or_else(|| { + log!(e:(1) "GetFileError. use `-H` option print help"); + }); + let mut repr_img + = image::open(path).unwrap_or_else(|e| { + log!(e:(2) "ReadImageError: {}", e); + }); + let img_size = Position::from([repr_img.width(), repr_img.height()]); + 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 mut is_start: bool = true; + let mut readbuf: [u8; 1] = [0]; + 'main: loop { + let mut term_size: Position + = Position::from(if let Some(size) = set_term_size { + [size.x, size.y * 2] + } else { + match dimensions() { + Some(x) => [x.0 as SizeType, x.1 as SizeType * 2], + None => { + log!(e "GetTerminalSizeError. use default: {:?}", + DEFAULT_TERM_SIZE); + DEFAULT_TERM_SIZE + }, + } + }); + 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 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]; + loop { + screen_buf.cfg.default_color + = Color::Rgb(back_grounds[back_ground_color_idx]); + let scale_term_size = term_size.mul_scale(scale); + let mut img + = repr_img.crop_imm(win_pos.x, + win_pos.y, + scale_term_size.x, + scale_term_size.y) + .resize( + term_size.x, + term_size.y, + FILTERS[filter_idx]); + if invert { + img.invert() + } + if grayscale { + img = img.grayscale() + } + { /* flush to screen buffer */ + screen_buf.init_colors(); + let mut count: usize = 0; + let img_width: usize = img.width() as usize; + let line_add_idx: usize = term_size.x as usize - img_width; + let mut i: usize = 0; + macro_rules! flush { + ( $i:ident in $from:expr => $f:expr ) => { + for $i in $from { + screen_buf.set_idx(i, Color::Rgb($f)); + i += 1; + count += 1; + if count == img_width { + i += line_add_idx; + count = 0; + } } - if let Some(_) = tgt2.next() { return None; } - } - if let Some(_) = tgt.next() { return None; } - Some(result) + }; } - debug_assert_eq!(get("50,50-75,75.3"), Some([[50.0, 50.0], [75.0, 75.3]])); - if let Some(result) = get(x) { - Some(result) + if is_alpha { + flush!(color in img.into_rgba8().pixels() + => rgba_to_rgb( + color.0, + back_grounds[back_ground_color_idx])); } else { - log!(e:(2) "FormatError: {:?}", x); + flush!(color in img.into_rgb8().pixels() => color.0); } - }, - None => None, - }; - - - let img = match read_img(path) { - Ok(mut img) => { - let img: DynamicImage // 裁剪后的图片 (如果需要) - = if let Some(r) = crop_image_range { - type T = f32; - let (old_width, old_height) - = (img.width() as T / 100.0, img.height() as T / 100.0); - let (x, y): (T, T) = (r[0][0] * old_width, r[0][1] * old_height); - let (new_width, new_height): (T, T) - = (r[1][0] * old_width, r[1][1] * old_height); - img.crop(x as u32, y as u32, new_width as u32, new_height as u32) - } else {img}; - let (img_w, img_h): (u32, u32) - = (img.width(), img.height()); - (width, height) - = if abs_size { - (width, height) + } + let status_line: String = format!(concat!( + "\x1b[7m", + "ImgSize[{}x{}] ", + "Pos[{},{}] ", + "Scale[{:.2}] ", + "Opt[{}] ", + "Fl[{}] ", + "Help(H) ", + "Quit(Q)", + "\x1b[0m"), + img_size.x, img_size.y, + win_pos.x, win_pos.y, + scale, + screen_buf.cfg.chromatic_aberration, + filter_idx); + eprint!("\x1b[H{}{}", screen_buf.flush(false), status_line); + is_start = false; + macro_rules! read_char { + () => { + stdin.read_exact(&mut readbuf).unwrap_or_else(|e| { + log!(e:(2) "ReadCharError: {}", e) + }) + }; + } + read_char!(); + let move_len: SizeType = { + let num = scale.ceil() as SizeType; + if num == 0 { + 1 } else { - image_size( - (img_w, img_h), - if has_set_size { - (width, height) - } else { - (term_width, term_height) - }, 0) + num + } + }; + macro_rules! ctrl_err { + ( $( $x:expr ),* ) => { + eprint!("\x07 \x1b[101m{}\x1b[0m", + format!( $( $x ),* )) }; - img.resize_exact(width, height, filter).into_rgba8() - }, - Err(e) => { - log!(e:(2) "{}", e); - }, - }; - - assert_eq!(width, img.width()); - assert_eq!(height, img.height()); - - let width_usize: usize = width as usize; - let mut line_num: u32 = 0; - let mut bg_line_buffer: Vec = Vec::with_capacity(width_usize); - let mut fg_line_buffer: Vec = Vec::with_capacity(width_usize); - let mut mode: bool = false; - let mut skip_fg_line: bool = false; - let mut output_buffer: String = String::new(); - macro_rules! out { - ( $( $x:expr ),* ) => (output_buffer.push_str(&format!( $($x),* ))); - } - macro_rules! color_presets { - ( $x:expr ) => { - match colors.get(&$x) { - Some(x) => x.clone(), - None => $x, - }}; - } - macro_rules! color { - (bf: $b:tt, $f:tt ) => { - format!("{}[{};{}m", ESC, color_presets!( - format!("48;2;{};{};{}", - $b.0, $b.1, $b.2)), - color_presets!( - format!("38;2;{};{};{}", - $f.0, $f.1, $f.2) - ) - )}; - (b: $b:tt ) => ( - format!("{}[{}m", ESC, color_presets!( - format!("48;2;{};{};{}", $b.0, $b.1, $b.2)))); - (f: $f:tt ) => ( - format!("{}[{}m", ESC, color_presets!( - format!("38;2;{};{};{}", $f.0, $f.1, $f.2)))); - } - let bg_ansi: String = color!(bf: background_color, background_color); - let clear_ansi: String = format!("{}[{}m", - ESC, color_presets!(String::from("0"))); - output_buffer.push_str(&bg_ansi); - let (mut old_fg, mut old_bg): (Rgb, Rgb) - = (Rgb::from(background_color), - Rgb::from(background_color)); - 'a: for color in img.pixels() { - let mut tmp = Rgb::from(background_color); - tmp.set_from_rgba((color.0[0], color.0[1], - color.0[2], color.0[3])); - loop { - match mode { - false => { - bg_line_buffer.push(tmp); - if bg_line_buffer.len() == width_usize { - mode = true; - line_num += 1; - if line_num == height { - skip_fg_line = true; - continue; - } + } + 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 + ]; + let [movec_wlen, movec_hlen] = [ + (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 { + 'r' => { + screen_buf.init_bg_colors(); + clear_screen!(); + }, + 'R' => continue 'main, + 'Q' => break, /* exit */ + 'h' => { + let old = win_pos.x; + win_pos.x -= move_len; + if win_pos.x > old { + win_pos.x = 0; + ctrl_err!("RB"); } }, - true => { // output buffer - if skip_fg_line { - for _ in 0..width { - fg_line_buffer.push(Rgb::from(background_color)); - } - } else { - fg_line_buffer.push(tmp); + 'j' => win_pos.y += move_len, + 'k' => { + let old = win_pos.y; + win_pos.y -= move_len; + if win_pos.y > old { + win_pos.y = 0; + ctrl_err!("RB"); } - if fg_line_buffer.len() == width_usize { - mode = false; - // output buffer color - if split_edge { - (old_fg, old_bg) - = (Rgb::from(background_color), - Rgb::from(background_color)); - } - let (mut fg, mut bg): (Rgb, Rgb); - let (mut fg_similar, mut bg_similar): (bool, bool); // 是否相似 - let mut bg_fg_similar: bool; - let mut target_char: &str; - for i in 0..width_usize { - bg = bg_line_buffer[i]; - fg = fg_line_buffer[i]; - (fg_similar, bg_similar) - = (fg.is_similar(old_fg, opt_level), - bg.is_similar(old_bg, opt_level)); - bg_fg_similar = enable_empty_char - && fg.is_similar(bg, opt_level); - - target_char = if bg_fg_similar { - empty_char - } else { - fg_char - }; + }, + 'l' => win_pos.x += move_len, - // 更新上一色的缓存 - if ! fg_similar { - old_fg = fg; - } - if ! bg_similar { - old_bg = bg; - } + 'a' => { + let old = win_pos.x; + win_pos.x -= moveb_wlen; + if win_pos.x > old { + win_pos.x = 0; + ctrl_err!("RB"); + } + }, + 's' => win_pos.y += moveb_hlen, + 'w' => { + let old = win_pos.y; + win_pos.y -= moveb_hlen; + if win_pos.y > old { + win_pos.y = 0; + ctrl_err!("RB"); + } + }, + 'd' => win_pos.x += moveb_wlen, + 'A' => { + let old = win_pos.x; + win_pos.x -= movec_wlen; + if win_pos.x > old { + win_pos.x = 0; + ctrl_err!("RB"); + } + }, + 'S' => win_pos.y += movec_hlen, + 'W' => { + let old = win_pos.y; + win_pos.y -= movec_hlen; + if win_pos.y > old { + win_pos.y = 0; + ctrl_err!("RB"); + } + }, + 'D' => win_pos.x += movec_wlen, + '+' | 'c' => scale *= zoom_sub_ratio, + '-' | 'x' => scale *= zoom_add_ratio, + 'o' => { /* opt */ + screen_buf.cfg.chromatic_aberration += 1; + } + 'O' => { /* opt */ + screen_buf.cfg.chromatic_aberration += 10; + } + 'i' => { /* opt */ + if screen_buf.cfg.chromatic_aberration != 0 { + screen_buf.cfg.chromatic_aberration -= 1 + } else { + ctrl_err!("FV") + }; + } + 'I' => { /* opt */ + if screen_buf.cfg.chromatic_aberration >= 10 { + screen_buf.cfg.chromatic_aberration -= 10 + } else { + screen_buf.cfg.chromatic_aberration = 0; + ctrl_err!("FV") + }; + } + 'z' => { + back_ground_color_idx += 1; + back_ground_color_idx %= back_grounds.len(); + }, + 'Z' => { + back_ground_color_idx = 0; + }, + 'f' => { + filter_idx += 1; + filter_idx %= FILTERS.len(); + }, + 'g' => repr_img = repr_img.fliph(), + 'G' => repr_img = repr_img.flipv(), + 'y' => repr_img = repr_img.rotate90(), + 'Y' => repr_img = repr_img.rotate270(), + 'm' => invert = ! invert, + 'M' => grayscale = ! grayscale, + 'H' | '?' => { + // help + clear_screen!(); - // to output_buffer - if bg_similar && fg_similar { - out!("{}", target_char); - } else if fg_similar { - out!("{}{}", color!(b: bg), target_char); - } else if bg_similar { - out!("{}{}", color!(f: fg), target_char); - } else { - out!("{}{}", color!(bf: bg, fg), target_char); - } - } - bg_line_buffer.clear(); - fg_line_buffer.clear(); - line_num += 1; - if line_num >= height { - break 'a; - } - if split_edge { - out!("{}{}{}", &clear_ansi, CR, &bg_ansi); - } else { - out!("{}", CR); - } + eprintln!("\x1b[H"); + macro_rules! outlines { + ( $( $fmt:tt $( , $( $x:expr ),+ )? ; )* ) => { + $( + eprint!( + concat!("\x1b[G", $fmt, "\n\x1b[G") + $(, $( $x ),+ )?); + )* + }; } + let bgcolors_fmt = back_grounds + .iter().map( + |x|x.map(|s| format!("{:02X}", s)) + .concat()) + .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})", + 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; + "ReDraw: `r`"; + "ReInit: `R`"; + "SwitchBackground: `z` [{}]", bgcolors_fmt; + "InitBackground: `Z`"; + "SetFilter: `f`, ({:?}) {:?}", FILTERS[filter_idx], FILTERS; + "FlipImage: `gG`"; + "Rotate: `yY`"; + "Invert: `m`"; + "Grayscale: `M`"; + "ThisHelpInfo: `H?`"; + "Quit: `Q`"; + }; + eprint!("\x1b[{}H", (term_size.y >> 1) + 1); + + read_char!(); + clear_screen!(); + screen_buf.init_bg_colors(); }, - }; - break; + c => { + ctrl_err!("EI:{:?}", c) + }, + } + eprint!("\x1b[K"); // 清除残留状态行 } + break; } - println!("{}{}", output_buffer, &clear_ansi); + eprintln!("\x1b[G"); // 退出时到头部换一行 } diff --git a/src/lib.rs b/src/lib.rs index 8bf6c95..31f9e3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,447 +1,143 @@ -pub const DEFAULT_COLORS: &str = -"48;2;0;0;0:48;5;16,\ -48;2;0;0;95:48;5;17,\ -48;2;0;0;135:48;5;18,\ -48;2;0;0;175:48;5;19,\ -48;2;0;0;215:48;5;20,\ -48;2;0;0;255:48;5;21,\ -48;2;0;95;0:48;5;22,\ -48;2;0;95;95:48;5;23,\ -48;2;0;95;135:48;5;24,\ -48;2;0;95;175:48;5;25,\ -48;2;0;95;215:48;5;26,\ -48;2;0;95;255:48;5;27,\ -48;2;0;135;0:48;5;28,\ -48;2;0;135;95:48;5;29,\ -48;2;0;135;135:48;5;30,\ -48;2;0;135;175:48;5;31,\ -48;2;0;135;215:48;5;32,\ -48;2;0;135;255:48;5;33,\ -48;2;0;175;0:48;5;34,\ -48;2;0;175;95:48;5;35,\ -48;2;0;175;135:48;5;36,\ -48;2;0;175;175:48;5;37,\ -48;2;0;175;215:48;5;38,\ -48;2;0;175;255:48;5;39,\ -48;2;0;215;0:48;5;40,\ -48;2;0;215;95:48;5;41,\ -48;2;0;215;135:48;5;42,\ -48;2;0;215;175:48;5;43,\ -48;2;0;215;215:48;5;44,\ -48;2;0;215;255:48;5;45,\ -48;2;0;255;0:48;5;46,\ -48;2;0;255;95:48;5;47,\ -48;2;0;255;135:48;5;48,\ -48;2;0;255;175:48;5;49,\ -48;2;0;255;215:48;5;50,\ -48;2;0;255;255:48;5;51,\ -48;2;95;0;0:48;5;52,\ -48;2;95;0;95:48;5;53,\ -48;2;95;0;135:48;5;54,\ -48;2;95;0;175:48;5;55,\ -48;2;95;0;215:48;5;56,\ -48;2;95;0;255:48;5;57,\ -48;2;95;95;0:48;5;58,\ -48;2;95;95;95:48;5;59,\ -48;2;95;95;135:48;5;60,\ -48;2;95;95;175:48;5;61,\ -48;2;95;95;215:48;5;62,\ -48;2;95;95;255:48;5;63,\ -48;2;95;135;0:48;5;64,\ -48;2;95;135;95:48;5;65,\ -48;2;95;135;135:48;5;66,\ -48;2;95;135;175:48;5;67,\ -48;2;95;135;215:48;5;68,\ -48;2;95;135;255:48;5;69,\ -48;2;95;175;0:48;5;70,\ -48;2;95;175;95:48;5;71,\ -48;2;95;175;135:48;5;72,\ -48;2;95;175;175:48;5;73,\ -48;2;95;175;215:48;5;74,\ -48;2;95;175;255:48;5;75,\ -48;2;95;215;0:48;5;76,\ -48;2;95;215;95:48;5;77,\ -48;2;95;215;135:48;5;78,\ -48;2;95;215;175:48;5;79,\ -48;2;95;215;215:48;5;80,\ -48;2;95;215;255:48;5;81,\ -48;2;95;255;0:48;5;82,\ -48;2;95;255;95:48;5;83,\ -48;2;95;255;135:48;5;84,\ -48;2;95;255;175:48;5;85,\ -48;2;95;255;215:48;5;86,\ -48;2;95;255;255:48;5;87,\ -48;2;135;0;0:48;5;88,\ -48;2;135;0;95:48;5;89,\ -48;2;135;0;135:48;5;90,\ -48;2;135;0;175:48;5;91,\ -48;2;135;0;215:48;5;92,\ -48;2;135;0;255:48;5;93,\ -48;2;135;95;0:48;5;94,\ -48;2;135;95;95:48;5;95,\ -48;2;135;95;135:48;5;96,\ -48;2;135;95;175:48;5;97,\ -48;2;135;95;215:48;5;98,\ -48;2;135;95;255:48;5;99,\ -48;2;135;135;0:48;5;100,\ -48;2;135;135;95:48;5;101,\ -48;2;135;135;135:48;5;102,\ -48;2;135;135;175:48;5;103,\ -48;2;135;135;215:48;5;104,\ -48;2;135;135;255:48;5;105,\ -48;2;135;175;0:48;5;106,\ -48;2;135;175;95:48;5;107,\ -48;2;135;175;135:48;5;108,\ -48;2;135;175;175:48;5;109,\ -48;2;135;175;215:48;5;110,\ -48;2;135;175;255:48;5;111,\ -48;2;135;215;0:48;5;112,\ -48;2;135;215;95:48;5;113,\ -48;2;135;215;135:48;5;114,\ -48;2;135;215;175:48;5;115,\ -48;2;135;215;215:48;5;116,\ -48;2;135;215;255:48;5;117,\ -48;2;135;255;0:48;5;118,\ -48;2;135;255;95:48;5;119,\ -48;2;135;255;135:48;5;120,\ -48;2;135;255;175:48;5;121,\ -48;2;135;255;215:48;5;122,\ -48;2;135;255;255:48;5;123,\ -48;2;175;0;0:48;5;124,\ -48;2;175;0;95:48;5;125,\ -48;2;175;0;135:48;5;126,\ -48;2;175;0;175:48;5;127,\ -48;2;175;0;215:48;5;128,\ -48;2;175;0;255:48;5;129,\ -48;2;175;95;0:48;5;130,\ -48;2;175;95;95:48;5;131,\ -48;2;175;95;135:48;5;132,\ -48;2;175;95;175:48;5;133,\ -48;2;175;95;215:48;5;134,\ -48;2;175;95;255:48;5;135,\ -48;2;175;135;0:48;5;136,\ -48;2;175;135;95:48;5;137,\ -48;2;175;135;135:48;5;138,\ -48;2;175;135;175:48;5;139,\ -48;2;175;135;215:48;5;140,\ -48;2;175;135;255:48;5;141,\ -48;2;175;175;0:48;5;142,\ -48;2;175;175;95:48;5;143,\ -48;2;175;175;135:48;5;144,\ -48;2;175;175;175:48;5;145,\ -48;2;175;175;215:48;5;146,\ -48;2;175;175;255:48;5;147,\ -48;2;175;215;0:48;5;148,\ -48;2;175;215;95:48;5;149,\ -48;2;175;215;135:48;5;150,\ -48;2;175;215;175:48;5;151,\ -48;2;175;215;215:48;5;152,\ -48;2;175;215;255:48;5;153,\ -48;2;175;255;0:48;5;154,\ -48;2;175;255;95:48;5;155,\ -48;2;175;255;135:48;5;156,\ -48;2;175;255;175:48;5;157,\ -48;2;175;255;215:48;5;158,\ -48;2;175;255;255:48;5;159,\ -48;2;215;0;0:48;5;160,\ -48;2;215;0;95:48;5;161,\ -48;2;215;0;135:48;5;162,\ -48;2;215;0;175:48;5;163,\ -48;2;215;0;215:48;5;164,\ -48;2;215;0;255:48;5;165,\ -48;2;215;95;0:48;5;166,\ -48;2;215;95;95:48;5;167,\ -48;2;215;95;135:48;5;168,\ -48;2;215;95;175:48;5;169,\ -48;2;215;95;215:48;5;170,\ -48;2;215;95;255:48;5;171,\ -48;2;215;135;0:48;5;172,\ -48;2;215;135;95:48;5;173,\ -48;2;215;135;135:48;5;174,\ -48;2;215;135;175:48;5;175,\ -48;2;215;135;215:48;5;176,\ -48;2;215;135;255:48;5;177,\ -48;2;215;175;0:48;5;178,\ -48;2;215;175;95:48;5;179,\ -48;2;215;175;135:48;5;180,\ -48;2;215;175;175:48;5;181,\ -48;2;215;175;215:48;5;182,\ -48;2;215;175;255:48;5;183,\ -48;2;215;215;0:48;5;184,\ -48;2;215;215;95:48;5;185,\ -48;2;215;215;135:48;5;186,\ -48;2;215;215;175:48;5;187,\ -48;2;215;215;215:48;5;188,\ -48;2;215;215;255:48;5;189,\ -48;2;215;255;0:48;5;190,\ -48;2;215;255;95:48;5;191,\ -48;2;215;255;135:48;5;192,\ -48;2;215;255;175:48;5;193,\ -48;2;215;255;215:48;5;194,\ -48;2;215;255;255:48;5;195,\ -48;2;255;0;0:48;5;196,\ -48;2;255;0;95:48;5;197,\ -48;2;255;0;135:48;5;198,\ -48;2;255;0;175:48;5;199,\ -48;2;255;0;215:48;5;200,\ -48;2;255;0;255:48;5;201,\ -48;2;255;95;0:48;5;202,\ -48;2;255;95;95:48;5;203,\ -48;2;255;95;135:48;5;204,\ -48;2;255;95;175:48;5;205,\ -48;2;255;95;215:48;5;206,\ -48;2;255;95;255:48;5;207,\ -48;2;255;135;0:48;5;208,\ -48;2;255;135;95:48;5;209,\ -48;2;255;135;135:48;5;210,\ -48;2;255;135;175:48;5;211,\ -48;2;255;135;215:48;5;212,\ -48;2;255;135;255:48;5;213,\ -48;2;255;175;0:48;5;214,\ -48;2;255;175;95:48;5;215,\ -48;2;255;175;135:48;5;216,\ -48;2;255;175;175:48;5;217,\ -48;2;255;175;215:48;5;218,\ -48;2;255;175;255:48;5;219,\ -48;2;255;215;0:48;5;220,\ -48;2;255;215;95:48;5;221,\ -48;2;255;215;135:48;5;222,\ -48;2;255;215;175:48;5;223,\ -48;2;255;215;215:48;5;224,\ -48;2;255;215;255:48;5;225,\ -48;2;255;255;0:48;5;226,\ -48;2;255;255;95:48;5;227,\ -48;2;255;255;135:48;5;228,\ -48;2;255;255;175:48;5;229,\ -48;2;255;255;215:48;5;230,\ -48;2;255;255;255:48;5;231,\ -48;2;8;8;8:48;5;232,\ -48;2;18;18;18:48;5;233,\ -48;2;28;28;28:48;5;234,\ -48;2;38;38;38:48;5;235,\ -48;2;48;48;48:48;5;236,\ -48;2;58;58;58:48;5;237,\ -48;2;68;68;68:48;5;238,\ -48;2;78;78;78:48;5;239,\ -48;2;88;88;88:48;5;240,\ -48;2;98;98;98:48;5;241,\ -48;2;108;108;108:48;5;242,\ -48;2;118;118;118:48;5;243,\ -48;2;128;128;128:48;5;244,\ -48;2;138;138;138:48;5;245,\ -48;2;148;148;148:48;5;246,\ -48;2;158;158;158:48;5;247,\ -48;2;168;168;168:48;5;248,\ -48;2;178;178;178:48;5;249,\ -48;2;188;188;188:48;5;250,\ -48;2;198;198;198:48;5;251,\ -48;2;208;208;208:48;5;252,\ -48;2;218;218;218:48;5;253,\ -48;2;228;228;228:48;5;254,\ -48;2;238;238;238:48;5;255"; +pub type SizeType = u32; +pub type Float = f64; -pub const ESC: char = '\x1b'; -pub const CR: &str = "\n"; +/// as float +macro_rules! asf { + ( $($x:expr),* ) => { + ( $($x as $crate::Float),* ) + }; +} -#[macro_export] -macro_rules! min { - ( $a:expr, $b:expr ) - => (if $a < $b {$a} else {$b}); +/// 一个位置 (x, y) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Position { + pub x: SizeType, + pub y: SizeType, } - -#[macro_export] -macro_rules! max { - ( $a:expr, $b:expr ) - => (if $a > $b {$a} else {$b}); +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 + } +} +impl From<[SizeType; 2]> for Position { + fn from(value: [SizeType; 2]) -> Self { + Self::new(value[0], value[1]) + } +} +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) + } } -#[macro_export] -macro_rules! pass { - ($a:tt < $( $x:tt )* ) => ($a); - ( $( $x:tt )* ) => ($( $x )*); +/// 获取要将图片大小缩到刚好放进终端大小时, 终端大小须乘的比例 +/// 终端大小 * 比例 得到刚好包括整个图片的大小 +pub fn get_scale(term_size: Position, img_size: Position) -> Float { + let (tw, th, iw, ih) + = asf!(term_size.x, term_size.y, img_size.x, img_size.y); + if tw / iw < th / ih { + /* 当 y 轴合适时 img.x 过长 */ + iw / tw + } else { + /* 当 x 轴合适时 img.y 过长 */ + ih / th + } } +#[test] +fn get_scale_test() { + let term_size = Position::new(80, 60); + let img_size = Position::new(240, 740); + let scale = get_scale(term_size, img_size); + assert!(term_size.x as Float * scale >= img_size.x as Float); + assert_eq!(term_size.y as Float * scale, img_size.y as Float); -#[doc = r#" -Examples: -``` -let a = (0, 1, 2); -let b = (0, 1, 2); -assert!(timg::compare_items!((a, b) : (0, 1, 2), (x, y) => {x == y})); -``` -"#] -#[macro_export] -macro_rules! compare_items { - ( ($a:tt, $b:tt) : ($( $i:tt ),+), ($p1:tt, $p2:tt) => $op:block) - => { - true $(&& { - let ($p1, $p2) = ($a.$i, $b.$i); - $op - })+ - } + let term_size = Position::new(80, 60); + let img_size = Position::new(240, 40); + let scale = get_scale(term_size, img_size); + assert!(term_size.y as Float * scale >= img_size.y as Float); + assert_eq!(term_size.x as Float * scale, img_size.x as Float); } -#[doc = " -Examples -``` -use timg::default_value; -let a = 0.0; -let result = 5.0 / default_value!(a => 0.0, 1.0); -assert_eq!(result, 5_f32); -``` -"] + +pub const ESC: char = '\x1b'; +pub const CR: &str = "\n"; + + +/// # Examples +/// ``` +/// use timg::default_value; +/// let a = 0.0; +/// let result = 5.0 / default_value!(a => 0.0, 1.0); +/// assert_eq!(result, 5_f32); +/// ``` #[macro_export] macro_rules! default_value { ( $x:expr => $v:expr , $default:expr ) => (if $x != $v { $x } else { $default }); } -#[doc = r#" -Examples: -``` -use timg::join_str; -use timg::pass; -let a = 2; -let b = "hello"; -assert_eq!(join_str!("hi,", a, b), "hi,2hello"); -``` -"#] +/// # Examples +/// ``` +/// use timg::pass; +/// assert_eq!(pass!(((8, 9, 12)) !(13, 15)), (8, 9, 12)); +/// assert_eq!(pass!((6) !(13, 15)), 6); +/// ``` #[macro_export] -macro_rules! join_str { - ( $( $x:expr ),+ ) => (format!(concat!($(pass!("{}" < $x)),+), $($x),+)); -} - -pub fn image_size( - osize: (u32, u32), - tsize: (u32, u32), - e_val: u32) --> (u32, u32) -{ - //! osize: input - //! tsize: target size - //! e_val: empty val - //! Examples - //! ``` - //! use timg::image_size; - //! let a = image_size((5, 8), (9, 0), 0); - //! assert_eq!(a, (9, 14)); - //! - //! let a = image_size((8, 4), (11, 0), 0); - //! assert_eq!(a, (11, 5)); - //! - //! let a = image_size((5, 8), (0, 16), 0); - //! assert_eq!(a, (10, 16)); - //! - //! let a = image_size((8, 4), (0, 11), 0); - //! assert_eq!(a, (22, 11)); - //! - //! let a = image_size((8, 4), (3, 3), 0); - //! assert_eq!(a, (3, 1)); - //! - //! let a = image_size((8, 4), (0, 0), 0); - //! assert_eq!(a, (0, 0)); - //! - //! let a = image_size((0, 4), (0, 0), 0); - //! assert_eq!(a, (0, 0)); - //! ``` - let (a, b): (f64, f64) - = (default_value!(osize.0 => 0, 1) as f64, - default_value!(osize.1 => 0, 1) as f64); - let (a1, b1): (f64, f64) - = (tsize.0 as f64, tsize.1 as f64); - let scale: f64 = if tsize.0 == e_val { - b1 / b - } else if tsize.1 == e_val { - a1 / a - } else { - min!(a1 / a, b1 / b) +macro_rules! pass { + ( ($( $v:tt )*) $( !( $( $d:expr ),* ) )? ) => { + $( $v )* }; - ((a * scale) as u32, - (b * scale) as u32) } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Rgb(pub u8, pub u8, pub u8); -impl Rgb { - pub fn new() -> Self { - Self (0, 0, 0) - } - pub fn is_similar(&self, other: Rgb, opt: u8) -> bool { - //! 比较自身与目标是否相似 - //! opt: 误差程度 - //! Examples: - //! ``` - //! use timg::Rgb; - //! let a = Rgb::from((180, 231, 99)); - //! let b = Rgb::from((178, 229, 101)); - //! assert!(a.is_similar(b, 2)); - //! - //! let a = Rgb::from((0, 231, 99)); - //! let b = Rgb::from((181, 231, 100)); - //! assert!(! a.is_similar(b, 0)); - //! ``` - compare_items!((self, other) : (0, 1, 2), (x, y) => { - (if x < opt {u8::MIN} else {x - opt}) - <= y && y <= - (if x > u8::MAX - opt {u8::MAX} else {x + opt}) - }) - } - pub fn get(&self) -> (u8, u8, u8) { - (self.0, self.1, self.2) - } - pub fn set(&mut self, color: (u8, u8, u8)) { - (self.0, self.1, self.2) = color - } - pub fn set_from_rgba(&mut self, fg: (u8, u8, u8, u8)) { - //! Examples: - //! ``` - //! use timg::Rgb; - //! let mut c = Rgb::new(); - //! c.set((2, 3, 9)); - //! c.set_from_rgba((150, 8, 72, 96)); - //! assert_eq!(c, Rgb::from((57, 4, 32))); - //! ``` - macro_rules! to { - ( $($x:expr ),+ ) => ( ($($x as f64 / 255.0),+) ); - } - macro_rules! merge { - ($bg:tt, $fg:expr, $( $t:tt ),+) - => {{ - let fga: f64 = to!($fg.3); - ($( - { - let (bg, fg): (f64, f64) = to!($bg.$t, $fg.$t); - ((fg * fga + bg * (1.0 - fga)) * 255.0) as u8 - } - ),+) - }}; - } - self.set(merge!(self, fg, 0, 1, 2)) - } -} -impl From<(u8, u8, u8)> for Rgb { - fn from(o: (u8, u8, u8)) -> Self { - //! Examples: - //! ``` - //! use timg::Rgb; - //! let mut a = Rgb::new(); - //! a.set((2, 8, 18)); - //! assert_eq!(a, Rgb::from((2, 8, 18))) - //! ``` - let mut a = Self::new(); - a.set(o); - a - } +/// # Examples +/// ``` +/// use timg::join_str; +/// use timg::pass; +/// let a = 2; +/// let b = "hello"; +/// assert_eq!(join_str!("hi,", a, b), "hi,2hello"); +/// ``` +#[macro_export] +macro_rules! join_str { + ( $( $x:expr ),+ ) => (format!(concat!($(pass!(("{}") !($x))),+), $($x),+)); } + +/// # Examples +/// ``` +/// use timg::base16_to_unum; +/// let n = base16_to_unum("abfF14"); +/// assert_eq!(n, Some(0xabff14)); +/// ``` pub fn base16_to_unum(s: &str) -> Option { - //! Examples: - //! ``` - //! use timg::base16_to_unum; - //! let n = base16_to_unum("abfF14"); - //! assert_eq!(n, Some(0xabff14)); - //! ``` let mut num: u32 = 0; for i in s.chars() { num <<= 4; @@ -458,15 +154,17 @@ pub fn base16_to_unum(s: &str) -> Option { Some(num) } -pub fn num_to_rgb(num: u32) -> (u8, u8, u8) { - //! Examples: - //! ``` - //! use timg::num_to_rgb; - //! assert_eq!(num_to_rgb(0xff0084), (0xff, 0x00, 0x84)); - //! ``` +/// # Examples +/// ``` +/// use timg::num_to_rgb; +/// assert_eq!(num_to_rgb(0xff0084), [0xff, 0x00, 0x84]); +/// ``` +pub fn num_to_rgb(num: u32) -> [u8; 3] { + // base16: 4bit + // base16 * 2: 8bit debug_assert!(num <= 0xffffff); - (((num >> 16) & 0xff) as u8, + [((num >> 16) & 0xff) as u8, ((num >> 8) & 0xff) as u8, - (num & 0xff) as u8) + (num & 0xff) as u8] } diff --git a/src/main.rs b/src/main.rs index d920a59..431b7d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,18 @@ use clap::{ mod app; +macro_rules! lines { + ( $first:tt $( $x:tt )* ) => { + concat!( + $first + $( + , "\n", + $x + )* + ) + }; +} + fn main() { let matches: ArgMatches = App::new(env!("CARGO_PKG_NAME")) @@ -13,94 +25,64 @@ fn main() { .version(env!("CARGO_PKG_VERSION")) .author(env!("CARGO_PKG_AUTHORS")) - .arg(Arg::with_name("width") - .short("w") - .long("width") - .value_name("width") - .help("Set display image width") - .takes_value(true)) - - .arg(Arg::with_name("height") - .short("h") - .long("height") - .value_name("height") - .help("Set display image height") - .takes_value(true)) - - .arg(Arg::with_name("foreground") - .short("f") - .long("foreground") - .value_name("char") - .help("Set display foreground char") - .takes_value(true)) - - .arg(Arg::with_name("opt_level") - .short("d") - .long("optimization-level") - .value_name("level") - .help(concat!( - "Set optimization level\n", - "(default:1) [0, 255]")) - .takes_value(true)) - - .arg(Arg::with_name("background_color") - .short("b") - .long("background-color") - .value_name("color") - .help(concat!( - "Display image background color\n", - "(default:000000)")) - .takes_value(true)) - - .arg(Arg::with_name("no_split_edge") - .short("s") - .long("no-split-edge") - .help("No split edge")) - - .arg(Arg::with_name("filter") + .arg(Arg::with_name("term_size") .short("t") - .long("filter") - .value_name("id") - .help(concat!( - "Type of filter used\n", - "0: Nearest (quick)\n", - "1: Triangle\n", - "2: CatmullRom\n", - "3: Gaussian\n", - "4: Lanczos3 (default)", - )) - .takes_value(true)) - - .arg(Arg::with_name("colors") - .long("colors") + .long("term-size") + .value_name("size") + .takes_value(true) + .help(lines!( + "When this value is set, it will be used as the terminal size." + "Format: (columns,lines)" + "Example: 80,40"))) + + .arg(Arg::with_name("bgs") + .short("b") + .long("background-colors") .value_name("colors") - .help(concat!( - "Define color output.\n", - "format: 38;2;0;0;0:30,48;2;0;0;0:40" - )) - .takes_value(true)) - - .arg(Arg::with_name("output_colors") - .long("output-colors")) - - .arg(Arg::with_name("disable_default_colors") - .long("disable-default-colors")) - - .arg(Arg::with_name("empty_char") - .long("empty-char") - .value_name("char") - .help(r"Set empty char\n(default:\x20)") - .takes_value(true)) - - .arg(Arg::with_name("disable_empty_char") - .long("disable-empty-char")) + .takes_value(true) + .help(lines!( + "Set background colors." + "Default: 000000,888888,ffffff"))) + + .arg(Arg::with_name("zoom_ratio") + .short("z") + .long("zoom-ratio") + .value_name("num") + .takes_value(true) + .help(lines!( + "Number multiplied during scaling" + "Range: 0 < num < 1" + "Default: 0.8"))) + + .arg(Arg::with_name("short_move_ratio") + .short("s") + .long("short-move-ratio") + .value_name("num") + .takes_value(true) + .help(lines!( + "short-move The distance to move at once is num screen sizes" + "Range: num > 0" + "Default: 0.25"))) - .arg(Arg::with_name("crop_image") - .short("c") - .long("crop-image") - .value_name("range") - .help("crop image\n(format:-c 'x,y-w,h') (0f-100f)") - .takes_value(true)) + .arg(Arg::with_name("opt_level") + .short("o") + .long("opt-level") + .value_name("level") + .takes_value(true) + .help(lines!( + "Default optimization level" + "Range: num >= 0" + "Default: 60"))) + + .arg(Arg::with_name("long_move_ratio") + .short("l") + .long("long-move-ratio") + .value_name("num") + .takes_value(true) + .help(lines!( + "long-move The distance to move at once is num screen sizes" + "Range: num > 0" + "Default: 0.75"))) .args(&[ Arg::with_name("FILE").index(1)