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