image.rs

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