gpui: Fix BGRA conversion for SVG rendering (#52641)

hnakashima and MrSubidubi created

### Description
Fixes swapped red/blue channels when rendering SVG images.

#### Describe the bug

When rendering a full-color SVG into an Image object using
Image::from_bytes(ImageFormat::Svg, ...) on macOS, the resulting bitmap
has its Red and Blue channels swapped. For example, a color specified as
#38BDF8 (Light Blue) in the SVG source appears as yellowish in the
rendered GPUI view.

#### Steps to reproduce
1. Create a GPUI application. 
1. Generate or load an SVG string containing a specific color, for
example:
```xml
   <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
     <rect width="100" height="100" fill="#38BDF8"/>
   </svg>
```
3. Load this SVG into an Image object:
```rust
   let image = Arc::new(Image::from_bytes(
       ImageFormat::Svg,
       svg_string.into_bytes(),
   ));
```
4. Display this image in a view using an img() element. 

#### Expected behavior
The rectangle should be rendered in **Light Blue (#38BDF8)**. 

#### Actual behavior
The rectangle is rendered in **Yellowish Color (#F8BD38)**.

### Self-Review Checklist:

- [X] I've reviewed my own diff for quality, security, and reliability
- [X] Unsafe blocks (if any) have justifying comments
- [X] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [X] Tests cover the new/changed behavior
- [X] Performance impact has been considered and is acceptable

### Closes #ISSUE

### Release Notes:

- Fixed swapped color channels when pasting SVG images from the
clipboard.

---------

Co-authored-by: MrSubidubi <finn@zed.dev>

Change summary

crates/gpui/src/elements/img.rs            |  2 
crates/gpui/src/platform.rs                | 26 +++++++++++++++++++++++
crates/gpui/src/svg_renderer.rs            |  7 +----
crates/markdown/src/mermaid.rs             |  3 -
crates/svg_preview/src/svg_preview_view.rs |  2 
5 files changed, 30 insertions(+), 10 deletions(-)

Detailed changes

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

@@ -697,7 +697,7 @@ impl Asset for ImageAssetLoader {
                 Ok(Arc::new(RenderImage::new(data)))
             } else {
                 svg_renderer
-                    .render_single_frame(&bytes, 1.0, true)
+                    .render_single_frame(&bytes, 1.0)
                     .map_err(Into::into)
             }
         }

crates/gpui/src/platform.rs 🔗

@@ -2108,7 +2108,7 @@ impl Image {
             ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
             ImageFormat::Svg => {
                 return svg_renderer
-                    .render_single_frame(&self.bytes, 1.0, false)
+                    .render_single_frame(&self.bytes, 1.0)
                     .map_err(Into::into);
             }
         };
@@ -2190,6 +2190,30 @@ impl From<String> for ClipboardString {
     }
 }
 
+#[cfg(test)]
+mod image_tests {
+    use super::*;
+    use std::sync::Arc;
+
+    #[test]
+    fn test_svg_image_to_image_data_converts_to_bgra() {
+        let image = Image::from_bytes(
+            ImageFormat::Svg,
+            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
+<rect width="1" height="1" fill="#38BDF8"/>
+</svg>"##
+                .to_vec(),
+        );
+
+        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
+        let bytes = render_image.as_bytes(0).unwrap();
+
+        for pixel in bytes.chunks_exact(4) {
+            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
+        }
+    }
+}
+
 #[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
 mod tests {
     use super::*;

crates/gpui/src/svg_renderer.rs 🔗

@@ -150,7 +150,6 @@ impl SvgRenderer {
         &self,
         bytes: &[u8],
         scale_factor: f32,
-        to_brga: bool,
     ) -> Result<Arc<RenderImage>, usvg::Error> {
         self.render_pixmap(
             bytes,
@@ -161,10 +160,8 @@ impl SvgRenderer {
                 image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
                     .unwrap();
 
-            if to_brga {
-                for pixel in buffer.chunks_exact_mut(4) {
-                    swap_rgba_pa_to_bgra(pixel);
-                }
+            for pixel in buffer.chunks_exact_mut(4) {
+                swap_rgba_pa_to_bgra(pixel);
             }
 
             let mut image = RenderImage::new(SmallVec::from_const([Frame::new(buffer)]));

crates/markdown/src/mermaid.rs 🔗

@@ -106,7 +106,7 @@ impl CachedMermaidDiagram {
                     let svg_string = mermaid_rs_renderer::render(&contents.contents)?;
                     let scale = contents.scale as f32 / 100.0;
                     svg_renderer
-                        .render_single_frame(svg_string.as_bytes(), scale, true)
+                        .render_single_frame(svg_string.as_bytes(), scale)
                         .map_err(|error| anyhow::anyhow!("{error}"))
                 })
                 .await;
@@ -325,7 +325,6 @@ mod tests {
                 .render_single_frame(
                     br#"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>"#,
                     1.0,
-                    true,
                 )
                 .unwrap()
         })

crates/svg_preview/src/svg_preview_view.rs 🔗

@@ -110,7 +110,7 @@ impl SvgPreviewView {
         let renderer = cx.svg_renderer();
         let content = buffer.read(cx).snapshot();
         let background_task = cx.background_spawn(async move {
-            renderer.render_single_frame(content.text().as_bytes(), SCALE_FACTOR, true)
+            renderer.render_single_frame(content.text().as_bytes(), SCALE_FACTOR)
         });
 
         self._refresh = cx.spawn_in(window, async move |this, cx| {