From 4d78f26c27048f121071bf6c9c6d29f59874d18e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Dutra?= Date: Mon, 20 Apr 2026 10:51:13 -0300 Subject: [PATCH] Add support for Netpbm image previews (#54256) This PR adds support for rendering **Netpbm** image formats (`.pbm`, `.ppm`, `.pgm`) within Zed's built-in image viewer. These formats are particularly useful for projects that want minimal external dependencies, a common scenario in academic environments and low-level graphics programming. Since the underlying `image` crate and `GPUI` already provide support for these codecs, this change explicitly exposes the `Pnm` variant within `gpui::ImageFormat` by mapping it to `image::ImageFormat::Pnm`. ## Screenshots/Examples Below is an example of `.pbm`, `.ppm`, and `.pgm` files being rendered correctly in the image preview (images taken from https://filesamples.com): pnm_example Release Notes: - Added support for PNM image previews (`.pbm`, `.ppm`, `.pgm`). --- crates/agent_ui/src/conversation_view/thread_view.rs | 1 + crates/gpui/src/platform.rs | 4 ++++ crates/gpui_linux/src/linux/x11/clipboard.rs | 3 ++- crates/gpui_macos/src/pasteboard.rs | 6 ++++++ crates/project/src/image_store.rs | 1 + 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index 37c99e7bd31d7b6afdd3882f930bb1618d8c5c69..a4ff0f31494aa262d852e735bbc3bbfa0a78ad15 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -7710,6 +7710,7 @@ impl ThreadView { gpui::ImageFormat::Bmp => "BMP", gpui::ImageFormat::Tiff => "TIFF", gpui::ImageFormat::Ico => "ICO", + gpui::ImageFormat::Pnm => "PNM", }; let dimensions = image::ImageReader::new(std::io::Cursor::new(image.bytes())) .with_guessed_format() diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index c67eb8f160f9db95c5150a056a3a9b787790426f..8fea468705eaf7950f41e47064e1962df2db8790 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -1981,6 +1981,8 @@ pub enum ImageFormat { Tiff, /// .ico Ico, + /// Netpbm image formats (.pbm, .ppm, .pgm). + Pnm, } impl ImageFormat { @@ -1995,6 +1997,7 @@ impl ImageFormat { ImageFormat::Bmp => "image/bmp", ImageFormat::Tiff => "image/tiff", ImageFormat::Ico => "image/ico", + ImageFormat::Pnm => "image/x-portable-anymap", } } @@ -2131,6 +2134,7 @@ impl Image { .render_single_frame(&self.bytes, 1.0) .map_err(Into::into); } + ImageFormat::Pnm => frames_for_image(&self.bytes, image::ImageFormat::Pnm)?, }; Ok(Arc::new(RenderImage::new(frames))) diff --git a/crates/gpui_linux/src/linux/x11/clipboard.rs b/crates/gpui_linux/src/linux/x11/clipboard.rs index d2ea58b3f8c2acd0b6fbeba44eaf8b5a2e57531f..cbefea7650b0934f6fc8c46ffbe5802835ee792a 100644 --- a/crates/gpui_linux/src/linux/x11/clipboard.rs +++ b/crates/gpui_linux/src/linux/x11/clipboard.rs @@ -87,7 +87,7 @@ x11rb::atom_manager! { BMP__MIME: ImageFormat::mime_type(ImageFormat::Bmp ).as_bytes(), TIFF_MIME: ImageFormat::mime_type(ImageFormat::Tiff).as_bytes(), ICO__MIME: ImageFormat::mime_type(ImageFormat::Ico ).as_bytes(), - + PNM__MIME: ImageFormat::mime_type(ImageFormat::Pnm ).as_bytes(), // This is just some random name for the property on our window, into which // the clipboard owner writes the data we requested. ARBOARD_CLIPBOARD, @@ -1005,6 +1005,7 @@ impl Clipboard { ImageFormat::Bmp => self.inner.atoms.BMP__MIME, ImageFormat::Tiff => self.inner.atoms.TIFF_MIME, ImageFormat::Ico => self.inner.atoms.ICO__MIME, + ImageFormat::Pnm => self.inner.atoms.PNM__MIME, }; let data = vec![ClipboardData { bytes: image.bytes, diff --git a/crates/gpui_macos/src/pasteboard.rs b/crates/gpui_macos/src/pasteboard.rs index d8b7f5627ddc44bea867132c91216b00729488d9..8362ab8f3b5c0ec76686d933699d5401b3c2c9fb 100644 --- a/crates/gpui_macos/src/pasteboard.rs +++ b/crates/gpui_macos/src/pasteboard.rs @@ -272,6 +272,7 @@ impl From for UTType { ImageFormat::Bmp => Self::bmp(), ImageFormat::Svg => Self::svg(), ImageFormat::Ico => Self::ico(), + ImageFormat::Pnm => Self::pnm(), } } } @@ -320,6 +321,11 @@ impl UTType { Self(unsafe { NSPasteboardTypeTIFF }) // This is a rare case where there's a built-in NSPasteboardType } + pub fn pnm() -> Self { + //https://en.wikipedia.org/w/index.php?title=Netpbm&oldid=1336679433 under Uniform Type Identifier + Self(unsafe { ns_string("public.pbm") }) + } + fn inner(&self) -> *const Object { self.0 } diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 0ba9787d2e4144cb529756b15fc05ff72dab83c8..0b6dcfa0078588a55067f18757ca575b4aad45d2 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -902,6 +902,7 @@ fn create_gpui_image(content: Vec) -> anyhow::Result> { image::ImageFormat::Bmp => gpui::ImageFormat::Bmp, image::ImageFormat::Tiff => gpui::ImageFormat::Tiff, image::ImageFormat::Ico => gpui::ImageFormat::Ico, + image::ImageFormat::Pnm => gpui::ImageFormat::Pnm, format => anyhow::bail!("Image format {format:?} not supported"), }, content,