1+ use std:: sync:: Arc ;
2+
13use crate :: stdio:: TerminalOutput ;
24use crate :: ExecutionId ;
3- use gpui:: { AnyElement , FontWeight , Render , View } ;
5+ use anyhow:: { anyhow, Result } ;
6+ use gpui:: { img, AnyElement , FontWeight , ImageData , Render , View } ;
47use runtimelib:: { ExecutionState , JupyterMessageContent , MimeType } ;
58use serde_json:: Value ;
69use ui:: { div, prelude:: * , v_flex, IntoElement , Styled , ViewContext } ;
710
11+ pub struct ImageView {
12+ height : u32 ,
13+ width : u32 ,
14+ image : Arc < ImageData > ,
15+ }
16+
17+ impl ImageView {
18+ fn render ( & self , cx : & ViewContext < ExecutionView > ) -> AnyElement {
19+ let line_height = cx. line_height ( ) ;
20+
21+ let ( height, width) = if self . height as f32 / line_height. 0 == u8:: MAX as f32 {
22+ let height = u8:: MAX as f32 * line_height. 0 ;
23+ let width = self . width as f32 * height / self . height as f32 ;
24+ ( height, width)
25+ } else {
26+ ( self . height as f32 , self . width as f32 )
27+ } ;
28+
29+ let image = self . image . clone ( ) ;
30+
31+ div ( )
32+ . h ( Pixels ( height as f32 ) )
33+ . w ( Pixels ( width as f32 ) )
34+ . child ( img ( image) )
35+ . into_any_element ( )
36+ }
37+ }
38+
39+ impl LineHeight for ImageView {
40+ fn num_lines ( & self , cx : & mut WindowContext ) -> u8 {
41+ let line_height = cx. line_height ( ) ;
42+
43+ let lines = self . height as f32 / line_height. 0 ;
44+
45+ if lines > u8:: MAX as f32 {
46+ return u8:: MAX ;
47+ }
48+ lines as u8
49+ }
50+ }
51+
852pub enum OutputType {
953 Plain ( TerminalOutput ) ,
1054 Media ( ( MimeType , Value ) ) ,
1155 Stream ( TerminalOutput ) ,
56+ Image ( ImageView ) ,
1257 ErrorOutput {
1358 ename : String ,
1459 evalue : String ,
1560 traceback : TerminalOutput ,
1661 } ,
62+ Message ( String ) ,
1763}
1864
1965pub trait LineHeight : Sized {
2066 fn num_lines ( & self , cx : & mut WindowContext ) -> u8 ;
2167}
2268
2369// Priority order goes from highest to lowest (plaintext is the common fallback)
24- const PRIORITY_ORDER : & [ MimeType ] = & [ MimeType :: Markdown , MimeType :: Plain ] ;
70+ const PRIORITY_ORDER : & [ MimeType ] = & [
71+ MimeType :: Png ,
72+ MimeType :: Jpeg ,
73+ MimeType :: Markdown ,
74+ MimeType :: Plain ,
75+ ] ;
2576
2677impl OutputType {
2778 fn render ( & self , cx : & ViewContext < ExecutionView > ) -> Option < AnyElement > {
@@ -32,6 +83,8 @@ impl OutputType {
3283 // Self::Markdown(markdown) => Some(markdown.render(theme)),
3384 Self :: Media ( ( mimetype, value) ) => render_rich ( mimetype, value) ,
3485 Self :: Stream ( stdio) => Some ( stdio. render ( cx) ) ,
86+ Self :: Image ( image) => Some ( image. render ( cx) ) ,
87+ Self :: Message ( message) => Some ( div ( ) . child ( message. clone ( ) ) . into_any_element ( ) ) ,
3588 Self :: ErrorOutput {
3689 ename,
3790 evalue,
@@ -50,6 +103,8 @@ impl LineHeight for OutputType {
50103 Self :: Plain ( stdio) => stdio. num_lines ( cx) ,
51104 Self :: Media ( ( _mimetype, value) ) => value. as_str ( ) . unwrap_or ( "" ) . lines ( ) . count ( ) as u8 ,
52105 Self :: Stream ( stdio) => stdio. num_lines ( cx) ,
106+ Self :: Image ( image) => image. num_lines ( cx) ,
107+ Self :: Message ( message) => message. lines ( ) . count ( ) as u8 ,
53108 Self :: ErrorOutput {
54109 ename,
55110 evalue,
@@ -119,15 +174,40 @@ pub enum ExecutionStatus {
119174 Finished ,
120175}
121176
122- // Cell has status that's dependent on the runtime
123- // The runtime itself has status
124-
125177pub struct ExecutionView {
126178 pub execution_id : ExecutionId ,
127179 pub outputs : Vec < OutputType > ,
128180 pub status : ExecutionStatus ,
129181}
130182
183+ pub fn extract_image_output ( mimetype : & MimeType , value : & Value ) -> Result < OutputType > {
184+ let media_type = match mimetype {
185+ // TODO: Introduce From<MimeType> for str in runtimelib
186+ // We don't necessarily need it since we use guess_format, however we could skip
187+ // it if we wanted to.
188+ MimeType :: Png => "image/png" ,
189+ MimeType :: Jpeg => "image/jpeg" ,
190+ _ => return Err ( anyhow:: anyhow!( "Unsupported image format" ) ) ,
191+ } ;
192+
193+ let bytes = value. as_str ( ) . ok_or ( anyhow ! ( "Invalid image data" ) ) ?;
194+ let bytes = base64:: decode ( bytes) ?;
195+
196+ let format = image:: guess_format ( & bytes) ?;
197+ let data = image:: load_from_memory_with_format ( & bytes, format) ?. into_bgra8 ( ) ;
198+
199+ let height = data. height ( ) ;
200+ let width = data. width ( ) ;
201+
202+ let gpui_image_data = ImageData :: new ( data) ;
203+
204+ return Ok ( OutputType :: Image ( ImageView {
205+ height,
206+ width,
207+ image : Arc :: new ( gpui_image_data) ,
208+ } ) ) ;
209+ }
210+
131211impl ExecutionView {
132212 pub fn new ( execution_id : ExecutionId , _cx : & mut ViewContext < Self > ) -> Self {
133213 Self {
@@ -156,6 +236,14 @@ impl ExecutionView {
156236 MimeType :: Markdown => {
157237 OutputType :: Plain ( TerminalOutput :: from ( value. as_str ( ) . unwrap_or ( "" ) ) )
158238 }
239+ MimeType :: Png | MimeType :: Jpeg => {
240+ match extract_image_output ( & mimetype, & value) {
241+ Ok ( output) => output,
242+ Err ( error) => {
243+ OutputType :: Message ( format ! ( "Failed to load image: {}" , error) )
244+ }
245+ }
246+ }
159247 // We don't handle this type, but ok
160248 _ => OutputType :: Media ( ( mimetype, value) ) ,
161249 }
0 commit comments