Add support for Netpbm image previews (#54256)

VinΓ­cius Dutra created

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):
<img width="1917" height="1012" alt="pnm_example"
src="https://github.com/user-attachments/assets/0056133f-908c-4c91-ba9d-53aef0657b05"
/>



Release Notes:
  - Added support for PNM image previews (`.pbm`, `.ppm`, `.pgm`).

Change summary

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(-)

Detailed changes

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()

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)))

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,

crates/gpui_macos/src/pasteboard.rs πŸ”—

@@ -272,6 +272,7 @@ impl From<ImageFormat> 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
     }

crates/project/src/image_store.rs πŸ”—

@@ -902,6 +902,7 @@ fn create_gpui_image(content: Vec<u8>) -> anyhow::Result<Arc<gpui::Image>> {
             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,