viuer-0.9.2/.cargo_vcs_info.json0000644000000001360000000000100121760ustar { "git": { "sha1": "60890c4da2ebca4b3ddc706c1e11b086173a1d33" }, "path_in_vcs": "" }viuer-0.9.2/.gitignore000064400000000000000000000000241046102023000127520ustar 00000000000000/target/ Cargo.lock viuer-0.9.2/CHANGELOG.md000064400000000000000000000033571046102023000126070ustar 00000000000000## 0.9.2 - Use iterm and sixel in more terminals ## 0.9.1 - Add docs.rs metadata ## 0.9.0 - Remove `image` default features - Put `print_from_file` behind the new `print-file` feature flag ## 0.8.1 - Revert removed `image` features ## 0.8.0 - Remove unneeded features and update dependencies - Use Catmull-Rom for up/downscaling - Add `premultiplied_alpha` Config option ## 0.7.1 - Bump `base64` and `crossterm` dependencies ## 0.7.0 - Update name of temporary files when using Kitty to contain `tty-graphics-protocol` ## 0.6.2 - Upgrade `crossterm` dependency to 0.25 - Check `LC_TERMINAL` env variable when deciding iTerm support ## 0.6.1 - Upgrade `crossterm` dependency - Move to 2021 Edition ## 0.6.0 - Upgrade `image` dependency ## 0.5.3 - Bump `crossterm` and `console` dependencies ## 0.5.2 - Use iTerm protocol for WezTerm and mintty - Fix compiler warnings ## 0.5.1 - Fix memory leak when checking for Kitty support not in tty ## 0.5.0 - Upgrade to `crossterm` 0.20 - Remove `ViuError::Crossterm` - Rename `ViuError::IO` -> `ViuError::Io` - Change `print_from_file` signature to take `AsRef` instead of `&str` - Add carriage return after every line of printed blocks ## 0.4.0 - Experimental Sixel support - Remove `resize` Config option - Change `Printer` trait function signatures - Improve test suite - Major refactor of `BlockPrinter` ## 0.3.1 - Make `ViuResult` public ## 0.3.0 - Add iTerm support and `use_iterm` Config option - Add support for remote Kitty printing, through escape sequences - Rename `has_kitty_suport` to `get_kitty_support` - Remove `kitty_delete` Config option ## 0.2.0 - Add support for local Kitty printing - Add `restore_cursor`, `use_kitty` and `kitty_delete` Config options ## 0.1.0 - Introduce block printing viuer-0.9.2/Cargo.lock0000644000001062610000000000100101570ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aligned-vec" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" dependencies = [ "equator", ] [[package]] name = "ansi_colours" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14eec43e0298190790f41679fe69ef7a829d2a2ddd78c8c00339e84710e435fe" dependencies = [ "rgb", ] [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "arg_enum_proc_macro" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" dependencies = [ "anyhow", "arrayvec", "log", "nom", "num-rational", "v_frame", ] [[package]] name = "avif-serialize" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" dependencies = [ "arrayvec", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitstream-io" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "built" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "bytemuck" version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder-lite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cfg-expr" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "console" version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys", ] [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.9.1", "crossterm_winapi", "parking_lot", "rustix 0.38.44", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equator" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" dependencies = [ "equator-macro", ] [[package]] name = "equator-macro" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys", ] [[package]] name = "exr" version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", "half", "lebe", "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gif" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ "color_quant", "weezl", ] [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "image" version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", "gif", "image-webp", "num-traits", "png", "qoi", "ravif", "rgb", "tiff", "zune-core", "zune-jpeg", ] [[package]] name = "image-webp" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14d75c7014ddab93c232bc6bb9f64790d3dfd1d605199acd4b40b6d69e691e9f" dependencies = [ "byteorder-lite", "quick-error", ] [[package]] name = "imgref" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "interpolate_name" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "jobserver" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ "getrandom 0.3.3", "libc", ] [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" [[package]] name = "libfuzzer-sys" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", ] [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" dependencies = [ "imgref", ] [[package]] name = "make-cmd" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8ca8afbe8af1785e09636acb5a41e08a765f5f0340568716c18a8700ba3c0d3" [[package]] name = "maybe-rayon" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "noop_proc_macro" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", "syn", ] [[package]] name = "qoi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ "bytemuck", ] [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "rav1e" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ "arbitrary", "arg_enum_proc_macro", "arrayvec", "av1-grain", "bitstream-io", "built", "cfg-if", "interpolate_name", "itertools", "libc", "libfuzzer-sys", "log", "maybe-rayon", "new_debug_unreachable", "noop_proc_macro", "num-derive", "num-traits", "once_cell", "paste", "profiling", "rand", "rand_chacha", "simd_helpers", "system-deps", "thiserror", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", "rgb", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags 2.9.1", ] [[package]] name = "rgb" version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys", ] [[package]] name = "rustix" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ "quote", ] [[package]] name = "sixel-rs" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfa95c014543113a192d906e5971d0c8d1e8b4cc1e61026539687a7016644ce5" dependencies = [ "sixel-sys", ] [[package]] name = "sixel-sys" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb46e0cd5569bf910390844174a5a99d52dd40681fff92228d221d9f8bf87dea" dependencies = [ "make-cmd", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "syn" version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix 1.0.7", "windows-sys", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiff" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" dependencies = [ "flate2", "jpeg-decoder", "weezl", ] [[package]] name = "toml" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "v_frame" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" dependencies = [ "aligned-vec", "num-traits", "wasm-bindgen", ] [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "viuer" version = "0.9.2" dependencies = [ "ansi_colours", "base64", "console", "crossterm", "image", "lazy_static", "sixel-rs", "tempfile", "termcolor", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "weezl" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.1", ] [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zune-core" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-inflate" version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] [[package]] name = "zune-jpeg" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f6fe2e33d02a98ee64423802e16df3de99c43e5cf5ff983767e1128b394c8ac" dependencies = [ "zune-core", ] viuer-0.9.2/Cargo.toml0000644000000033070000000000100101770ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "viuer" version = "0.9.2" authors = ["Atanas Yankov "] build = false exclude = [".github"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Display images in the terminal" documentation = "https://docs.rs/viuer" readme = "README.md" keywords = [ "terminal", "image", ] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/atanunq/viuer" [package.metadata.docs.rs] all-features = true [features] default = [] print-file = ["image/default-formats"] sixel = ["sixel-rs"] [lib] name = "viuer" path = "src/lib.rs" [dependencies.ansi_colours] version = "1" [dependencies.base64] version = "0.22" [dependencies.console] version = "0.15" default-features = false [dependencies.crossterm] version = "0.28" default-features = false [dependencies.image] version = "0.25" features = ["png"] default-features = false [dependencies.lazy_static] version = "1.5" [dependencies.sixel-rs] version = "0.3" optional = true [dependencies.tempfile] version = "3" [dependencies.termcolor] version = "1" [target."cfg(windows)".dependencies.crossterm] version = "0.28" features = ["windows"] default-features = false viuer-0.9.2/Cargo.toml.orig000064400000000000000000000021341046102023000136550ustar 00000000000000[package] name = "viuer" version = "0.9.2" authors = ["Atanas Yankov "] edition = "2021" description = "Display images in the terminal" documentation = "https://docs.rs/viuer" readme = "README.md" repository = "https://github.com/atanunq/viuer" license = "MIT" categories = ["command-line-interface"] keywords = ["terminal", "image"] exclude = [".github"] [dependencies] ansi_colours = "1" base64 = "0.22" console = { version = "0.15", default-features = false } crossterm = { version = "0.28", default-features = false } image = { version = "0.25", default-features = false, features = ["png"] } lazy_static = "1.5" tempfile = "3" termcolor = "1" sixel-rs = { version = "0.3", optional = true} [target.'cfg(windows)'.dependencies] crossterm = { version = "0.28", default-features = false, features = ["windows"]} [features] default = [] sixel = ["sixel-rs"] print-file = ["image/default-formats"] # Hide file printing behind a flag because it adds heavy dependencies. [package.metadata.docs.rs] # Show all methods in the documentation, even the non-default ones. all-features = true viuer-0.9.2/LICENSE-MIT000064400000000000000000000020561046102023000124250ustar 00000000000000MIT License Copyright (c) 2022 Atanas Yankov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. viuer-0.9.2/README.md000064400000000000000000000034221046102023000122460ustar 00000000000000# `viuer` ![ci](https://github.com/atanunq/viuer/actions/workflows/ci.yml/badge.svg) Display images in the terminal with ease. `viuer` is a Rust library that makes it easy to show images in the terminal. It has a straightforward interface and is configured through a single struct. The default printing method is through lower half blocks (`▄` or `\u2585`). However some custom graphics protocols are supported. They result in full resolution images being displayed in specific environments: - [Kitty](https://sw.kovidgoyal.net/kitty/graphics-protocol.html) - [iTerm](https://iterm2.com/documentation-images.html) - [Sixel](https://github.com/saitoha/libsixel) (behind the `sixel` feature gate) For a demo of the library's usage and example screenshots, see [`viu`](https://github.com/atanunq/viu?tab=readme-ov-file#examples). ## Usage With the default features, only [image::DynamicImage](https://docs.rs/image/latest/image/enum.DynamicImage.html) can be printed: ```rust use viuer::{print, Config}; let conf = Config { // Start from row 4 and column 20. x: 20, y: 4, ..Default::default() }; let img = image::DynamicImage::ImageRgba8(image::RgbaImage::new(20, 10)); print(&img, &conf).expect("Image printing failed."); ``` And with the `print-file` feature, `viuer` can work with files, too: ```rust use viuer::{print_from_file, Config}; let conf = Config { // Set dimensions. width: Some(80), height: Some(25), ..Default::default() }; // Display `img.jpg` with dimensions 80×25 in terminal cells. // The image resolution will be 80×50 because each cell contains two pixels. print_from_file("img.jpg", &conf).expect("Image printing failed."); ``` ## Docs Find all the configuration options in the [full documentation](https://docs.rs/viuer/latest/viuer/). viuer-0.9.2/src/config.rs000064400000000000000000000037471046102023000134030ustar 00000000000000use crate::utils; /// Configuration struct to customize printing behaviour. pub struct Config { /// Enable true transparency instead of checkerboard background. /// Available only for the block printer. Defaults to false. pub transparent: bool, /// If we assume the alpha channel is premultiplied for blending with the /// checkerboard background. /// Defaults to false. pub premultiplied_alpha: bool, /// Make the x and y offset be relative to the top left terminal corner. /// If false, the y offset is relative to the cursor's position. /// Defaults to true. pub absolute_offset: bool, /// X offset. Defaults to 0. pub x: u16, /// Y offset. Can be negative only when `absolute_offset` is `false`. /// Defaults to 0. pub y: i16, /// Take a note of cursor position before printing and restore it when finished. /// Defaults to false. pub restore_cursor: bool, /// Optional image width. Defaults to None. pub width: Option, /// Optional image height. Defaults to None. pub height: Option, /// Use truecolor if the terminal supports it. Defaults to true. pub truecolor: bool, /// Use Kitty protocol if the terminal supports it. Defaults to true. pub use_kitty: bool, /// Use iTerm protocol if the terminal supports it. Defaults to true. pub use_iterm: bool, /// Use Sixel protocol if the terminal supports it. Defaults to true. #[cfg(feature = "sixel")] pub use_sixel: bool, } impl std::default::Default for Config { fn default() -> Self { Self { transparent: false, premultiplied_alpha: false, absolute_offset: true, x: 0, y: 0, restore_cursor: false, width: None, height: None, truecolor: utils::truecolor_available(), use_kitty: true, use_iterm: true, #[cfg(feature = "sixel")] use_sixel: true, } } } viuer-0.9.2/src/error.rs000064400000000000000000000040611046102023000132550ustar 00000000000000/// Custom result type for error-prone operations pub type ViuResult = std::result::Result; /// Custom error enum for `viu`ing operations #[derive(Debug)] pub enum ViuError { /// Error while doing transformations with the [`image`] crate Image(image::ImageError), /// Error while doing IO operations Io(std::io::Error), /// Invalid configuration provided InvalidConfiguration(String), /// Error while creating temp files Tempfile(tempfile::PersistError), /// Errenous response received from Kitty KittyResponse(Vec), /// Kitty protocol not supported KittyNotSupported, /// Error while printing with sixel #[cfg(feature = "sixel")] SixelError(sixel_rs::status::Error), } impl std::error::Error for ViuError {} impl From for ViuError { fn from(err: std::io::Error) -> Self { ViuError::Io(err) } } impl From for ViuError { fn from(err: image::ImageError) -> Self { ViuError::Image(err) } } impl From for ViuError { fn from(err: tempfile::PersistError) -> Self { ViuError::Tempfile(err) } } #[cfg(feature = "sixel")] impl From for ViuError { fn from(e: sixel_rs::status::Error) -> Self { ViuError::SixelError(e) } } impl std::fmt::Display for ViuError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ViuError::Image(e) => write!(f, "Image error: {}", e), ViuError::Io(e) => write!(f, "IO error: {}", e), ViuError::InvalidConfiguration(s) => write!(f, "Invalid Configuration: {}", s), ViuError::Tempfile(e) => write!(f, "Tempfile error: {}", e), ViuError::KittyResponse(keys) => write!(f, "Kitty response: {:?}", keys), ViuError::KittyNotSupported => write!(f, "Kitty graphics protocol not supported"), #[cfg(feature = "sixel")] ViuError::SixelError(e) => write!(f, "Sixel error: {:?}", e), } } } viuer-0.9.2/src/lib.rs000064400000000000000000000116671046102023000127040ustar 00000000000000#![deny(missing_docs)] //! Library to display images in the terminal. //! //! This library contains functionality extracted from the [`viu`](https://github.com/atanunq/viu) crate. //! It aims to provide an easy to use interface to print images in the terminal. Uses some abstractions //! provided by the [`image`] crate. [Kitty](https://sw.kovidgoyal.net/kitty/graphics-protocol.html) //! and [iTerm](https://iterm2.com/documentation-images.html) graphic protocols are supported and used by default, //! if detected. If not, `viuer` will fallback to using regular half blocks instead (▄ and ▀). //! //! ## Basic Usage //! The default features of this crate can only work with [image::DynamicImage]. The below example //! creates a 60x60 gradient and prints it. More options are available through the [Config] struct. //! ``` //! use image::{DynamicImage, Pixel, Rgba, RgbaImage}; //! //! let conf = viuer::Config { //! absolute_offset: false, //! ..Default::default() //! }; //! //! let mut img = DynamicImage::ImageRgba8(RgbaImage::new(60, 60)); //! let start = Rgba::from_slice(&[0, 196, 0, 255]); //! let end = Rgba::from_slice(&[255, 255, 255, 255]); //! image::imageops::horizontal_gradient(&mut img, start, end); //! //! viuer::print(&img, &conf).unwrap(); //! ``` //! //! ## Decoding files //! To work directly with files, the non-default `print-file` feature must be enabled. //! //! The example below shows how to print the image `img.jpg` in 40x30 terminal cells, with vertical //! offset of 4 and horizontal of 10, starting from the top left corner. //! ```no_run //! let conf = viuer::Config { //! width: Some(40), //! height: Some(30), //! x: 10, //! y: 4, //! ..Default::default() //! }; //! //! #[cfg(feature="print-file")] //! viuer::print_from_file("img.jpg", &conf).expect("Image printing failed."); //! ``` #[cfg(feature = "print-file")] use std::path::Path; use crossterm::{ cursor::{RestorePosition, SavePosition}, execute, }; use image::DynamicImage; use printer::{Printer, PrinterType}; mod config; mod error; mod printer; mod utils; pub use config::Config; pub use error::{ViuError, ViuResult}; pub use printer::{get_kitty_support, is_iterm_supported, resize, KittySupport}; pub use utils::terminal_size; #[cfg(feature = "sixel")] pub use printer::is_sixel_supported; /// Default printing method. Uses either iTerm or Kitty graphics protocol, if supported, /// and half blocks otherwise. /// /// Check the [Config] struct for all customization options. /// ## Example /// The snippet below reads all of stdin, decodes it with the [`image`] crate /// and prints it to the terminal. The image will also be resized to fit in the terminal. /// /// ```no_run /// use std::io::{stdin, Read}; /// use viuer::{Config, print}; /// /// let stdin = stdin(); /// let mut handle = stdin.lock(); /// /// let mut buf: Vec = Vec::new(); /// let _ = handle /// .read_to_end(&mut buf) /// .expect("Could not read until EOF."); /// /// let img = image::load_from_memory(&buf).expect("Data from stdin could not be decoded."); /// print(&img, &Config::default()).expect("Image printing failed."); /// ``` pub fn print(img: &DynamicImage, config: &Config) -> ViuResult<(u32, u32)> { let mut stdout = std::io::stdout(); if config.restore_cursor { execute!(&mut stdout, SavePosition)?; } let (w, h) = choose_printer(config).print(&mut stdout, img, config)?; if config.restore_cursor { execute!(&mut stdout, RestorePosition)?; }; Ok((w, h)) } /// Helper method that reads a file, tries to decode and print it. The feature is available only /// with the `print-file` feature. /// /// ## Example /// ```no_run /// use viuer::{Config, print_from_file}; /// let conf = Config { /// width: Some(30), /// transparent: true, /// ..Default::default() /// }; /// // Image will be scaled down to width 30. Aspect ratio will be preserved. /// // Also, the terminal's background color will be used instead of checkerboard pattern. /// print_from_file("img.jpg", &conf).expect("Image printing failed."); /// ``` #[cfg(feature = "print-file")] pub fn print_from_file>(filename: P, config: &Config) -> ViuResult<(u32, u32)> { let mut stdout = std::io::stdout(); if config.restore_cursor { execute!(&mut stdout, SavePosition)?; } let (w, h) = choose_printer(config).print_from_file(&mut stdout, filename, config)?; if config.restore_cursor { execute!(&mut stdout, RestorePosition)?; }; Ok((w, h)) } // Choose the appropriate printer to use based on user config and availability fn choose_printer(config: &Config) -> PrinterType { #[cfg(feature = "sixel")] if config.use_sixel && is_sixel_supported() { return PrinterType::Sixel; } if config.use_iterm && is_iterm_supported() { PrinterType::iTerm } else if config.use_kitty && get_kitty_support() != KittySupport::None { PrinterType::Kitty } else { PrinterType::Block } } viuer-0.9.2/src/printer/block.rs000064400000000000000000000271571046102023000147140ustar 00000000000000use crate::error::ViuResult; use crate::printer::{adjust_offset, Printer}; use crate::Config; use ansi_colours::ansi256_from_rgb; use image::{DynamicImage, GenericImageView, Rgba}; use std::io::Write; use termcolor::{BufferedStandardStream, Color, ColorChoice, ColorSpec, WriteColor}; use crossterm::cursor::MoveRight; use crossterm::execute; const UPPER_HALF_BLOCK: &str = "\u{2580}"; const LOWER_HALF_BLOCK: &str = "\u{2584}"; const CHECKERBOARD_BACKGROUND_LIGHT: (u8, u8, u8) = (153, 153, 153); const CHECKERBOARD_BACKGROUND_DARK: (u8, u8, u8) = (102, 102, 102); pub struct BlockPrinter; impl Printer for BlockPrinter { fn print( &self, // TODO: The provided object is not used because termcolor needs an implementation of the WriteColor trait _stdout: &mut impl Write, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { let mut stream = BufferedStandardStream::stdout(ColorChoice::Always); print_to_writecolor(&mut stream, img, config) } } fn print_to_writecolor( stdout: &mut impl WriteColor, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { // adjust with x=0 and handle horizontal offset entirely below adjust_offset(stdout, &Config { x: 0, ..*config })?; // resize the image so that it fits in the constraints, if any let img = super::resize(img, config.width, config.height); let (width, height) = img.dimensions(); let mut row_color_buffer: Vec = vec![ColorSpec::new(); width as usize]; let img_buffer = img.to_rgba8(); //TODO: Can conversion be avoided? for (curr_row, img_row) in img_buffer.enumerate_rows() { let is_even_row = curr_row % 2 == 0; let is_last_row = curr_row == height - 1; // move right if x offset is specified if config.x > 0 && (!is_even_row || is_last_row) { execute!(stdout, MoveRight(config.x))?; } for pixel in img_row { // choose the half block's color let color = if is_pixel_transparent(pixel) { if config.transparent { None } else { Some(transparency_color(curr_row, pixel.0, config.truecolor)) } } else { Some(color_from_pixel(curr_row, pixel, config)) }; // Even rows modify the background, odd rows the foreground // because lower half blocks are used by default let colorspec = &mut row_color_buffer[pixel.0 as usize]; if is_even_row { colorspec.set_bg(color); if is_last_row { write_colored_character(stdout, colorspec, true)?; } } else { colorspec.set_fg(color); write_colored_character(stdout, colorspec, false)?; } } if !is_even_row && !is_last_row { stdout.reset()?; writeln!(stdout, "\r")?; } } stdout.reset()?; writeln!(stdout)?; stdout.flush()?; Ok((width, height / 2 + height % 2)) } fn write_colored_character( stdout: &mut impl WriteColor, c: &ColorSpec, is_last_row: bool, ) -> ViuResult { let out_color; let out_char; let mut new_color; // On the last row use upper blocks and leave the bottom half empty (transparent) if is_last_row { new_color = ColorSpec::new(); if let Some(bg) = c.bg() { new_color.set_fg(Some(*bg)); out_char = UPPER_HALF_BLOCK; } else { execute!(stdout, MoveRight(1))?; return Ok(()); } out_color = &new_color; } else { match (c.fg(), c.bg()) { (None, None) => { // completely transparent execute!(stdout, MoveRight(1))?; return Ok(()); } (Some(bottom), None) => { // only top transparent new_color = ColorSpec::new(); new_color.set_fg(Some(*bottom)); out_color = &new_color; out_char = LOWER_HALF_BLOCK; } (None, Some(top)) => { // only bottom transparent new_color = ColorSpec::new(); new_color.set_fg(Some(*top)); out_color = &new_color; out_char = UPPER_HALF_BLOCK; } (Some(_top), Some(_bottom)) => { // both parts have a color out_color = c; out_char = LOWER_HALF_BLOCK; } } } stdout.set_color(out_color)?; write!(stdout, "{}", out_char)?; Ok(()) } fn is_pixel_transparent(pixel: (u32, u32, &Rgba)) -> bool { pixel.2[3] == 0 } #[inline(always)] fn checkerboard(row: u32, col: u32) -> (u8, u8, u8) { if row % 2 == col % 2 { CHECKERBOARD_BACKGROUND_DARK } else { CHECKERBOARD_BACKGROUND_LIGHT } } #[inline(always)] fn transparency_color(row: u32, col: u32, truecolor: bool) -> Color { //imitate the transparent chess board pattern let rgb = checkerboard(row, col); if truecolor { Color::Rgb(rgb.0, rgb.1, rgb.2) } else { Color::Ansi256(ansi256_from_rgb(rgb)) } } /// Composes the foreground over the background. /// /// This assumes unpremultiplied alpha. #[inline(always)] fn over(fg: u8, bg: u8, alpha: u8) -> u8 { ((fg as u16 * alpha as u16 + bg as u16 * (255u16 - alpha as u16)) / 255) as _ } /// Composes the foreground over the background. /// /// This assumes premultiplied alpha (standard Porter-Duff compositing). #[inline(always)] fn over_porter_duff(fg: u8, bg: u8, alpha: u8) -> u8 { ((fg as u16 + bg as u16 * (255u16 - alpha as u16)) / 255) as _ } #[inline(always)] fn color_from_pixel(row: u32, pixel: (u32, u32, &Rgba), config: &Config) -> Color { let (col, _y, color) = pixel; let alpha = color[3]; let rgb = if !config.transparent && alpha < 255 { // We need to blend the pixel's color with the checkerboard pattern. let checker = checkerboard(row, col); if config.premultiplied_alpha { ( over_porter_duff(color[0], checker.0, alpha), over_porter_duff(color[1], checker.1, alpha), over_porter_duff(color[2], checker.2, alpha), ) } else { ( over(color[0], checker.0, alpha), over(color[1], checker.1, alpha), over(color[2], checker.2, alpha), ) } } else { (color[0], color[1], color[2]) }; if config.truecolor { Color::Rgb(rgb.0, rgb.1, rgb.2) } else { Color::Ansi256(ansi256_from_rgb(rgb)) } } #[cfg(test)] mod tests { use super::*; use termcolor::{Ansi, Color}; // Note: truecolor is not supported in CI. Hence, it should be disabled when writing the tests #[test] fn test_block_printer_e2e() { let img = DynamicImage::ImageRgba8(image::RgbaImage::new(5, 4)); let mut buf = Ansi::new(vec![]); let config = Config { truecolor: false, ..Default::default() }; let (w, h) = print_to_writecolor(&mut buf, &img, &config).unwrap(); assert_eq!((w, h), (5, 2)); assert_eq!( std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[1;1H\x1b[0m\x1b[38;5;247m\x1b[48;5;241m▄\x1b[0m\x1b[38;5;241m\x1b[48;5;247m▄\x1b[0m\x1b[38;5;247m\x1b[48;5;241m▄\x1b[0m\x1b[38;5;241m\x1b[48;5;247m▄\x1b[0m\x1b[38;5;247m\x1b[48;5;241m▄\x1b[0m\r\n\x1b[0m\x1b[38;5;247m\x1b[48;5;241m▄\x1b[0m\x1b[38;5;241m\x1b[48;5;247m▄\x1b[0m\x1b[38;5;247m\x1b[48;5;241m▄\x1b[0m\x1b[38;5;241m\x1b[48;5;247m▄\x1b[0m\x1b[38;5;247m\x1b[48;5;241m▄\x1b[0m\n" ); } #[test] fn test_block_printer_e2e_transparent() { let img = DynamicImage::ImageRgba8(image::RgbaImage::new(5, 4)); let mut buf = Ansi::new(vec![]); let config = Config { transparent: true, ..Default::default() }; let (w, h) = print_to_writecolor(&mut buf, &img, &config).unwrap(); assert_eq!((w, h), (5, 2)); assert_eq!( std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[1;1H\x1b[1C\x1b[1C\x1b[1C\x1b[1C\x1b[1C\x1b[0m\r\n\x1b[1C\x1b[1C\x1b[1C\x1b[1C\x1b[1C\x1b[0m\n" ); } #[test] fn test_block_printer_e2e_odd_height() { let img = DynamicImage::ImageRgba8(image::RgbaImage::new(4, 3)); let mut buf = Ansi::new(vec![]); let config = Config { truecolor: false, absolute_offset: false, ..Default::default() }; let (w, h) = print_to_writecolor(&mut buf, &img, &config).unwrap(); assert_eq!((w, h), (4, 2)); assert_eq!( std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[0m\x1b[38;5;247m\x1b[48;5;241m▄\x1b[0m\x1b[38;5;241m\x1b[48;5;247m▄\x1b[0m\x1b[38;5;247m\x1b[48;5;241m▄\x1b[0m\x1b[38;5;241m\x1b[48;5;247m▄\x1b[0m\r\n\x1b[0m\x1b[38;5;241m▀\x1b[0m\x1b[38;5;247m▀\x1b[0m\x1b[38;5;241m▀\x1b[0m\x1b[38;5;247m▀\x1b[0m\n" ); } #[test] fn test_write_colored_char_only_fg() { let mut buf = Ansi::new(vec![]); let mut c = ColorSpec::new(); c.set_fg(Some(Color::Rgb(10, 20, 30))); write_colored_character(&mut buf, &c, false).unwrap(); assert_eq!( std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[0m\x1b[38;2;10;20;30m▄" ); } #[test] fn test_write_colored_char_only_bg() { let mut buf = Ansi::new(vec![]); let mut c = ColorSpec::new(); c.set_bg(Some(Color::Rgb(50, 60, 70))); write_colored_character(&mut buf, &c, false).unwrap(); assert_eq!( std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[0m\x1b[38;2;50;60;70m▀" ); } #[test] fn test_write_colored_char_fg_and_bg() { let mut buf = Ansi::new(vec![]); let mut c = ColorSpec::new(); c.set_fg(Some(Color::Rgb(10, 20, 30))); c.set_bg(Some(Color::Rgb(15, 25, 35))); write_colored_character(&mut buf, &c, false).unwrap(); assert_eq!( std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[0m\x1b[38;2;10;20;30m\x1b[48;2;15;25;35m▄" ); } #[test] fn test_write_colored_char_no_color() { let mut buf = Ansi::new(vec![]); let c = ColorSpec::new(); write_colored_character(&mut buf, &c, false).unwrap(); // expect to print nothing, just move cursor to the right assert_eq!(std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[1C"); } #[test] fn test_write_colored_char_last_row_bg() { let mut buf = Ansi::new(vec![]); let mut c = ColorSpec::new(); c.set_bg(Some(Color::Rgb(10, 20, 30))); write_colored_character(&mut buf, &c, true).unwrap(); assert_eq!( std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[0m\x1b[38;2;10;20;30m▀" ); } #[test] fn test_write_colored_char_last_row_no_bg() { let mut buf = Ansi::new(vec![]); let mut c = ColorSpec::new(); // test with no color write_colored_character(&mut buf, &c, true).unwrap(); assert_eq!(std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[1C"); c.set_fg(Some(Color::Rgb(10, 20, 30))); // test with fg (unusual case) let mut buf = Ansi::new(vec![]); write_colored_character(&mut buf, &c, true).unwrap(); assert_eq!(std::str::from_utf8(buf.get_ref()).unwrap(), "\x1b[1C"); } } viuer-0.9.2/src/printer/iterm.rs000064400000000000000000000075061046102023000147360ustar 00000000000000use crate::error::ViuResult; use crate::printer::{adjust_offset, find_best_fit, Printer}; use crate::Config; use base64::{engine::general_purpose, Engine}; use image::{DynamicImage, GenericImageView, ImageEncoder}; use lazy_static::lazy_static; use std::io::Write; #[cfg(feature = "print-file")] use std::{ io::{BufReader, Read}, path::Path, }; #[allow(non_camel_case_types)] pub struct iTermPrinter; lazy_static! { static ref ITERM_SUPPORT: bool = check_iterm_support(); } /// Returns the terminal's support for the iTerm graphics protocol. pub fn is_iterm_supported() -> bool { *ITERM_SUPPORT } impl Printer for iTermPrinter { fn print( &self, stdout: &mut impl Write, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { let (width, height) = img.dimensions(); // Transform the dynamic image to a PNG which can be given directly to iTerm let mut png_bytes: Vec = Vec::new(); image::codecs::png::PngEncoder::new(&mut png_bytes).write_image( img.as_bytes(), width, height, img.color().into(), )?; print_buffer(stdout, img, &png_bytes[..], config) } #[cfg(feature = "print-file")] fn print_from_file>( &self, stdout: &mut impl Write, filename: P, config: &Config, ) -> ViuResult<(u32, u32)> { let file = std::fs::File::open(filename)?; // load the file content let mut buf_reader = BufReader::new(file); let mut file_content = Vec::new(); buf_reader.read_to_end(&mut file_content)?; let img = image::load_from_memory(&file_content[..])?; print_buffer(stdout, &img, &file_content[..], config) } } // This function requires both a DynamicImage, which is used to calculate dimensions, // and it's raw representation as a file, because that's the data iTerm needs to display it. fn print_buffer( stdout: &mut impl Write, img: &DynamicImage, img_content: &[u8], config: &Config, ) -> ViuResult<(u32, u32)> { adjust_offset(stdout, config)?; let (w, h) = find_best_fit(img, config.width, config.height); writeln!( stdout, "\x1b]1337;File=inline=1;preserveAspectRatio=1;size={};width={};height={}:{}\x07", img_content.len(), w, h, general_purpose::STANDARD.encode(img_content) )?; stdout.flush()?; Ok((w, h)) } // Check if the iTerm protocol can be used fn check_iterm_support() -> bool { if let Ok(term) = std::env::var("TERM_PROGRAM") { if term.contains("iTerm") || term.contains("WezTerm") || term.contains("mintty") || term.contains("rio") || term.contains("WarpTerminal") { return true; } } if let Ok(lc_term) = std::env::var("LC_TERMINAL") { if lc_term.contains("iTerm") || lc_term.contains("WezTerm") || lc_term.contains("mintty") || lc_term.contains("rio") { return true; } } false } #[cfg(test)] mod tests { use super::*; use image::GenericImage; #[test] fn test_print_e2e() { let mut img = DynamicImage::ImageRgba8(image::RgbaImage::new(2, 3)); img.put_pixel(1, 2, image::Rgba([2, 4, 6, 8])); let config = Config { x: 4, y: 3, ..Default::default() }; let mut vec = Vec::new(); assert_eq!(iTermPrinter.print(&mut vec, &img, &config).unwrap(), (2, 2)); assert_eq!(std::str::from_utf8(&vec).unwrap(), "\x1b[4;5H\x1b]1337;File=inline=1;preserveAspectRatio=1;size=95;width=2;height=2:iVBORw0KGgoAAAANSUhEUgAAAAIAAAADCAYAAAC56t6BAAAAJklEQVR4AQEbAOT/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBAYIAEMAFdTlTsEAAAAASUVORK5CYII=\x07\n"); } } viuer-0.9.2/src/printer/kitty.rs000064400000000000000000000170231046102023000147550ustar 00000000000000use crate::error::{ViuError, ViuResult}; use crate::printer::{adjust_offset, find_best_fit, Printer}; use crate::Config; use base64::{engine::general_purpose, Engine}; use console::{Key, Term}; use lazy_static::lazy_static; use std::io::Error; use std::io::Write; use tempfile::NamedTempFile; pub struct KittyPrinter; const TEMP_FILE_PREFIX: &str = ".tty-graphics-protocol.viuer."; lazy_static! { static ref KITTY_SUPPORT: KittySupport = check_kitty_support(); } /// Returns the terminal's support for the Kitty graphics protocol. pub fn get_kitty_support() -> KittySupport { *KITTY_SUPPORT } impl Printer for KittyPrinter { fn print( &self, stdout: &mut impl Write, img: &image::DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { match get_kitty_support() { KittySupport::None => Err(ViuError::KittyNotSupported), KittySupport::Local => { // print from file print_local(stdout, img, config) } KittySupport::Remote => { // print through escape codes print_remote(stdout, img, config) } } } // TODO: guess_format() here in order to treat PNGs specially (f=100). // Also, maybe get channel count and use f=24 or f=32 accordingly. // fn print_from_file(&self, filename: &str, config: &Config) -> ViuResult<(u32, u32)> {} } #[derive(PartialEq, Eq, Copy, Clone)] /// The extend to which the Kitty graphics protocol can be used. pub enum KittySupport { /// The Kitty graphics protocol is not supported. None, /// Kitty is running locally, data can be shared through a file. Local, /// Kitty is not running locally, data has to be sent through escape codes. Remote, } // Check if Kitty protocol can be used fn check_kitty_support() -> KittySupport { if let Ok(term) = std::env::var("TERM") { if term.contains("kitty") || term.contains("ghostty") { if has_local_support().is_ok() { return KittySupport::Local; } return KittySupport::Remote; } } KittySupport::None } // Query the terminal whether it can display an image from a file fn has_local_support() -> ViuResult { // create a temp file that will hold a 1x1 image let x = image::RgbaImage::new(1, 1); let raw_img = x.as_raw(); let temp_file = store_in_tmp_file(raw_img)?; // send the query print!( // t=t tells Kitty it's reading from a temp file and will attempt to delete if afterwards "\x1b_Gi=31,s=1,v=1,a=q,t=t;{}\x1b\\", general_purpose::STANDARD.encode( temp_file .path() .to_str() .ok_or_else(|| ViuError::Io(Error::other("Could not convert path to &str")))? ) ); std::io::stdout().flush()?; // collect Kitty's response after the query let term = Term::stdout(); let mut response = Vec::new(); while let Ok(key) = term.read_key() { // The response will end with Esc('x1b'), followed by Backslash('\'). // Also, break if the Unknown key is found, which is returned when we're not in a tty let should_break = key == Key::UnknownEscSeq(vec!['\\']) || key == Key::Unknown; response.push(key); if should_break { break; } } // Explicitly clean up when finished with the file because destructor, OS and Kitty are not deterministic. temp_file.close()?; // Kitty response should end with these 3 Keys if it was successful let expected = [ Key::Char('O'), Key::Char('K'), Key::UnknownEscSeq(vec!['\\']), ]; if response.len() >= expected.len() && response[response.len() - 3..] == expected { return Ok(()); } Err(ViuError::KittyResponse(response)) } // Print with kitty graphics protocol through a temp file // TODO: try with kitty's supported compression fn print_local( stdout: &mut impl Write, img: &image::DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { let rgba = img.to_rgba8(); let raw_img = rgba.as_raw(); let temp_file = store_in_tmp_file(raw_img)?; adjust_offset(stdout, config)?; // get the desired width and height let (w, h) = find_best_fit(img, config.width, config.height); write!( stdout, "\x1b_Gf=32,s={},v={},c={},r={},a=T,t=t;{}\x1b\\", img.width(), img.height(), w, h, general_purpose::STANDARD.encode( temp_file .path() .to_str() .ok_or_else(|| ViuError::Io(Error::other("Could not convert path to &str")))? ) )?; writeln!(stdout)?; stdout.flush()?; // Explicitly clean up when finished with the file because destructor, OS and Kitty are not deterministic. temp_file.close()?; Ok((w, h)) } // Print with escape codes // TODO: try compression fn print_remote( stdout: &mut impl Write, img: &image::DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { let rgba = img.to_rgba8(); let raw = rgba.as_raw(); let encoded = general_purpose::STANDARD.encode(raw); let mut iter = encoded.chars().peekable(); adjust_offset(stdout, config)?; let (w, h) = find_best_fit(img, config.width, config.height); let first_chunk: String = iter.by_ref().take(4096).collect(); // write the first chunk, which describes the image write!( stdout, "\x1b_Gf=32,a=T,t=d,s={},v={},c={},r={},m=1;{}\x1b\\", img.width(), img.height(), w, h, first_chunk )?; // write all the chunks, each containing 4096 bytes of data while iter.peek().is_some() { let chunk: String = iter.by_ref().take(4096).collect(); let m = if iter.peek().is_some() { 1 } else { 0 }; write!(stdout, "\x1b_Gm={};{}\x1b\\", m, chunk)?; } writeln!(stdout)?; stdout.flush()?; Ok((w, h)) } // Create a file in temporary dir and write the byte slice to it. // The NamedTempFile will be deleted once it goes out of scope. fn store_in_tmp_file(buf: &[u8]) -> std::result::Result { let mut tmpfile = tempfile::Builder::new() .prefix(TEMP_FILE_PREFIX) .rand_bytes(1) .tempfile()?; tmpfile.write_all(buf)?; tmpfile.flush()?; Ok(tmpfile) } #[cfg(test)] mod tests { use super::*; use image::{DynamicImage, GenericImage}; #[test] fn test_print_local() { let img = DynamicImage::ImageRgba8(image::RgbaImage::new(40, 25)); let config = Config { x: 4, y: 3, ..Default::default() }; let mut vec = Vec::new(); assert_eq!(print_local(&mut vec, &img, &config).unwrap(), (40, 13)); let result = std::str::from_utf8(&vec).unwrap(); assert!(result.starts_with("\x1b[4;5H\x1b_Gf=32,s=40,v=25,c=40,r=13,a=T,t=t;")); assert!(result.ends_with("\x1b\\\n")); } #[test] fn test_print_remote() { let mut img = DynamicImage::ImageRgba8(image::RgbaImage::new(1, 2)); img.put_pixel(0, 1, image::Rgba([2, 4, 6, 8])); let config = Config { x: 2, y: 5, ..Default::default() }; let mut vec = Vec::new(); assert_eq!(print_remote(&mut vec, &img, &config).unwrap(), (1, 1)); let result = std::str::from_utf8(&vec).unwrap(); assert_eq!( result, "\x1b[6;3H\x1b_Gf=32,a=T,t=d,s=1,v=2,c=1,r=1,m=1;AAAAAAIEBgg=\x1b\\\n" ); } } viuer-0.9.2/src/printer/mod.rs000064400000000000000000000353651046102023000144010ustar 00000000000000use crate::config::Config; use crate::error::{ViuError, ViuResult}; use crate::utils::terminal_size; use crossterm::cursor::{MoveRight, MoveTo, MoveToPreviousLine}; use crossterm::execute; use image::{DynamicImage, GenericImageView}; use std::io::Write; #[cfg(feature = "print-file")] use std::path::Path; mod block; pub use block::BlockPrinter; mod kitty; pub use kitty::{get_kitty_support, KittyPrinter, KittySupport}; #[cfg(feature = "sixel")] mod sixel; #[cfg(feature = "sixel")] pub use self::sixel::{is_sixel_supported, SixelPrinter}; mod iterm; pub use iterm::iTermPrinter; pub use iterm::is_iterm_supported; pub trait Printer { // Print the given image in the terminal while respecting the options in the config struct. // Return the dimensions of the printed image in **terminal cells**. fn print( &self, stdout: &mut impl Write, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)>; #[cfg(feature = "print-file")] fn print_from_file>( &self, stdout: &mut impl Write, filename: P, config: &Config, ) -> ViuResult<(u32, u32)> { let img = image::ImageReader::open(filename)? .with_guessed_format()? .decode()?; self.print(stdout, &img, config) } } #[allow(non_camel_case_types)] pub enum PrinterType { Block, Kitty, iTerm, #[cfg(feature = "sixel")] Sixel, } impl Printer for PrinterType { fn print( &self, stdout: &mut impl Write, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { match self { PrinterType::Block => BlockPrinter.print(stdout, img, config), PrinterType::Kitty => KittyPrinter.print(stdout, img, config), PrinterType::iTerm => iTermPrinter.print(stdout, img, config), #[cfg(feature = "sixel")] PrinterType::Sixel => SixelPrinter.print(stdout, img, config), } } #[cfg(feature = "print-file")] fn print_from_file>( &self, stdout: &mut impl Write, filename: P, config: &Config, ) -> ViuResult<(u32, u32)> { match self { PrinterType::Block => BlockPrinter.print_from_file(stdout, filename, config), PrinterType::Kitty => KittyPrinter.print_from_file(stdout, filename, config), PrinterType::iTerm => iTermPrinter.print_from_file(stdout, filename, config), #[cfg(feature = "sixel")] PrinterType::Sixel => SixelPrinter.print_from_file(stdout, filename, config), } } } /// Resize a [image::DynamicImage] so that it fits within optional width and height bounds. /// If none are provided, terminal size is used instead. pub fn resize(img: &DynamicImage, width: Option, height: Option) -> DynamicImage { let (w, h) = find_best_fit(img, width, height); // find_best_fit returns values in terminal cells. Hence, we multiply by two // because a 5x10 image can fit in 5x5 cells. However, a 5x9 image will also // fit in 5x5 and 1 is deducted in such cases. img.resize_exact( w, 2 * h - img.height() % 2, image::imageops::FilterType::CatmullRom, ) } /// Find the best dimensions for the printed image, based on user's input. /// Returns the dimensions of how the image should be printed in **terminal cells**. /// /// The behaviour is different based on the provided width and height: /// - If both are None, the image will be resized to fit in the terminal. Aspect ratio is preserved. /// - If only one is provided and the other is None, it will fit the image in the provided boundary. Aspect ratio is preserved. /// - If both are provided, the image will be resized to match the new size. Aspect ratio is **not** preserved. /// /// Example: /// Use None for both dimensions to use terminal size (80x24) instead. /// The image ratio is 2:1, the terminal can be split into 80x46 squares. /// The best fit would be to use the whole width (80) and 40 vertical squares, /// which is equivalent to 20 terminal cells. /// /// let img = image::DynamicImage::ImageRgba8(image::RgbaImage::new(160, 80)); /// let (w, h) = find_best_fit(&img, None, None); /// assert_eq!(w, 80); /// assert_eq!(h, 20); //TODO: it might make more sense to change signiture from img to (width, height) fn find_best_fit(img: &DynamicImage, width: Option, height: Option) -> (u32, u32) { let (img_width, img_height) = img.dimensions(); // Match user's width and height preferences match (width, height) { (None, None) => { let (term_w, term_h) = terminal_size(); let (w, h) = fit_dimensions(img_width, img_height, term_w as u32, term_h as u32); // One less row because two reasons: // - the prompt after executing the command will take a line // - gifs flicker let h = if h == term_h as u32 { h - 1 } else { h }; (w, h) } // Either width or height is specified, will fit and preserve aspect ratio. (Some(w), None) => fit_dimensions(img_width, img_height, w, img_height), (None, Some(h)) => fit_dimensions(img_width, img_height, img_width, h), // Both width and height are specified, will resize to match exactly (Some(w), Some(h)) => (w, h), } } /// Given width & height of an image, scale the size so that it can fit within given bounds /// while preserving aspect ratio. Will only scale down - if dimensions are smaller than the /// bounds, they will be returned unmodified. /// /// Note: input bounds are meant to hold dimensions of a terminal, where the height of a cell is /// twice it's width. It is best illustrated in an example: /// /// Trying to fit a 100x100 image in 40x15 terminal cells. The best fit, while having an aspect /// ratio of 1:1, would be to use all of the available height, 15, which is /// equivalent in size to 30 vertical cells. Hence, the returned dimensions will be 30x15. /// /// assert_eq!((30, 15), viuer::fit_dimensions(100, 100, 40, 15)); fn fit_dimensions(width: u32, height: u32, bound_width: u32, bound_height: u32) -> (u32, u32) { let bound_height = 2 * bound_height; if width <= bound_width && height <= bound_height { return (width, std::cmp::max(1, height / 2 + height % 2)); } let ratio = width * bound_height; let nratio = bound_width * height; let use_width = nratio <= ratio; let intermediate = if use_width { height * bound_width / width } else { width * bound_height / height }; if use_width { (bound_width, std::cmp::max(1, intermediate / 2)) } else { (intermediate, std::cmp::max(1, bound_height / 2)) } } // Move the cursor to a location from where it should start printing. Calculations are based on // offsets from the config. fn adjust_offset(stdout: &mut impl Write, config: &Config) -> ViuResult { if config.absolute_offset { if config.y >= 0 { // If absolute_offset, move to (x,y). execute!(stdout, MoveTo(config.x, config.y as u16))?; } else { //Negative values do not make sense. return Err(ViuError::InvalidConfiguration( "absolute_offset is true but y offset is negative".to_owned(), )); } } else { if config.y < 0 { // MoveUp if negative execute!(stdout, MoveToPreviousLine(-config.y as u16))?; } else { // Move down y lines for _ in 0..config.y { // writeln! is used instead of MoveDown to force scrolldown // observed when config.y > 0 and cursor is on the last terminal line writeln!(stdout)?; } } // Some terminals interpret 0 as 1, see MoveRight documentation if config.x > 0 { execute!(stdout, MoveRight(config.x))?; } } Ok(()) } #[cfg(test)] mod tests { use super::*; fn test_adjust_offset_output(config: &Config, str: &str) { let mut vec = Vec::new(); adjust_offset(&mut vec, config).unwrap(); assert_eq!(std::str::from_utf8(&vec).unwrap(), str); } fn best_fit_large_test_image() -> DynamicImage { DynamicImage::ImageRgba8(image::RgbaImage::new(600, 499)) } fn best_fit_small_test_image() -> DynamicImage { DynamicImage::ImageRgba8(image::RgbaImage::new(40, 25)) } fn resize_get_large_test_image() -> DynamicImage { DynamicImage::ImageRgba8(image::RgbaImage::new(1000, 799)) } fn resize_get_small_test_image() -> DynamicImage { DynamicImage::ImageRgba8(image::RgbaImage::new(20, 10)) } // Resize tests #[test] fn test_resize_none() { let width = None; let height = None; let img = resize_get_large_test_image(); let new_img = resize(&img, width, height); assert_eq!(new_img.width(), 60); assert_eq!(new_img.height(), 45); let img = resize_get_small_test_image(); let new_img = resize(&img, width, height); assert_eq!(new_img.width(), 20); assert_eq!(new_img.height(), 10); } #[test] fn test_resize_some_none() { let width = Some(100); let height = None; let img = resize_get_large_test_image(); let new_img = resize(&img, width, height); assert_eq!(new_img.width(), 100); assert_eq!(new_img.height(), 77); let img = resize_get_small_test_image(); let new_img = resize(&img, width, height); assert_eq!(new_img.width(), 20); assert_eq!(new_img.height(), 10); } #[test] fn test_resize_none_some() { let width = None; let mut height = Some(90); let img = resize_get_large_test_image(); let new_img = resize(&img, width, height); assert_eq!(new_img.width(), 225); assert_eq!(new_img.height(), 179); height = Some(4); let img = resize_get_small_test_image(); let new_img = resize(&img, width, height); assert_eq!(new_img.width(), 16); assert_eq!(new_img.height(), 8); } #[test] fn test_resize_some_some() { let width = Some(15); let height = Some(9); let img = resize_get_large_test_image(); let new_img = resize(&img, width, height); assert_eq!(new_img.width(), 15); assert_eq!(new_img.height(), 17); let img = resize_get_small_test_image(); let new_img = resize(&img, width, height); assert_eq!(new_img.width(), 15); assert_eq!(new_img.height(), 18); } // Best fit tests #[test] fn find_best_fit_none() { let width = None; let height = None; let img = best_fit_large_test_image(); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 57); assert_eq!(h, 23); let img = best_fit_small_test_image(); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 40); assert_eq!(h, 13); let img = DynamicImage::ImageRgba8(image::RgbaImage::new(160, 80)); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 80); assert_eq!(h, 20); } #[test] fn find_best_fit_some_none() { let width = Some(100); let height = None; let img = best_fit_large_test_image(); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 100); assert_eq!(h, 41); let img = best_fit_small_test_image(); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 40); assert_eq!(h, 13); let width = Some(6); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 6); assert_eq!(h, 1); let width = Some(3); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 3); assert_eq!(h, 1); } #[test] fn find_best_fit_none_some() { let width = None; let height = Some(90); let img = best_fit_large_test_image(); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 216); assert_eq!(h, 90); let height = Some(4); let img = best_fit_small_test_image(); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 12); assert_eq!(h, 4); } #[test] fn find_best_fit_some_some() { let width = Some(15); let height = Some(9); let img = best_fit_large_test_image(); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 15); assert_eq!(h, 9); let img = best_fit_small_test_image(); let (w, h) = find_best_fit(&img, width, height); assert_eq!(w, 15); assert_eq!(h, 9); } #[test] fn test_fit_dimensions() { // ratio 1:1 assert_eq!((40, 20), fit_dimensions(100, 100, 40, 50)); assert_eq!((20, 10), fit_dimensions(100, 100, 40, 10)); // ratio 3:2 assert_eq!((30, 10), fit_dimensions(240, 160, 30, 100)); // ratio 5:7 assert_eq!((200, 140), fit_dimensions(300, 420, 320, 140)); } #[test] fn test_fit_smaller_than_bounds() { assert_eq!((4, 2), fit_dimensions(4, 3, 80, 24)); assert_eq!((4, 1), fit_dimensions(4, 1, 80, 24)); } #[test] fn test_fit_equal_to_bounds() { assert_eq!((80, 12), fit_dimensions(80, 24, 80, 24)); } #[test] fn test_zero_offset() { let config = Config { absolute_offset: false, x: 0, y: 0, ..Default::default() }; // Should not move at all test_adjust_offset_output(&config, ""); } #[test] fn test_adjust_offset_absolute() { let mut config = Config { absolute_offset: true, x: 3, y: 4, ..Default::default() }; config.x = 3; config.y = 0; test_adjust_offset_output(&config, "\x1b[1;4H"); config.x = 7; config.y = 4; test_adjust_offset_output(&config, "\x1b[5;8H"); } #[test] fn test_adjust_offset_not_absolute() { let mut config = Config { absolute_offset: false, x: 3, y: 4, ..Default::default() }; test_adjust_offset_output(&config, "\n\n\n\n\x1b[3C"); config.x = 1; config.y = -2; test_adjust_offset_output(&config, "\x1b[2F\x1b[1C"); } #[test] fn test_invalid_adjust_offset() { let config = Config { absolute_offset: true, y: -1, ..Default::default() }; let mut vec = Vec::new(); let err = adjust_offset(&mut vec, &config).unwrap_err(); assert!(matches!(err, ViuError::InvalidConfiguration { .. })); } } viuer-0.9.2/src/printer/sixel.rs000064400000000000000000000053661046102023000147440ustar 00000000000000use crate::error::ViuResult; use crate::printer::{adjust_offset, find_best_fit, Printer}; use crate::Config; use console::{Key, Term}; use image::{imageops::FilterType, DynamicImage, GenericImageView}; use lazy_static::lazy_static; use sixel_rs::encoder::{Encoder, QuickFrameBuilder}; use sixel_rs::optflags::EncodePolicy; use std::io::Write; pub struct SixelPrinter; lazy_static! { static ref SIXEL_SUPPORT: bool = check_sixel_support(); } /// Returns the terminal's support for Sixel. pub fn is_sixel_supported() -> bool { *SIXEL_SUPPORT } impl Printer for SixelPrinter { fn print( &self, stdout: &mut impl Write, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { let (w, h) = find_best_fit(img, config.width, config.height); //TODO: the max 1000 width is an xterm bug workaround, other terminals may not be affected let resized_img = img.resize_exact(std::cmp::min(6 * w, 1000), 12 * h, FilterType::Triangle); let (width, height) = resized_img.dimensions(); let rgba = resized_img.to_rgba8(); let raw = rgba.as_raw(); adjust_offset(stdout, config)?; let encoder = Encoder::new()?; encoder.set_encode_policy(EncodePolicy::Fast)?; let frame = QuickFrameBuilder::new() .width(width as usize) .height(height as usize) .format(sixel_rs::sys::PixelFormat::RGBA8888) .pixels(raw.to_vec()); encoder.encode_bytes(frame)?; Ok((w, h)) } } // Check if Sixel is within the terminal's attributes // see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Sixel-Graphics // and https://vt100.net/docs/vt510-rm/DA1.html fn check_device_attrs() -> ViuResult { let mut term = Term::stdout(); write!(&mut term, "\x1b[c")?; term.flush()?; let mut response = String::new(); while let Ok(key) = term.read_key() { if let Key::Char(c) = key { response.push(c); if c == 'c' { break; } } } Ok(response.contains(";4;") || response.contains(";4c")) } // Check if Sixel protocol can be used fn check_sixel_support() -> bool { if let Ok(term) = std::env::var("TERM") { match term.as_str() { "mlterm" | "yaft-256color" | "foot" | "foot-extra" | "eat-truecolor" | "rio" => { return true } "st-256color" | "xterm" | "xterm-256color" => { return check_device_attrs().unwrap_or(false) } _ => { if let Ok(term_program) = std::env::var("TERM_PROGRAM") { return term_program == "MacTerm"; } } } } false } viuer-0.9.2/src/utils.rs000064400000000000000000000016341046102023000132670ustar 00000000000000use std::env; const DEFAULT_TERM_SIZE: (u16, u16) = (80, 24); pub fn truecolor_available() -> bool { if let Ok(value) = env::var("COLORTERM") { value.contains("truecolor") || value.contains("24bit") } else { false } } /// Try to get the terminal size. If unsuccessful, fallback to a default (80x24). Uses [crossterm::terminal::size]. #[cfg(not(test))] pub fn terminal_size() -> (u16, u16) { match crossterm::terminal::size() { Ok(s) => s, Err(_) => DEFAULT_TERM_SIZE, } } /// Returns a constant and only used when running the tests. #[cfg(test)] pub fn terminal_size() -> (u16, u16) { DEFAULT_TERM_SIZE } #[cfg(test)] mod tests { use super::*; #[test] fn test_truecolor() { env::set_var("COLORTERM", "truecolor"); assert!(truecolor_available()); env::set_var("COLORTERM", ""); assert!(!truecolor_available()); } }