mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 03:55:23 +00:00
Image preview (#363)
I initially didn't notice that an image previewer was already implemented but commented out—still, I wanted to finish mine! :) It works almost instantly on my side. I tested it in different terminals and only noticed some slowness in the RustRover-integrated terminal. So far, I’ve tested it with PNG, JPEG, ICO, GIF, and TIFF formats, and it works well. In theory, it should support all formats that the image crate can handle. I included them in the file list but commented out the ones I haven’t tested yet. To optimize memory usage, images are resized to a maximum of 128x128 before being cached. I’m not really sure what the best size is, since the image gets resized again when rendered to fit the preview window. Let me know if you have any feedback! 🚀    --------- Co-authored-by: Alexandre Pasmantier <47638216+alexpasmantier@users.noreply.github.com> Co-authored-by: alexpasmantier <alex.pasmant@gmail.com>
This commit is contained in:
parent
d47d6f7850
commit
e6c1a2a2a2
547
Cargo.lock
generated
547
Cargo.lock
generated
@ -26,6 +26,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned-vec"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
@ -103,12 +109,58 @@ version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[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.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf"
|
||||
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 = "backtrace"
|
||||
version = "0.3.74"
|
||||
@ -186,6 +238,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -201,6 +259,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitstream-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.11.3"
|
||||
@ -211,6 +275,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
@ -223,6 +293,18 @@ version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.9.0"
|
||||
@ -262,9 +344,21 @@ version = "1.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf"
|
||||
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.0"
|
||||
@ -357,6 +451,12 @@ dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
@ -622,12 +722,36 @@ version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
|
||||
|
||||
[[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 = "filedescriptor"
|
||||
version = "0.8.2"
|
||||
@ -755,6 +879,16 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[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 = "gimli"
|
||||
version = "0.31.1"
|
||||
@ -854,6 +988,45 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"color_quant",
|
||||
"exr",
|
||||
"gif",
|
||||
"image-webp",
|
||||
"num-traits",
|
||||
"png",
|
||||
"qoi",
|
||||
"ravif",
|
||||
"rayon",
|
||||
"rgb",
|
||||
"tiff",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f"
|
||||
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.7.1"
|
||||
@ -884,6 +1057,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[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 = "is-terminal"
|
||||
version = "0.4.15"
|
||||
@ -910,6 +1094,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
@ -925,6 +1118,21 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@ -941,12 +1149,28 @@ 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.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[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 = "libredox"
|
||||
version = "0.1.3"
|
||||
@ -979,6 +1203,15 @@ version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "loop9"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
|
||||
dependencies = [
|
||||
"imgref",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.12.5"
|
||||
@ -997,6 +1230,16 @@ dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maybe-rayon"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
@ -1016,6 +1259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1030,6 +1274,12 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -1040,6 +1290,12 @@ dependencies = [
|
||||
"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 = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@ -1080,12 +1336,53 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[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-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[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"
|
||||
@ -1258,12 +1555,34 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[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 = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
@ -1273,6 +1592,40 @@ 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 = "quick-xml"
|
||||
version = "0.32.0"
|
||||
@ -1291,6 +1644,36 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[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.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
@ -1313,6 +1696,56 @@ dependencies = [
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[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 0.12.1",
|
||||
"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 1.0.69",
|
||||
"v_frame",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ravif"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6"
|
||||
dependencies = [
|
||||
"avif-serialize",
|
||||
"imgref",
|
||||
"loop9",
|
||||
"quick-error",
|
||||
"rav1e",
|
||||
"rayon",
|
||||
"rgb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
@ -1586,6 +2019,21 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[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 = "simdutf8"
|
||||
version = "0.1.5"
|
||||
@ -1679,6 +2127,25 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[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 = "television"
|
||||
version = "0.10.6"
|
||||
@ -1696,6 +2163,7 @@ dependencies = [
|
||||
"gag",
|
||||
"human-panic",
|
||||
"ignore",
|
||||
"image",
|
||||
"nom",
|
||||
"nucleo",
|
||||
"parking_lot",
|
||||
@ -1818,6 +2286,17 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[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 = "time"
|
||||
version = "0.3.37"
|
||||
@ -2040,12 +2519,29 @@ dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v_frame"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b"
|
||||
dependencies = [
|
||||
"aligned-vec",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
@ -2139,6 +2635,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@ -2328,3 +2830,48 @@ name = "xterm-color"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4de5f056fb9dc8b7908754867544e26145767187aaac5a98495e88ad7cb8a80f"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
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.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
@ -58,6 +58,8 @@ bat = { version = "0.25", default-features = false, features = ["regex-onig"] }
|
||||
gag = "1.0"
|
||||
nucleo = "0.5"
|
||||
toml = "0.8"
|
||||
image = "0.25"
|
||||
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi-util = "0.1.9"
|
||||
|
@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::channels::entry::{Entry, PreviewType};
|
||||
use devicons::FileIcon;
|
||||
use ratatui::layout::Rect;
|
||||
|
||||
pub mod ansi;
|
||||
pub mod cache;
|
||||
@ -9,6 +10,7 @@ pub mod previewers;
|
||||
|
||||
// previewer types
|
||||
use crate::utils::cache::RingSet;
|
||||
use crate::utils::image::ImagePreviewWidget;
|
||||
use crate::utils::syntax::HighlightedLines;
|
||||
pub use previewers::basic::BasicPreviewer;
|
||||
pub use previewers::basic::BasicPreviewerConfig;
|
||||
@ -30,6 +32,7 @@ pub enum PreviewContent {
|
||||
PlainText(Vec<String>),
|
||||
PlainTextWrapped(String),
|
||||
AnsiText(String),
|
||||
Image(ImagePreviewWidget),
|
||||
}
|
||||
|
||||
impl PreviewContent {
|
||||
@ -44,6 +47,9 @@ impl PreviewContent {
|
||||
PreviewContent::AnsiText(text) => {
|
||||
text.lines().count().try_into().unwrap_or(u16::MAX)
|
||||
}
|
||||
PreviewContent::Image(image) => {
|
||||
image.height().try_into().unwrap_or(u16::MAX)
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
@ -203,11 +209,15 @@ impl Previewer {
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_request(&mut self, entry: &Entry) -> Option<Arc<Preview>> {
|
||||
fn dispatch_request(
|
||||
&mut self,
|
||||
entry: &Entry,
|
||||
preview_window: Option<Rect>,
|
||||
) -> Option<Arc<Preview>> {
|
||||
match &entry.preview_type {
|
||||
PreviewType::Basic => Some(self.basic.preview(entry)),
|
||||
PreviewType::EnvVar => Some(self.env_var.preview(entry)),
|
||||
PreviewType::Files => self.file.preview(entry),
|
||||
PreviewType::Files => self.file.preview(entry, preview_window),
|
||||
PreviewType::Command(cmd) => self.command.preview(entry, cmd),
|
||||
PreviewType::None => Some(Arc::new(Preview::default())),
|
||||
}
|
||||
@ -217,17 +227,23 @@ impl Previewer {
|
||||
// faster, but since it's already running in the background and quite
|
||||
// fast for most standard file sizes, plus we're caching the previews,
|
||||
// I'm not sure the extra complexity is worth it.
|
||||
pub fn preview(&mut self, entry: &Entry) -> Option<Arc<Preview>> {
|
||||
pub fn preview(
|
||||
&mut self,
|
||||
entry: &Entry,
|
||||
preview_window: Option<Rect>,
|
||||
) -> Option<Arc<Preview>> {
|
||||
// if we haven't acknowledged the request yet, acknowledge it
|
||||
self.requests.push(entry.clone());
|
||||
|
||||
if let Some(preview) = self.dispatch_request(entry) {
|
||||
if let Some(preview) = self.dispatch_request(entry, preview_window) {
|
||||
return Some(preview);
|
||||
}
|
||||
|
||||
// lookup request stack and return the most recent preview available
|
||||
for request in self.requests.back_to_front() {
|
||||
if let Some(preview) = self.dispatch_request(&request) {
|
||||
if let Some(preview) =
|
||||
self.dispatch_request(&request, preview_window)
|
||||
{
|
||||
return Some(preview);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use crate::utils::files::{read_into_lines_capped, ReadResult};
|
||||
use crate::utils::syntax::HighlightedLines;
|
||||
use image::ImageReader;
|
||||
use parking_lot::Mutex;
|
||||
use ratatui::layout::Rect;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
@ -10,13 +12,13 @@ use std::sync::{
|
||||
atomic::{AtomicU8, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
use syntect::{highlighting::Theme, parsing::SyntaxSet};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::channels::entry;
|
||||
use crate::preview::cache::PreviewCache;
|
||||
use crate::preview::{previewers::meta, Preview, PreviewContent};
|
||||
use crate::utils::image::ImagePreviewWidget;
|
||||
use crate::utils::{
|
||||
files::FileType,
|
||||
strings::preprocess_line,
|
||||
@ -78,20 +80,28 @@ impl FilePreviewer {
|
||||
self.cache.lock().get(&entry.name)
|
||||
}
|
||||
|
||||
pub fn preview(&mut self, entry: &entry::Entry) -> Option<Arc<Preview>> {
|
||||
pub fn preview(
|
||||
&mut self,
|
||||
entry: &entry::Entry,
|
||||
preview_window: Option<Rect>,
|
||||
) -> Option<Arc<Preview>> {
|
||||
if let Some(preview) = self.cached(entry) {
|
||||
trace!("Preview cache hit for {:?}", entry.name);
|
||||
if preview.partial_offset.is_some() {
|
||||
// preview is partial, spawn a task to compute the next chunk
|
||||
// and return the partial preview
|
||||
debug!("Spawning partial preview task for {:?}", entry.name);
|
||||
self.handle_preview_request(entry, Some(preview.clone()));
|
||||
self.handle_preview_request(
|
||||
entry,
|
||||
Some(preview.clone()),
|
||||
preview_window,
|
||||
);
|
||||
}
|
||||
Some(preview)
|
||||
} else {
|
||||
// preview is not in cache, spawn a task to compute the preview
|
||||
trace!("Preview cache miss for {:?}", entry.name);
|
||||
self.handle_preview_request(entry, None);
|
||||
self.handle_preview_request(entry, None, preview_window);
|
||||
None
|
||||
}
|
||||
}
|
||||
@ -100,6 +110,7 @@ impl FilePreviewer {
|
||||
&mut self,
|
||||
entry: &entry::Entry,
|
||||
partial_preview: Option<Arc<Preview>>,
|
||||
preview_window: Option<Rect>,
|
||||
) {
|
||||
if self.in_flight_previews.lock().contains(&entry.name) {
|
||||
trace!("Preview already in flight for {:?}", entry.name);
|
||||
@ -126,6 +137,7 @@ impl FilePreviewer {
|
||||
&syntax_theme,
|
||||
&concurrent_tasks,
|
||||
&in_flight_previews,
|
||||
preview_window,
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -141,6 +153,7 @@ impl FilePreviewer {
|
||||
/// This ends up being the max size of partial previews.
|
||||
const PARTIAL_BUFREAD_SIZE: usize = 5 * 1024 * 1024;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn try_preview(
|
||||
entry: &entry::Entry,
|
||||
partial_preview: Option<Arc<Preview>>,
|
||||
@ -149,6 +162,7 @@ pub fn try_preview(
|
||||
syntax_theme: &Arc<Theme>,
|
||||
concurrent_tasks: &Arc<AtomicU8>,
|
||||
in_flight_previews: &Arc<Mutex<FxHashSet<String>>>,
|
||||
preview_window: Option<Rect>,
|
||||
) {
|
||||
debug!("Computing preview for {:?}", entry.name);
|
||||
let path = PathBuf::from(&entry.name);
|
||||
@ -236,8 +250,66 @@ pub fn try_preview(
|
||||
cache.lock().insert(entry.name.clone(), &p);
|
||||
}
|
||||
}
|
||||
} else if matches!(FileType::from(&path), FileType::Image) {
|
||||
cache.lock().insert(
|
||||
entry.name.clone(),
|
||||
&meta::loading(&format!("Loading {}", entry.name)),
|
||||
);
|
||||
|
||||
debug!("File {:?} is an image", entry.name);
|
||||
let option_image = match ImageReader::open(path) {
|
||||
Ok(reader) => match reader.with_guessed_format() {
|
||||
Ok(reader) => match reader.decode() {
|
||||
Ok(image) => Some(image),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Error impossible to decode {}: {:?}",
|
||||
entry.name, e
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Error impossible to guess the format of {}: {:?}",
|
||||
entry.name, e
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("Error opening image {}: {:?}", entry.name, e);
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(image) = option_image {
|
||||
let preview_window_dimension = preview_window.map(|rect| {
|
||||
(
|
||||
u32::from(rect.width.saturating_sub(2)),
|
||||
u32::from(rect.height.saturating_sub(2)),
|
||||
) // - 2 for the margin
|
||||
});
|
||||
let image_preview_widget = ImagePreviewWidget::from_dynamic_image(
|
||||
image,
|
||||
preview_window_dimension,
|
||||
);
|
||||
let total_lines =
|
||||
image_preview_widget.height().try_into().unwrap_or(u16::MAX);
|
||||
let content = PreviewContent::Image(image_preview_widget);
|
||||
let preview = Arc::new(Preview::new(
|
||||
entry.name.clone(),
|
||||
content,
|
||||
entry.icon,
|
||||
None,
|
||||
total_lines,
|
||||
));
|
||||
cache.lock().insert(entry.name.clone(), &preview);
|
||||
} else {
|
||||
debug!("File isn't text-based: {:?}", entry.name);
|
||||
let p = meta::not_supported(&entry.name);
|
||||
cache.lock().insert(entry.name.clone(), &p);
|
||||
}
|
||||
} else {
|
||||
debug!("File format isn't supported for preview: {:?}", entry.name);
|
||||
let preview = meta::not_supported(&entry.name);
|
||||
cache.lock().insert(entry.name.clone(), &preview);
|
||||
}
|
||||
|
@ -4,13 +4,17 @@ use crate::preview::{
|
||||
PREVIEW_NOT_SUPPORTED_MSG, TIMEOUT_MSG,
|
||||
};
|
||||
use crate::screen::colors::{Colorscheme, PreviewColorscheme};
|
||||
use crate::utils::image::ImagePreviewWidget;
|
||||
use crate::utils::strings::{
|
||||
replace_non_printable, shrink_with_ellipsis, ReplaceNonPrintableConfig,
|
||||
EMPTY_STRING,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use devicons::FileIcon;
|
||||
use ratatui::widgets::{Block, BorderType, Borders, Padding, Paragraph, Wrap};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::widgets::{
|
||||
Block, BorderType, Borders, Padding, Paragraph, Widget, Wrap,
|
||||
};
|
||||
use ratatui::Frame;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
@ -22,6 +26,22 @@ use std::str::FromStr;
|
||||
const FILL_CHAR_SLANTED: char = '╱';
|
||||
const FILL_CHAR_EMPTY: char = ' ';
|
||||
|
||||
pub enum PreviewWidget<'a> {
|
||||
Paragraph(Paragraph<'a>),
|
||||
Image(ImagePreviewWidget),
|
||||
}
|
||||
impl Widget for PreviewWidget<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
PreviewWidget::Paragraph(p) => p.render(area, buf),
|
||||
PreviewWidget::Image(image) => image.render(area, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn draw_preview_content_block(
|
||||
f: &mut Frame,
|
||||
@ -38,9 +58,8 @@ pub fn draw_preview_content_block(
|
||||
&preview_state.preview.title,
|
||||
use_nerd_font_icons,
|
||||
)?;
|
||||
|
||||
// render the preview content
|
||||
let rp = build_preview_paragraph(
|
||||
let rp = build_preview_widget(
|
||||
inner,
|
||||
&preview_state.preview.content,
|
||||
preview_state.target_line,
|
||||
@ -48,16 +67,17 @@ pub fn draw_preview_content_block(
|
||||
colorscheme,
|
||||
);
|
||||
f.render_widget(rp, inner);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build_preview_paragraph<'a>(
|
||||
pub fn build_preview_widget<'a>(
|
||||
inner: Rect,
|
||||
preview_content: &'a PreviewContent,
|
||||
target_line: Option<u16>,
|
||||
preview_scroll: u16,
|
||||
colorscheme: &'a Colorscheme,
|
||||
) -> Paragraph<'a> {
|
||||
) -> PreviewWidget<'a> {
|
||||
let preview_block =
|
||||
Block::default().style(Style::default()).padding(Padding {
|
||||
top: 0,
|
||||
@ -65,43 +85,49 @@ pub fn build_preview_paragraph<'a>(
|
||||
bottom: 0,
|
||||
left: 1,
|
||||
});
|
||||
|
||||
match preview_content {
|
||||
PreviewContent::AnsiText(text) => {
|
||||
build_ansi_text_paragraph(text, preview_block, preview_scroll)
|
||||
}
|
||||
PreviewContent::PlainText(content) => build_plain_text_paragraph(
|
||||
PreviewContent::AnsiText(text) => PreviewWidget::Paragraph(
|
||||
build_ansi_text_paragraph(text, preview_block, preview_scroll),
|
||||
),
|
||||
PreviewContent::PlainText(content) => {
|
||||
PreviewWidget::Paragraph(build_plain_text_paragraph(
|
||||
content,
|
||||
preview_block,
|
||||
target_line,
|
||||
preview_scroll,
|
||||
colorscheme.preview,
|
||||
),
|
||||
PreviewContent::PlainTextWrapped(content) => {
|
||||
))
|
||||
}
|
||||
PreviewContent::PlainTextWrapped(content) => PreviewWidget::Paragraph(
|
||||
build_plain_text_wrapped_paragraph(
|
||||
content,
|
||||
preview_block,
|
||||
colorscheme.preview,
|
||||
)
|
||||
.scroll((preview_scroll, 0))
|
||||
}
|
||||
.scroll((preview_scroll, 0)),
|
||||
),
|
||||
PreviewContent::SyntectHighlightedText(highlighted_lines) => {
|
||||
build_syntect_highlighted_paragraph(
|
||||
PreviewWidget::Paragraph(build_syntect_highlighted_paragraph(
|
||||
&highlighted_lines.lines,
|
||||
preview_block,
|
||||
target_line,
|
||||
preview_scroll,
|
||||
colorscheme.preview,
|
||||
inner.height,
|
||||
)
|
||||
))
|
||||
}
|
||||
PreviewContent::Image(image) => PreviewWidget::Image(image.clone()),
|
||||
|
||||
// meta
|
||||
PreviewContent::Loading => {
|
||||
PreviewContent::Loading => PreviewWidget::Paragraph(
|
||||
build_meta_preview_paragraph(inner, LOADING_MSG, FILL_CHAR_EMPTY)
|
||||
.block(preview_block)
|
||||
.alignment(Alignment::Left)
|
||||
.style(Style::default().add_modifier(Modifier::ITALIC))
|
||||
}
|
||||
PreviewContent::NotSupported => build_meta_preview_paragraph(
|
||||
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
||||
),
|
||||
PreviewContent::NotSupported => PreviewWidget::Paragraph(
|
||||
build_meta_preview_paragraph(
|
||||
inner,
|
||||
PREVIEW_NOT_SUPPORTED_MSG,
|
||||
FILL_CHAR_EMPTY,
|
||||
@ -109,7 +135,9 @@ pub fn build_preview_paragraph<'a>(
|
||||
.block(preview_block)
|
||||
.alignment(Alignment::Left)
|
||||
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
||||
PreviewContent::FileTooLarge => build_meta_preview_paragraph(
|
||||
),
|
||||
PreviewContent::FileTooLarge => PreviewWidget::Paragraph(
|
||||
build_meta_preview_paragraph(
|
||||
inner,
|
||||
FILE_TOO_LARGE_MSG,
|
||||
FILL_CHAR_EMPTY,
|
||||
@ -117,13 +145,17 @@ pub fn build_preview_paragraph<'a>(
|
||||
.block(preview_block)
|
||||
.alignment(Alignment::Left)
|
||||
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
||||
PreviewContent::Timeout => {
|
||||
),
|
||||
|
||||
PreviewContent::Timeout => PreviewWidget::Paragraph(
|
||||
build_meta_preview_paragraph(inner, TIMEOUT_MSG, FILL_CHAR_EMPTY)
|
||||
}
|
||||
.block(preview_block)
|
||||
.alignment(Alignment::Left)
|
||||
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
||||
PreviewContent::Empty => Paragraph::new(Text::raw(EMPTY_STRING)),
|
||||
),
|
||||
PreviewContent::Empty => {
|
||||
PreviewWidget::Paragraph(Paragraph::new(Text::raw(EMPTY_STRING)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,7 +304,10 @@ impl Television {
|
||||
&& !matches!(selected_entry.preview_type, PreviewType::None)
|
||||
{
|
||||
// preview content
|
||||
if let Some(preview) = self.previewer.preview(selected_entry) {
|
||||
if let Some(preview) = self
|
||||
.previewer
|
||||
.preview(selected_entry, self.ui_state.layout.preview_window)
|
||||
{
|
||||
// only update if the preview content has changed
|
||||
if self.preview_state.preview.title != preview.title {
|
||||
self.preview_state.update(
|
||||
|
@ -106,6 +106,7 @@ pub fn get_file_size(path: &Path) -> Option<u64> {
|
||||
#[derive(Debug)]
|
||||
pub enum FileType {
|
||||
Text,
|
||||
Image,
|
||||
Other,
|
||||
Unknown,
|
||||
}
|
||||
@ -117,6 +118,9 @@ where
|
||||
fn from(path: P) -> Self {
|
||||
debug!("Getting file type for {:?}", path);
|
||||
let p = path.as_ref();
|
||||
if is_accepted_image_extension(p) {
|
||||
return FileType::Image;
|
||||
}
|
||||
if is_known_text_extension(p) {
|
||||
return FileType::Text;
|
||||
}
|
||||
@ -487,3 +491,28 @@ pub fn get_known_text_file_extensions() -> &'static FxHashSet<&'static str> {
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_accepted_image_extension<P>(path: P) -> bool
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
path.as_ref()
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.is_some_and(|ext| get_known_image_file_extensions().contains(ext))
|
||||
}
|
||||
pub static KNOWN_IMAGE_FILE_EXTENSIONS: OnceLock<FxHashSet<&'static str>> =
|
||||
OnceLock::new();
|
||||
pub fn get_known_image_file_extensions() -> &'static FxHashSet<&'static str> {
|
||||
KNOWN_IMAGE_FILE_EXTENSIONS.get_or_init(|| {
|
||||
[
|
||||
// "avif", requires the avif-native feature, uses the libdav1d C library.
|
||||
// dds, dosen't work for some reason
|
||||
"bmp", "ff", "gif", "hdr", "ico", "jpeg", "jpg", "exr", "png",
|
||||
"pnm", "qoi", "tga", "tif", "webp",
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
185
television/utils/image.rs
Normal file
185
television/utils/image.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use image::imageops::FilterType;
|
||||
use image::{DynamicImage, Pixel, Rgba};
|
||||
use ratatui::buffer::{Buffer, Cell};
|
||||
use ratatui::layout::{Position, Rect};
|
||||
use ratatui::prelude::Color;
|
||||
use ratatui::widgets::Widget;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
static PIXEL_STRING: &str = "▀";
|
||||
const FILTER_TYPE: FilterType = FilterType::Lanczos3;
|
||||
|
||||
// use to reduce the size of the image before storing it
|
||||
const DEFAULT_CACHED_WIDTH: u32 = 50;
|
||||
const DEFAULT_CACHED_HEIGHT: u32 = 100;
|
||||
|
||||
const GRAY: Rgba<u8> = Rgba([242, 242, 242, 255]);
|
||||
const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]);
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub struct ImagePreviewWidget {
|
||||
cells: Vec<Vec<Cell>>,
|
||||
}
|
||||
|
||||
impl Widget for &ImagePreviewWidget {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let height = self.height();
|
||||
let width = self.width();
|
||||
// offset of the left top corner where the image is centered
|
||||
let total_width = usize::from(area.width) + 2 * usize::from(area.x);
|
||||
let x_offset = total_width.saturating_sub(width) / 2 + 1;
|
||||
let total_height = usize::from(area.height) + 2 * usize::from(area.y);
|
||||
let y_offset = total_height.saturating_sub(height) / 2;
|
||||
|
||||
let (area_border_up, area_border_down) =
|
||||
(area.y, area.y + area.height);
|
||||
let (area_border_left, area_border_right) =
|
||||
(area.x, area.x + area.width);
|
||||
for (y, row) in self.cells.iter().enumerate() {
|
||||
let pos_y = u16::try_from(y_offset + y).unwrap_or(u16::MAX);
|
||||
if pos_y >= area_border_up && pos_y < area_border_down {
|
||||
for (x, cell) in row.iter().enumerate() {
|
||||
let pos_x =
|
||||
u16::try_from(x_offset + x).unwrap_or(u16::MAX);
|
||||
if pos_x >= area_border_left && pos_x <= area_border_right
|
||||
{
|
||||
if let Some(buf_cell) =
|
||||
buf.cell_mut(Position::new(pos_x, pos_y))
|
||||
{
|
||||
*buf_cell = cell.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ImagePreviewWidget {
|
||||
pub fn new(cells: Vec<Vec<Cell>>) -> ImagePreviewWidget {
|
||||
ImagePreviewWidget { cells }
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.cells.len()
|
||||
}
|
||||
pub fn width(&self) -> usize {
|
||||
if self.height() > 0 {
|
||||
self.cells[0].len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_dynamic_image(
|
||||
dynamic_image: DynamicImage,
|
||||
dimension: Option<(u32, u32)>,
|
||||
) -> Self {
|
||||
let (window_width, window_height) =
|
||||
dimension.unwrap_or((DEFAULT_CACHED_WIDTH, DEFAULT_CACHED_HEIGHT));
|
||||
let (max_width, max_height) = (window_width, window_height * 2 - 2); // -2 to have some space with the title
|
||||
|
||||
// first quick resize
|
||||
let big_resized_image = if dynamic_image.width() > max_width * 4
|
||||
|| dynamic_image.height() > max_height * 4
|
||||
{
|
||||
dynamic_image.resize(
|
||||
max_width * 4,
|
||||
max_height * 4,
|
||||
FilterType::Nearest,
|
||||
)
|
||||
} else {
|
||||
dynamic_image
|
||||
};
|
||||
|
||||
// this time resize with the filter
|
||||
let resized_image = if big_resized_image.width() > max_width
|
||||
|| big_resized_image.height() > max_height
|
||||
{
|
||||
big_resized_image.resize(max_width, max_height, FILTER_TYPE)
|
||||
} else {
|
||||
big_resized_image
|
||||
};
|
||||
|
||||
let cells = Self::cells_from_dynamic_image(resized_image);
|
||||
ImagePreviewWidget::new(cells)
|
||||
}
|
||||
|
||||
fn cells_from_dynamic_image(image: DynamicImage) -> Vec<Vec<Cell>> {
|
||||
let image_rgba = image.into_rgba8();
|
||||
|
||||
//creation of the grid of cell
|
||||
image_rgba
|
||||
// iter over pair of rows
|
||||
.rows()
|
||||
.step_by(2)
|
||||
.zip(image_rgba.rows().skip(1).step_by(2))
|
||||
.enumerate()
|
||||
.map(|(double_row_y, (row_1, row_2))| {
|
||||
// create rows of cells
|
||||
row_1
|
||||
.into_iter()
|
||||
.zip(row_2)
|
||||
.enumerate()
|
||||
.map(|(x, (color_up, color_down))| {
|
||||
let position = (x, double_row_y);
|
||||
DoublePixel::new(*color_up, *color_down)
|
||||
.add_grid_background(position)
|
||||
.into_cell()
|
||||
})
|
||||
.collect::<Vec<Cell>>()
|
||||
})
|
||||
.collect::<Vec<Vec<Cell>>>()
|
||||
}
|
||||
}
|
||||
|
||||
// util to convert Rgba into ratatui's Cell
|
||||
struct DoublePixel {
|
||||
color_up: Rgba<u8>,
|
||||
color_down: Rgba<u8>,
|
||||
}
|
||||
impl DoublePixel {
|
||||
pub fn new(color_up: Rgba<u8>, color_down: Rgba<u8>) -> Self {
|
||||
Self {
|
||||
color_up,
|
||||
color_down,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_grid_background(mut self, position: (usize, usize)) -> Self {
|
||||
let color_up = self.color_up.0;
|
||||
let color_down = self.color_down.0;
|
||||
self.color_up = Self::blend_with_background(color_up, position, 0);
|
||||
self.color_down = Self::blend_with_background(color_down, position, 1);
|
||||
self
|
||||
}
|
||||
|
||||
fn blend_with_background(
|
||||
color: impl Into<Rgba<u8>>,
|
||||
position: (usize, usize),
|
||||
offset: usize,
|
||||
) -> Rgba<u8> {
|
||||
let color = color.into();
|
||||
if color[3] == 255 {
|
||||
color
|
||||
} else {
|
||||
let is_white = (position.0 + position.1 * 2 + offset) % 2 == 0;
|
||||
let mut base = if is_white { WHITE } else { GRAY };
|
||||
base.blend(&color);
|
||||
base
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_cell(self) -> Cell {
|
||||
let mut cell = Cell::new(PIXEL_STRING);
|
||||
cell.set_bg(Self::convert_image_color_to_ratatui_color(
|
||||
self.color_down,
|
||||
))
|
||||
.set_fg(Self::convert_image_color_to_ratatui_color(self.color_up));
|
||||
cell
|
||||
}
|
||||
|
||||
fn convert_image_color_to_ratatui_color(color: Rgba<u8>) -> Color {
|
||||
Color::Rgb(color[0], color[1], color[2])
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ pub mod clipboard;
|
||||
pub mod command;
|
||||
pub mod files;
|
||||
pub mod hashmaps;
|
||||
pub mod image;
|
||||
pub mod indices;
|
||||
pub mod input;
|
||||
pub mod metadata;
|
||||
|
Loading…
x
Reference in New Issue
Block a user