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 bytes = BASE64_STANDARD.decode(base64_encoded_data.trim())?;
20
21 let format = image::guess_format(&bytes)?;
22 let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
23
24 // Convert from RGBA to BGRA.
25 for pixel in data.chunks_exact_mut(4) {
26 pixel.swap(0, 2);
27 }
28
29 let height = data.height();
30 let width = data.width();
31
32 let gpui_image_data = RenderImage::new(vec![image::Frame::new(data)]);
33
34 let format = match format {
35 image::ImageFormat::Png => ImageFormat::Png,
36 image::ImageFormat::Jpeg => ImageFormat::Jpeg,
37 image::ImageFormat::Gif => ImageFormat::Gif,
38 image::ImageFormat::WebP => ImageFormat::Webp,
39 image::ImageFormat::Tiff => ImageFormat::Tiff,
40 image::ImageFormat::Bmp => ImageFormat::Bmp,
41 _ => {
42 return Err(anyhow::anyhow!("unsupported image format"));
43 }
44 };
45
46 // Convert back to a GPUI image for use with the clipboard
47 let clipboard_image = Arc::new(Image {
48 format,
49 bytes,
50 id: gpui_image_data.id.0 as u64,
51 });
52
53 Ok(ImageView {
54 clipboard_image,
55 height,
56 width,
57 image: Arc::new(gpui_image_data),
58 })
59 }
60}
61
62impl Render for ImageView {
63 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
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().h(Pixels(height)).w(Pixels(width)).child(img(image))
77 }
78}
79
80impl OutputContent for ImageView {
81 fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
82 Some(ClipboardItem::new_image(self.clipboard_image.as_ref()))
83 }
84
85 fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
86 true
87 }
88}