Fix auto size rendering of SVG images in Markdown (#36663)

Jason Lee created

Release Notes:

- Fixed auto size rendering of SVG images in Markdown.

## Before

<img width="836" height="844" alt="image"
src="https://github.com/user-attachments/assets/0782e17e-620f-4c29-a5bc-a2ffe877d220"
/>
<img width="691" height="678" alt="image"
src="https://github.com/user-attachments/assets/dbe2dd5f-fd5b-48f9-bd09-0ee35e116aec"
/>


## After

<img width="873" height="1015" alt="image"
src="https://github.com/user-attachments/assets/59cbb69f-6a81-43cb-989f-3bcea873d81e"
/>
<img width="647" height="598" alt="image"
src="https://github.com/user-attachments/assets/11b67d8e-2b6c-4245-ad13-d4616fdabf22"
/>

For GPUI example

```
cargo run -p gpui --example image
```

<img width="1212" height="740" alt="SCR-20250821-ojoy"
src="https://github.com/user-attachments/assets/62bb2847-c533-4c4d-b5f7-c9764796262a"
/>

Change summary

crates/gpui/examples/image/image.rs | 88 ++++++++++++++++--------------
crates/gpui/src/assets.rs           | 11 +++
crates/gpui/src/elements/img.rs     | 23 +++----
3 files changed, 68 insertions(+), 54 deletions(-)

Detailed changes

crates/gpui/examples/image/image.rs 🔗

@@ -75,65 +75,71 @@ impl Render for ImageShowcase {
     fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
         div()
             .id("main")
+            .bg(gpui::white())
             .overflow_y_scroll()
             .p_5()
             .size_full()
-            .flex()
-            .flex_col()
-            .justify_center()
-            .items_center()
-            .gap_8()
-            .bg(rgb(0xffffff))
             .child(
                 div()
                     .flex()
-                    .flex_row()
+                    .flex_col()
                     .justify_center()
                     .items_center()
                     .gap_8()
-                    .child(ImageContainer::new(
-                        "Image loaded from a local file",
-                        self.local_resource.clone(),
-                    ))
-                    .child(ImageContainer::new(
-                        "Image loaded from a remote resource",
-                        self.remote_resource.clone(),
+                    .child(img(
+                        "https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg",
                     ))
-                    .child(ImageContainer::new(
-                        "Image loaded from an asset",
-                        self.asset_resource.clone(),
-                    )),
-            )
-            .child(
-                div()
-                    .flex()
-                    .flex_row()
-                    .gap_8()
                     .child(
                         div()
-                            .flex_col()
-                            .child("Auto Width")
-                            .child(img("https://picsum.photos/800/400").h(px(180.))),
+                            .flex()
+                            .flex_row()
+                            .justify_center()
+                            .items_center()
+                            .gap_8()
+                            .child(ImageContainer::new(
+                                "Image loaded from a local file",
+                                self.local_resource.clone(),
+                            ))
+                            .child(ImageContainer::new(
+                                "Image loaded from a remote resource",
+                                self.remote_resource.clone(),
+                            ))
+                            .child(ImageContainer::new(
+                                "Image loaded from an asset",
+                                self.asset_resource.clone(),
+                            )),
+                    )
+                    .child(
+                        div()
+                            .flex()
+                            .flex_row()
+                            .gap_8()
+                            .child(
+                                div()
+                                    .flex_col()
+                                    .child("Auto Width")
+                                    .child(img("https://picsum.photos/800/400").h(px(180.))),
+                            )
+                            .child(
+                                div()
+                                    .flex_col()
+                                    .child("Auto Height")
+                                    .child(img("https://picsum.photos/800/400").w(px(180.))),
+                            ),
                     )
                     .child(
                         div()
+                            .flex()
                             .flex_col()
-                            .child("Auto Height")
-                            .child(img("https://picsum.photos/800/400").w(px(180.))),
+                            .justify_center()
+                            .items_center()
+                            .w_full()
+                            .border_1()
+                            .border_color(rgb(0xC0C0C0))
+                            .child("image with max width 100%")
+                            .child(img("https://picsum.photos/800/400").max_w_full()),
                     ),
             )
-            .child(
-                div()
-                    .flex()
-                    .flex_col()
-                    .justify_center()
-                    .items_center()
-                    .w_full()
-                    .border_1()
-                    .border_color(rgb(0xC0C0C0))
-                    .child("image with max width 100%")
-                    .child(img("https://picsum.photos/800/400").max_w_full()),
-            )
     }
 }
 

crates/gpui/src/assets.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{DevicePixels, Result, SharedString, Size, size};
+use crate::{DevicePixels, Pixels, Result, SharedString, Size, size};
 use smallvec::SmallVec;
 
 use image::{Delay, Frame};
@@ -42,6 +42,8 @@ pub(crate) struct RenderImageParams {
 pub struct RenderImage {
     /// The ID associated with this image
     pub id: ImageId,
+    /// The scale factor of this image on render.
+    pub(crate) scale_factor: f32,
     data: SmallVec<[Frame; 1]>,
 }
 
@@ -60,6 +62,7 @@ impl RenderImage {
 
         Self {
             id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
+            scale_factor: 1.0,
             data: data.into(),
         }
     }
@@ -77,6 +80,12 @@ impl RenderImage {
         size(width.into(), height.into())
     }
 
+    /// Get the size of this image, in pixels for display, adjusted for the scale factor.
+    pub(crate) fn render_size(&self, frame_index: usize) -> Size<Pixels> {
+        self.size(frame_index)
+            .map(|v| (v.0 as f32 / self.scale_factor).into())
+    }
+
     /// Get the delay of this frame from the previous
     pub fn delay(&self, frame_index: usize) -> Delay {
         self.data[frame_index].delay()

crates/gpui/src/elements/img.rs 🔗

@@ -332,20 +332,18 @@ impl Element for Img {
                                 state.started_loading = None;
                             }
 
-                            let image_size = data.size(frame_index);
-                            style.aspect_ratio =
-                                Some(image_size.width.0 as f32 / image_size.height.0 as f32);
+                            let image_size = data.render_size(frame_index);
+                            style.aspect_ratio = Some(image_size.width / image_size.height);
 
                             if let Length::Auto = style.size.width {
                                 style.size.width = match style.size.height {
                                     Length::Definite(DefiniteLength::Absolute(
                                         AbsoluteLength::Pixels(height),
                                     )) => Length::Definite(
-                                        px(image_size.width.0 as f32 * height.0
-                                            / image_size.height.0 as f32)
-                                        .into(),
+                                        px(image_size.width.0 * height.0 / image_size.height.0)
+                                            .into(),
                                     ),
-                                    _ => Length::Definite(px(image_size.width.0 as f32).into()),
+                                    _ => Length::Definite(image_size.width.into()),
                                 };
                             }
 
@@ -354,11 +352,10 @@ impl Element for Img {
                                     Length::Definite(DefiniteLength::Absolute(
                                         AbsoluteLength::Pixels(width),
                                     )) => Length::Definite(
-                                        px(image_size.height.0 as f32 * width.0
-                                            / image_size.width.0 as f32)
-                                        .into(),
+                                        px(image_size.height.0 * width.0 / image_size.width.0)
+                                            .into(),
                                     ),
-                                    _ => Length::Definite(px(image_size.height.0 as f32).into()),
+                                    _ => Length::Definite(image_size.height.into()),
                                 };
                             }
 
@@ -701,7 +698,9 @@ impl Asset for ImageAssetLoader {
                     swap_rgba_pa_to_bgra(pixel);
                 }
 
-                RenderImage::new(SmallVec::from_elem(Frame::new(buffer), 1))
+                let mut image = RenderImage::new(SmallVec::from_elem(Frame::new(buffer), 1));
+                image.scale_factor = SMOOTH_SVG_SCALE_FACTOR;
+                image
             };
 
             Ok(Arc::new(data))