Fix crash and enable GIF animation in markdown preview (#53459)

Closes #53426

Fixes a crash when previewing markdown with GIFs, and enables GIF
animation in the preview panel.

Two issues: a partially-decoded GIF could crash the preview, and GIFs in
markdown previews never animated.

The fix for GIFs with empty comment extensions (`21 fe 00`) was
implemented upstream in the `image-gif` crate (image-rs/image-gif#228)
and released as `gif 0.14.2`. This PR bumps the dependency so those GIFs
now render correctly in the markdown preview without any further changes
to Zed itself.

## Screenshot
Left=VS Code
Right=Zed


https://github.com/user-attachments/assets/7950abbc-1a79-4f01-a425-9595aa688325

Release Notes:

- Fixed a crash in certain scenarios when opening Markdown Preview with
GIFs.
- Added GIF animation support for Markdown Preview.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
David Alecrim 2026-04-16 04:44:00 -03:00 committed by GitHub
parent 920ead54b7
commit 0ddc7d0666
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 240 additions and 108 deletions

223
Cargo.lock generated
View file

@ -500,6 +500,15 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "aligned"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
dependencies = [
"as-slice",
]
[[package]] [[package]]
name = "aligned-vec" name = "aligned-vec"
version = "0.6.4" version = "0.6.4"
@ -734,6 +743,15 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
[[package]]
name = "as-slice"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
dependencies = [
"stable_deref_trait",
]
[[package]] [[package]]
name = "ascii" name = "ascii"
version = "1.1.0" version = "1.1.0"
@ -1270,6 +1288,26 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "av-scenechange"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
dependencies = [
"aligned",
"anyhow",
"arg_enum_proc_macro",
"arrayvec",
"log",
"num-rational",
"num-traits",
"pastey",
"rayon",
"thiserror 2.0.17",
"v_frame",
"y4m",
]
[[package]] [[package]]
name = "av1-grain" name = "av1-grain"
version = "0.2.4" version = "0.2.4"
@ -1930,7 +1968,7 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.10.5",
"log", "log",
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
@ -1950,7 +1988,7 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.10.5",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
@ -2012,9 +2050,12 @@ dependencies = [
[[package]] [[package]]
name = "bitstream-io" name = "bitstream-io"
version = "2.6.0" version = "4.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757"
dependencies = [
"core2",
]
[[package]] [[package]]
name = "bitvec" name = "bitvec"
@ -2199,9 +2240,9 @@ dependencies = [
[[package]] [[package]]
name = "built" name = "built"
version = "0.7.7" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
@ -2666,16 +2707,6 @@ dependencies = [
"nom 7.1.3", "nom 7.1.3",
] ]
[[package]]
name = "cfg-expr"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
dependencies = [
"smallvec",
"target-lexicon 0.12.16",
]
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.20.6" version = "0.20.6"
@ -2683,7 +2714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a"
dependencies = [ dependencies = [
"smallvec", "smallvec",
"target-lexicon 0.13.3", "target-lexicon",
] ]
[[package]] [[package]]
@ -3765,6 +3796,15 @@ dependencies = [
"metal", "metal",
] ]
[[package]]
name = "core2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "core_maths" name = "core_maths"
version = "0.1.1" version = "0.1.1"
@ -3943,7 +3983,7 @@ dependencies = [
"serde_derive", "serde_derive",
"sha2", "sha2",
"smallvec", "smallvec",
"target-lexicon 0.13.3", "target-lexicon",
"wasmtime-internal-math", "wasmtime-internal-math",
] ]
@ -3995,7 +4035,7 @@ dependencies = [
"cranelift-codegen", "cranelift-codegen",
"log", "log",
"smallvec", "smallvec",
"target-lexicon 0.13.3", "target-lexicon",
] ]
[[package]] [[package]]
@ -4012,7 +4052,7 @@ checksum = "976a3d85f197a56ae34ee4d5a5e469855ac52804a09a513d0562d425da0ff56e"
dependencies = [ dependencies = [
"cranelift-codegen", "cranelift-codegen",
"libc", "libc",
"target-lexicon 0.13.3", "target-lexicon",
] ]
[[package]] [[package]]
@ -5867,9 +5907,9 @@ dependencies = [
[[package]] [[package]]
name = "exr" name = "exr"
version = "1.73.0" version = "1.74.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
dependencies = [ dependencies = [
"bit_field", "bit_field",
"half", "half",
@ -7103,6 +7143,16 @@ dependencies = [
"weezl", "weezl",
] ]
[[package]]
name = "gif"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [
"color_quant",
"weezl",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.32.3" version = "0.32.3"
@ -7123,7 +7173,7 @@ dependencies = [
"glib-sys", "glib-sys",
"gobject-sys", "gobject-sys",
"libc", "libc",
"system-deps 7.0.7", "system-deps",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@ -7345,7 +7395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c"
dependencies = [ dependencies = [
"libc", "libc",
"system-deps 7.0.7", "system-deps",
] ]
[[package]] [[package]]
@ -7430,7 +7480,7 @@ checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
"system-deps 7.0.7", "system-deps",
] ]
[[package]] [[package]]
@ -8611,15 +8661,15 @@ dependencies = [
[[package]] [[package]]
name = "image" name = "image"
version = "0.25.8" version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder-lite", "byteorder-lite",
"color_quant", "color_quant",
"exr", "exr",
"gif", "gif 0.14.2",
"image-webp", "image-webp",
"moxcms", "moxcms",
"num-traits", "num-traits",
@ -8629,8 +8679,8 @@ dependencies = [
"rayon", "rayon",
"rgb", "rgb",
"tiff", "tiff",
"zune-core", "zune-core 0.5.1",
"zune-jpeg", "zune-jpeg 0.5.15",
] ]
[[package]] [[package]]
@ -8697,7 +8747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.16.1", "hashbrown 0.15.5",
"serde", "serde",
"serde_core", "serde_core",
] ]
@ -8958,15 +9008,6 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.14.0" version = "0.14.0"
@ -10708,9 +10749,9 @@ dependencies = [
[[package]] [[package]]
name = "moxcms" name = "moxcms"
version = "0.7.7" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c588e11a3082784af229e23e8e4ecf5bcc6fbe4f69101e0421ce8d79da7f0b40" checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"pxfm", "pxfm",
@ -12033,6 +12074,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pastey"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
[[package]] [[package]]
name = "pathdiff" name = "pathdiff"
version = "0.2.3" version = "0.2.3"
@ -13544,8 +13591,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [ dependencies = [
"bytes 1.11.1", "bytes 1.11.1",
"heck 0.5.0", "heck 0.4.1",
"itertools 0.12.1", "itertools 0.10.5",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
@ -13578,7 +13625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.12.1", "itertools 0.10.5",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn 2.0.117",
@ -14031,19 +14078,21 @@ checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68"
[[package]] [[package]]
name = "rav1e" name = "rav1e"
version = "0.7.1" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
dependencies = [ dependencies = [
"aligned-vec",
"arbitrary", "arbitrary",
"arg_enum_proc_macro", "arg_enum_proc_macro",
"arrayvec", "arrayvec",
"av-scenechange",
"av1-grain", "av1-grain",
"bitstream-io", "bitstream-io",
"built", "built",
"cfg-if", "cfg-if",
"interpolate_name", "interpolate_name",
"itertools 0.12.1", "itertools 0.14.0",
"libc", "libc",
"libfuzzer-sys", "libfuzzer-sys",
"log", "log",
@ -14052,23 +14101,21 @@ dependencies = [
"noop_proc_macro", "noop_proc_macro",
"num-derive", "num-derive",
"num-traits", "num-traits",
"once_cell",
"paste", "paste",
"profiling", "profiling",
"rand 0.8.5", "rand 0.9.3",
"rand_chacha 0.3.1", "rand_chacha 0.9.0",
"simd_helpers", "simd_helpers",
"system-deps 6.2.2", "thiserror 2.0.17",
"thiserror 1.0.69",
"v_frame", "v_frame",
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]] [[package]]
name = "ravif" name = "ravif"
version = "0.11.20" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
dependencies = [ dependencies = [
"avif-serialize", "avif-serialize",
"imgref", "imgref",
@ -14656,7 +14703,7 @@ version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43"
dependencies = [ dependencies = [
"gif", "gif 0.13.3",
"image-webp", "image-webp",
"log", "log",
"pico-args", "pico-args",
@ -14664,7 +14711,7 @@ dependencies = [
"svgtypes", "svgtypes",
"tiny-skia", "tiny-skia",
"usvg", "usvg",
"zune-jpeg", "zune-jpeg 0.4.21",
] ]
[[package]] [[package]]
@ -16278,7 +16325,7 @@ version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.4.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn 2.0.117",
@ -17278,26 +17325,13 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "system-deps"
version = "6.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
dependencies = [
"cfg-expr 0.15.8",
"heck 0.5.0",
"pkg-config",
"toml 0.8.23",
"version-compare",
]
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "7.0.7" version = "7.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f"
dependencies = [ dependencies = [
"cfg-expr 0.20.6", "cfg-expr",
"heck 0.5.0", "heck 0.5.0",
"pkg-config", "pkg-config",
"toml 0.9.8", "toml 0.9.8",
@ -17402,12 +17436,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.13.3" version = "0.13.3"
@ -17760,16 +17788,16 @@ dependencies = [
[[package]] [[package]]
name = "tiff" name = "tiff"
version = "0.10.3" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
dependencies = [ dependencies = [
"fax", "fax",
"flate2", "flate2",
"half", "half",
"quick-error 2.0.1", "quick-error 2.0.1",
"weezl", "weezl",
"zune-jpeg", "zune-jpeg 0.5.15",
] ]
[[package]] [[package]]
@ -19730,7 +19758,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"smallvec", "smallvec",
"target-lexicon 0.13.3", "target-lexicon",
"wasmparser 0.236.1", "wasmparser 0.236.1",
"wasmtime-environ", "wasmtime-environ",
"wasmtime-internal-asm-macros", "wasmtime-internal-asm-macros",
@ -19781,7 +19809,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"smallvec", "smallvec",
"target-lexicon 0.13.3", "target-lexicon",
"wasm-encoder 0.236.1", "wasm-encoder 0.236.1",
"wasmparser 0.236.1", "wasmparser 0.236.1",
"wasmprinter", "wasmprinter",
@ -19847,7 +19875,7 @@ dependencies = [
"object", "object",
"pulley-interpreter", "pulley-interpreter",
"smallvec", "smallvec",
"target-lexicon 0.13.3", "target-lexicon",
"thiserror 2.0.17", "thiserror 2.0.17",
"wasmparser 0.236.1", "wasmparser 0.236.1",
"wasmtime-environ", "wasmtime-environ",
@ -19942,7 +19970,7 @@ dependencies = [
"cranelift-codegen", "cranelift-codegen",
"gimli", "gimli",
"object", "object",
"target-lexicon 0.13.3", "target-lexicon",
"wasmparser 0.236.1", "wasmparser 0.236.1",
"wasmtime-environ", "wasmtime-environ",
"wasmtime-internal-cranelift", "wasmtime-internal-cranelift",
@ -20544,7 +20572,7 @@ dependencies = [
"gimli", "gimli",
"regalloc2", "regalloc2",
"smallvec", "smallvec",
"target-lexicon 0.13.3", "target-lexicon",
"thiserror 2.0.17", "thiserror 2.0.17",
"wasmparser 0.236.1", "wasmparser 0.236.1",
"wasmtime-environ", "wasmtime-environ",
@ -21959,6 +21987,12 @@ dependencies = [
"toml_edit 0.22.27", "toml_edit 0.22.27",
] ]
[[package]]
name = "y4m"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
[[package]] [[package]]
name = "yaml-rust2" name = "yaml-rust2"
version = "0.8.1" version = "0.8.1"
@ -22743,6 +22777,12 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
[[package]] [[package]]
name = "zune-inflate" name = "zune-inflate"
version = "0.2.54" version = "0.2.54"
@ -22758,7 +22798,16 @@ version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
dependencies = [ dependencies = [
"zune-core", "zune-core 0.4.12",
]
[[package]]
name = "zune-jpeg"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core 0.5.1",
] ]
[[package]] [[package]]

View file

@ -77,8 +77,13 @@ impl RenderImage {
/// Get the size of this image, in pixels. /// Get the size of this image, in pixels.
pub fn size(&self, frame_index: usize) -> Size<DevicePixels> { pub fn size(&self, frame_index: usize) -> Size<DevicePixels> {
let (width, height) = self.data[frame_index].buffer().dimensions(); self.data
size(width.into(), height.into()) .get(frame_index)
.map(|frame| {
let (width, height) = frame.buffer().dimensions();
size(width.into(), height.into())
})
.unwrap_or_default()
} }
/// Get the size of this image, in pixels for display, adjusted for the scale factor. /// Get the size of this image, in pixels for display, adjusted for the scale factor.
@ -89,7 +94,10 @@ impl RenderImage {
/// Get the delay of this frame from the previous /// Get the delay of this frame from the previous
pub fn delay(&self, frame_index: usize) -> Delay { pub fn delay(&self, frame_index: usize) -> Delay {
self.data[frame_index].delay() self.data
.get(frame_index)
.map(|frame| frame.delay())
.unwrap_or(Delay::from_numer_denom_ms(100, 1))
} }
/// Get the number of frames for this image. /// Get the number of frames for this image.
@ -102,7 +110,24 @@ impl fmt::Debug for RenderImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ImageData") f.debug_struct("ImageData")
.field("id", &self.id) .field("id", &self.id)
.field("size", &self.size(0)) .field("size", &self.data.first().map(|f| f.buffer().dimensions()))
.finish() .finish()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use smallvec::SmallVec;
#[test]
fn empty_render_image_does_not_panic() {
let image = RenderImage::new(SmallVec::new());
assert_eq!(image.frame_count(), 0);
assert_eq!(image.size(0), Size::default());
assert_eq!(image.as_bytes(0), None);
assert_eq!(image.render_size(0), Size::default());
assert_eq!(image.delay(0), Delay::from_numer_denom_ms(100, 1));
let _ = format!("{image:?}");
}
}

View file

@ -473,6 +473,9 @@ impl Element for Img {
window, window,
cx, cx,
) { ) {
if data.frame_count() == 0 {
return;
}
let new_bounds = self let new_bounds = self
.style .style
.object_fit .object_fit
@ -650,12 +653,26 @@ impl Asset for ImageAssetLoader {
let mut frames = SmallVec::new(); let mut frames = SmallVec::new();
for frame in decoder.into_frames() { for frame in decoder.into_frames() {
let mut frame = frame?; match frame {
// Convert from RGBA to BGRA. Ok(mut frame) => {
for pixel in frame.buffer_mut().chunks_exact_mut(4) { // Convert from RGBA to BGRA.
pixel.swap(0, 2); for pixel in frame.buffer_mut().chunks_exact_mut(4) {
pixel.swap(0, 2);
}
frames.push(frame);
}
Err(err) => {
log::debug!(
"Skipping GIF frame in {source:?} due to decode error: {err}"
);
}
} }
frames.push(frame); }
if frames.is_empty() {
return Err(ImageCacheError::Other(Arc::new(anyhow::anyhow!(
"GIF could not be decoded: all frames failed ({source:?})"
))));
} }
frames frames
@ -668,12 +685,26 @@ impl Asset for ImageAssetLoader {
let mut frames = SmallVec::new(); let mut frames = SmallVec::new();
for frame in decoder.into_frames() { for frame in decoder.into_frames() {
let mut frame = frame?; match frame {
// Convert from RGBA to BGRA. Ok(mut frame) => {
for pixel in frame.buffer_mut().chunks_exact_mut(4) { // Convert from RGBA to BGRA.
pixel.swap(0, 2); for pixel in frame.buffer_mut().chunks_exact_mut(4) {
pixel.swap(0, 2);
}
frames.push(frame);
}
Err(err) => {
log::debug!(
"Skipping WebP frame in {source:?} due to decode error: {err}"
);
}
} }
frames.push(frame); }
if frames.is_empty() {
return Err(ImageCacheError::Other(Arc::new(anyhow::anyhow!(
"WebP could not be decoded: all frames failed ({source:?})"
))));
} }
frames frames
@ -764,3 +795,18 @@ impl From<image::ImageError> for ImageCacheError {
Self::Image(Arc::new(value)) Self::Image(Arc::new(value))
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::{TestAppContext, point, px, size};
#[gpui::test]
fn zero_frame_image_does_not_panic_on_paint(cx: &mut TestAppContext) {
let cx = cx.add_empty_window();
let image = Arc::new(RenderImage::new(SmallVec::new()));
cx.draw(point(px(0.), px(0.)), size(px(100.), px(100.)), |_, _| {
img(ImageSource::Render(image)).into_any_element()
});
}
}

View file

@ -2100,12 +2100,22 @@ impl Image {
let mut frames = SmallVec::new(); let mut frames = SmallVec::new();
for frame in decoder.into_frames() { for frame in decoder.into_frames() {
let mut frame = frame?; match frame {
// Convert from RGBA to BGRA. Ok(mut frame) => {
for pixel in frame.buffer_mut().chunks_exact_mut(4) { // Convert from RGBA to BGRA.
pixel.swap(0, 2); for pixel in frame.buffer_mut().chunks_exact_mut(4) {
pixel.swap(0, 2);
}
frames.push(frame);
}
Err(err) => {
log::debug!("Skipping GIF frame due to decode error: {err}");
}
} }
frames.push(frame); }
if frames.is_empty() {
anyhow::bail!("GIF could not be decoded: all frames failed");
} }
frames frames

View file

@ -1067,12 +1067,12 @@ impl MarkdownElement {
image_container.child( image_container.child(
img(source) img(source)
.id(("markdown-image", range.start))
.max_w_full() .max_w_full()
.when_some(height, |this, height| this.h(height)) .when_some(height, |this, height| this.h(height))
.when_some(width, |this, width| this.w(width)), .when_some(width, |this, width| this.w(width)),
) )
}); });
let _ = range;
} }
fn push_markdown_paragraph( fn push_markdown_paragraph(

View file

@ -94,6 +94,8 @@ extend-ignore-re = [
# "noet" is a vim variable (ideally to ignore locally) # "noet" is a vim variable (ideally to ignore locally)
"noet", "noet",
# Yarn Plug'n'Play # Yarn Plug'n'Play
"PnP" "PnP",
# `image` crate method: Delay::from_numer_denom_ms
"numer"
] ]
check-filename = true check-filename = true