notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 6a989e388b7609ba71dddbc3b8da5945450d78b4
parent 21c00a41e0b6b62db22327c89cee7c06dbab5057
Author: William Casarin <jb55@jb55.com>
Date:   Mon,  2 Sep 2024 17:56:18 -0700

Merge images support

Thanks to Ken Sedgwick for getting this over the line by generalizing
our images cache!

Ken Sedgwick (2):
      build: updated num_enum to get around build problem
      Extend ImageCache to handle content images

William Casarin (4):
      update image to 0.25
      initial image support
      use slightly better carousel id

Fixes: https://github.com/damus-io/notedeck/issues/249
Fixes: https://github.com/damus-io/notedeck/issues/148

Diffstat:
MCargo.lock | 409+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
MCargo.toml | 2+-
Msrc/images.rs | 89+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/imgcache.rs | 2+-
Msrc/timeline.rs | 2+-
Msrc/ui/note/contents.rs | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/ui/profile/picture.rs | 5+++--
7 files changed, 524 insertions(+), 98 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -77,6 +77,12 @@ dependencies = [ ] [[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.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -117,7 +123,7 @@ dependencies = [ "ndk 0.8.0", "ndk-context", "ndk-sys 0.5.0+25.2.9519653", - "num_enum 0.7.2", + "num_enum 0.7.3", "thiserror", ] @@ -210,6 +216,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] name = "arboard" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -225,6 +237,17 @@ dependencies = [ ] [[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 2.0.68", +] + +[[package]] name = "arrayref" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -286,6 +309,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + +[[package]] name = "backtrace" version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -454,6 +500,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] +name = "bitstream-io" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415f8399438eb5e4b2f73ed3152a3448b98149dda642a957ee704e1daa5cf1d8" + +[[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -506,6 +558,12 @@ dependencies = [ ] [[package]] +name = "built" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" + +[[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -538,6 +596,12 @@ 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.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -605,6 +669,16 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -936,7 +1010,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "image 0.25.1", + "image", "js-sys", "log", "objc2 0.5.2", @@ -1015,7 +1089,7 @@ dependencies = [ "egui", "ehttp 0.5.0", "enum-map", - "image 0.25.1", + "image", "log", "mime_guess2", "resvg", @@ -1799,34 +1873,35 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" dependencies = [ "bytemuck", "byteorder", "color_quant", "exr", "gif", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", ] [[package]] -name = "image" -version = "0.25.1" +name = "image-webp" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "num-traits", - "png", + "byteorder-lite", + "thiserror", ] [[package]] @@ -1836,6 +1911,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" [[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + +[[package]] name = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1869,6 +1950,17 @@ dependencies = [ ] [[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 2.0.68", +] + +[[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1942,9 +2034,6 @@ name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -2006,6 +2095,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] name = "libloading" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2065,6 +2165,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] name = "lz4_flex" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2080,6 +2189,16 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2212,7 +2331,7 @@ dependencies = [ "jni-sys", "log", "ndk-sys 0.5.0+25.2.9519653", - "num_enum 0.7.2", + "num_enum 0.7.3", "raw-window-handle 0.6.2", "thiserror", ] @@ -2248,6 +2367,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe" [[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] name = "nohash-hasher" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2264,6 +2389,12 @@ dependencies = [ ] [[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] name = "nostr" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2326,7 +2457,7 @@ dependencies = [ "enostr", "env_logger 0.10.2", "hex", - "image 0.24.9", + "image", "log", "nostrdb", "poll-promise", @@ -2359,12 +2490,53 @@ dependencies = [ ] [[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 2.0.68", +] + +[[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" @@ -2403,11 +2575,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ - "num_enum_derive 0.7.2", + "num_enum_derive 0.7.3", ] [[package]] @@ -2416,7 +2588,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", @@ -2428,7 +2600,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.68", @@ -2436,11 +2608,11 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.68", @@ -2816,15 +2988,6 @@ dependencies = [ ] [[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2838,6 +3001,19 @@ name = "profiling" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.68", +] [[package]] name = "puffin" @@ -2882,6 +3058,12 @@ dependencies = [ ] [[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.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2977,6 +3159,56 @@ dependencies = [ ] [[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67376f469e7e7840d0040bbf4b9b3334005bb167f814621326e4c7ab8cd6e944" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] name = "raw-window-handle" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3462,6 +3694,15 @@ dependencies = [ ] [[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3517,6 +3758,15 @@ 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 = "simplecss" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3713,6 +3963,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3913,10 +4182,25 @@ dependencies = [ ] [[package]] +name = "toml" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.14", +] + +[[package]] name = "toml_datetime" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -3926,18 +4210,20 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.13", ] [[package]] @@ -4248,6 +4534,17 @@ dependencies = [ ] [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4260,6 +4557,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eab68b56840f69efb0fefbe3ab6661499217ffdc58e2eef7c3f6f69835386322" [[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4984,6 +5287,15 @@ dependencies = [ ] [[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + +[[package]] name = "winreg" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5089,6 +5401,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[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" @@ -5096,3 +5414,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml @@ -21,7 +21,7 @@ egui_tabs = { git = "https://github.com/damus-io/egui-tabs", branch = "egui-0.28 egui_nav = { git = "https://github.com/damus-io/egui-nav", branch = "egui-0.28" } egui_virtual_list = { git = "https://github.com/jb55/hello_egui", branch = "egui-0.28", package = "egui_virtual_list" } reqwest = { version = "0.12.4", default-features = false, features = [ "rustls-tls-native-roots" ] } -image = { version = "0.24", features = ["jpeg", "png", "webp"] } +image = { version = "0.25", features = ["jpeg", "png", "webp"] } log = "0.4.17" poll-promise = { version = "0.3.0", features = ["tokio"] } serde_derive = "1" diff --git a/src/images.rs b/src/images.rs @@ -102,54 +102,72 @@ pub fn round_image(image: &mut ColorImage) { } } -fn process_pfp_bitmap(size: u32, image: &mut image::DynamicImage) -> ColorImage { +fn process_pfp_bitmap(imgtyp: ImageType, image: &mut image::DynamicImage) -> ColorImage { #[cfg(feature = "profiling")] puffin::profile_function!(); - // Crop square - let smaller = image.width().min(image.height()); - - if image.width() > smaller { - let excess = image.width() - smaller; - *image = image.crop_imm(excess / 2, 0, image.width() - excess, image.height()); - } else if image.height() > smaller { - let excess = image.height() - smaller; - *image = image.crop_imm(0, excess / 2, image.width(), image.height() - excess); + match imgtyp { + ImageType::Content(w, h) => { + let image = image.resize(w, h, FilterType::CatmullRom); // DynamicImage + let image_buffer = image.into_rgba8(); // RgbaImage (ImageBuffer) + let color_image = ColorImage::from_rgba_unmultiplied( + [ + image_buffer.width() as usize, + image_buffer.height() as usize, + ], + image_buffer.as_flat_samples().as_slice(), + ); + color_image + } + ImageType::Profile(size) => { + // Crop square + let smaller = image.width().min(image.height()); + + if image.width() > smaller { + let excess = image.width() - smaller; + *image = image.crop_imm(excess / 2, 0, image.width() - excess, image.height()); + } else if image.height() > smaller { + let excess = image.height() - smaller; + *image = image.crop_imm(0, excess / 2, image.width(), image.height() - excess); + } + let image = image.resize(size, size, FilterType::CatmullRom); // DynamicImage + let image_buffer = image.into_rgba8(); // RgbaImage (ImageBuffer) + let mut color_image = ColorImage::from_rgba_unmultiplied( + [ + image_buffer.width() as usize, + image_buffer.height() as usize, + ], + image_buffer.as_flat_samples().as_slice(), + ); + round_image(&mut color_image); + color_image + } } - let image = image.resize(size, size, FilterType::CatmullRom); // DynamicImage - let image_buffer = image.into_rgba8(); // RgbaImage (ImageBuffer) - let mut color_image = ColorImage::from_rgba_unmultiplied( - [ - image_buffer.width() as usize, - image_buffer.height() as usize, - ], - image_buffer.as_flat_samples().as_slice(), - ); - round_image(&mut color_image); - color_image } -fn parse_img_response(response: ehttp::Response, size: u32) -> Result<ColorImage> { +fn parse_img_response(response: ehttp::Response, imgtyp: ImageType) -> Result<ColorImage> { #[cfg(feature = "profiling")] puffin::profile_function!(); let content_type = response.content_type().unwrap_or_default(); + let size_hint = match imgtyp { + ImageType::Profile(size) => SizeHint::Size(size, size), + ImageType::Content(w, h) => SizeHint::Size(w, h), + }; if content_type.starts_with("image/svg") { #[cfg(feature = "profiling")] puffin::profile_scope!("load_svg"); - let mut color_image = egui_extras::image::load_svg_bytes_with_size( - &response.bytes, - Some(SizeHint::Size(size, size)), - )?; + let mut color_image = + egui_extras::image::load_svg_bytes_with_size(&response.bytes, Some(size_hint))?; round_image(&mut color_image); Ok(color_image) } else if content_type.starts_with("image/") { #[cfg(feature = "profiling")] puffin::profile_scope!("load_from_memory"); let mut dyn_image = image::load_from_memory(&response.bytes)?; - Ok(process_pfp_bitmap(size, &mut dyn_image)) + Ok(process_pfp_bitmap(imgtyp, &mut dyn_image)) } else { Err(format!("Expected image, found content-type {:?}", content_type).into()) } @@ -181,11 +199,20 @@ fn fetch_img_from_disk( }) } +/// Controls type-specific handling +#[derive(Debug, Clone, Copy)] +pub enum ImageType { + /// Profile Image (size) + Profile(u32), + /// Content Image (width, height) + Content(u32, u32), +} + pub fn fetch_img( img_cache: &ImageCache, ctx: &egui::Context, url: &str, - size: u32, + imgtyp: ImageType, ) -> Promise<Result<TextureHandle>> { let key = ImageCache::key(url); let path = img_cache.cache_dir.join(key); @@ -193,7 +220,7 @@ pub fn fetch_img( if path.exists() { fetch_img_from_disk(ctx, url, &path) } else { - fetch_img_from_net(&img_cache.cache_dir, ctx, url, size) + fetch_img_from_net(&img_cache.cache_dir, ctx, url, imgtyp) } // TODO: fetch image from local cache @@ -203,7 +230,7 @@ fn fetch_img_from_net( cache_path: &path::Path, ctx: &egui::Context, url: &str, - size: u32, + imgtyp: ImageType, ) -> Promise<Result<TextureHandle>> { let (sender, promise) = Promise::new(); let request = ehttp::Request::get(url); @@ -213,7 +240,7 @@ fn fetch_img_from_net( ehttp::fetch(request, move |response| { let handle = response .map_err(Error::Generic) - .and_then(|resp| parse_img_response(resp, size)) + .and_then(|resp| parse_img_response(resp, imgtyp)) .map(|img| { let texture_handle = ctx.load_texture(&cloned_url, img.clone(), Default::default()); diff --git a/src/imgcache.rs b/src/imgcache.rs @@ -42,7 +42,7 @@ impl ImageCache { data.as_raw(), data.size[0] as u32, data.size[1] as u32, - image::ColorType::Rgba8, + image::ColorType::Rgba8.into(), )?; Ok(()) diff --git a/src/timeline.rs b/src/timeline.rs @@ -202,6 +202,7 @@ impl TimelineTab { let selection = 0i32; let mut list = VirtualList::new(); list.hide_on_resize(None); + list.over_scan(1000.0); let list = Rc::new(RefCell::new(list)); let notes: Vec<NoteRef> = Vec::with_capacity(cap); @@ -344,7 +345,6 @@ impl Timeline { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum MergeKind { FrontInsert, Spliced, diff --git a/src/ui/note/contents.rs b/src/ui/note/contents.rs @@ -1,4 +1,7 @@ +use crate::images::ImageType; +use crate::imgcache::ImageCache; use crate::ui::note::NoteOptions; +use crate::ui::ProfilePic; use crate::{colors, ui, Damus}; use egui::{Color32, Hyperlink, Image, RichText}; use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction}; @@ -111,7 +114,7 @@ fn render_note_contents( puffin::profile_function!(); let selectable = options.has_selectable_text(); - let images: Vec<String> = vec![]; + let mut images: Vec<String> = vec![]; let mut inline_note: Option<(&[u8; 32], &str)> = None; let resp = ui.horizontal_wrapped(|ui| { @@ -156,19 +159,17 @@ fn render_note_contents( } BlockType::Url => { - /* - let url = block.as_str().to_lowercase(); - if url.ends_with("png") || url.ends_with("jpg") { - images.push(url); + let lower_url = block.as_str().to_lowercase(); + if lower_url.ends_with("png") || lower_url.ends_with("jpg") { + images.push(block.as_str().to_string()); } else { - */ - #[cfg(feature = "profiling")] - puffin::profile_scope!("url contents"); - ui.add(Hyperlink::from_label_and_url( - RichText::new(block.as_str()).color(colors::PURPLE), - block.as_str(), - )); - //} + #[cfg(feature = "profiling")] + puffin::profile_scope!("url contents"); + ui.add(Hyperlink::from_label_and_url( + RichText::new(block.as_str()).color(colors::PURPLE), + block.as_str(), + )); + } } BlockType::Text => { @@ -188,15 +189,85 @@ fn render_note_contents( render_note_preview(ui, damus, txn, id, block_str); } - for image in images { - let img_resp = ui.add(Image::new(image.clone())); - img_resp.context_menu(|ui| { - if ui.button("Copy Link").clicked() { - ui.ctx().copy_text(image); - ui.close_menu(); - } - }); + if !images.is_empty() && !damus.textmode { + ui.add_space(2.0); + let carousel_id = egui::Id::new(("carousel", note.key().expect("expected tx note"))); + image_carousel(ui, &mut damus.img_cache, images, carousel_id); + ui.add_space(2.0); } resp } + +fn image_carousel( + ui: &mut egui::Ui, + img_cache: &mut ImageCache, + images: Vec<String>, + carousel_id: egui::Id, +) { + // let's make sure everything is within our area + + let height = 360.0; + let width = ui.available_size().x; + let spinsz = if height > width { width } else { height }; + + ui.add_sized([width, height], |ui: &mut egui::Ui| { + egui::ScrollArea::horizontal() + .id_source(carousel_id) + .show(ui, |ui| { + ui.horizontal(|ui| { + for image in images { + // If the cache is empty, initiate the fetch + let m_cached_promise = img_cache.map().get(&image); + if m_cached_promise.is_none() { + let res = crate::images::fetch_img( + img_cache, + ui.ctx(), + &image, + ImageType::Content(width.round() as u32, height.round() as u32), + ); + img_cache.map_mut().insert(image.to_owned(), res); + } + + // What is the state of the fetch? + match img_cache.map()[&image].ready() { + // Still waiting + None => { + ui.add(egui::Spinner::new().size(spinsz)); + } + // Failed to fetch image! + Some(Err(_err)) => { + // FIXME - use content-specific error instead + let no_pfp = crate::images::fetch_img( + img_cache, + ui.ctx(), + ProfilePic::no_pfp_url(), + ImageType::Profile(128), + ); + img_cache.map_mut().insert(image.to_owned(), no_pfp); + // spin until next pass + ui.add(egui::Spinner::new().size(spinsz)); + } + // Use the previously resolved image + Some(Ok(img)) => { + let img_resp = ui.add( + Image::new(img) + .max_height(height) + .rounding(5.0) + .fit_to_original_size(1.0), + ); + img_resp.context_menu(|ui| { + if ui.button("Copy Link").clicked() { + ui.ctx().copy_text(image); + ui.close_menu(); + } + }); + } + } + } + }) + .response + }) + .inner + }); +} diff --git a/src/ui/profile/picture.rs b/src/ui/profile/picture.rs @@ -1,3 +1,4 @@ +use crate::images::ImageType; use crate::imgcache::ImageCache; use crate::ui::{Preview, PreviewConfig, View}; use egui::{vec2, Sense, TextureHandle}; @@ -72,7 +73,7 @@ fn render_pfp( let m_cached_promise = img_cache.map().get(url); if m_cached_promise.is_none() { - let res = crate::images::fetch_img(img_cache, ui.ctx(), url, img_size); + let res = crate::images::fetch_img(img_cache, ui.ctx(), url, ImageType::Profile(img_size)); img_cache.map_mut().insert(url.to_owned(), res); } @@ -87,7 +88,7 @@ fn render_pfp( img_cache, ui.ctx(), ProfilePic::no_pfp_url(), - img_size, + ImageType::Profile(img_size), ); img_cache.map_mut().insert(url.to_owned(), no_pfp); }