image.rs

 1use anyhow::Result;
 2use base64::prelude::*;
 3use gpui::{img, ClipboardItem, Image, ImageFormat, Pixels, RenderImage, WindowContext};
 4use std::sync::Arc;
 5use ui::{div, prelude::*, IntoElement, Styled};
 6
 7use crate::outputs::OutputContent;
 8
 9/// ImageView renders an image inline in an editor, adapting to the line height to fit the image.
10pub struct ImageView {
11    clipboard_image: Arc<Image>,
12    height: u32,
13    width: u32,
14    image: Arc<RenderImage>,
15}
16
17impl ImageView {
18    pub fn from(base64_encoded_data: &str) -> Result<Self> {
19        let filtered =
20            base64_encoded_data.replace(&[' ', '\n', '\t', '\r', '\x0b', '\x0c'][..], "");
21        let bytes = BASE64_STANDARD_NO_PAD.decode(filtered)?;
22
23        let format = image::guess_format(&bytes)?;
24
25        let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
26
27        // Convert from RGBA to BGRA.
28        for pixel in data.chunks_exact_mut(4) {
29            pixel.swap(0, 2);
30        }
31
32        let height = data.height();
33        let width = data.width();
34
35        let gpui_image_data = RenderImage::new(vec![image::Frame::new(data)]);
36
37        let format = match format {
38            image::ImageFormat::Png => ImageFormat::Png,
39            image::ImageFormat::Jpeg => ImageFormat::Jpeg,
40            image::ImageFormat::Gif => ImageFormat::Gif,
41            image::ImageFormat::WebP => ImageFormat::Webp,
42            image::ImageFormat::Tiff => ImageFormat::Tiff,
43            image::ImageFormat::Bmp => ImageFormat::Bmp,
44            _ => {
45                return Err(anyhow::anyhow!("unsupported image format"));
46            }
47        };
48
49        // Convert back to a GPUI image for use with the clipboard
50        let clipboard_image = Arc::new(Image {
51            format,
52            bytes,
53            id: gpui_image_data.id.0 as u64,
54        });
55
56        Ok(ImageView {
57            clipboard_image,
58            height,
59            width,
60            image: Arc::new(gpui_image_data),
61        })
62    }
63}
64
65impl Render for ImageView {
66    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
67        let line_height = cx.line_height();
68
69        let (height, width) = if self.height as f32 / line_height.0 == u8::MAX as f32 {
70            let height = u8::MAX as f32 * line_height.0;
71            let width = self.width as f32 * height / self.height as f32;
72            (height, width)
73        } else {
74            (self.height as f32, self.width as f32)
75        };
76
77        let image = self.image.clone();
78
79        div().h(Pixels(height)).w(Pixels(width)).child(img(image))
80    }
81}
82
83impl OutputContent for ImageView {
84    fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
85        Some(ClipboardItem::new_image(self.clipboard_image.as_ref()))
86    }
87
88    fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
89        true
90    }
91}