@@ -31,6 +31,63 @@ pub(crate) fn render_rgba_zlib_for_size(
3131 render_with_rgba_zlib_chunks_for_size ( image, Some ( ( columns. max ( 1 ) , rows. max ( 1 ) ) ) )
3232}
3333
34+ pub ( crate ) fn render_rgba_zlib_for_size_with_id (
35+ image : & RgbaImage ,
36+ image_id : u32 ,
37+ columns : u32 ,
38+ rows : u32 ,
39+ ) -> Result < String > {
40+ let z_index = z_index_for_image_id ( image_id) ;
41+ let compressed =
42+ zlib_compress_bytes ( image. as_raw ( ) ) . context ( "compressing kitty RGBA payload" ) ?;
43+ let encoded = STANDARD . encode ( compressed) ;
44+ render_chunked_payload_str_with_action (
45+ image. width ( ) ,
46+ image. height ( ) ,
47+ Some ( ( columns. max ( 1 ) , rows. max ( 1 ) ) ) ,
48+ & encoded,
49+ "a=T" ,
50+ & format ! ( "i={image_id},p=1,z={z_index},f=32,o=z,q=2" ) ,
51+ false ,
52+ )
53+ }
54+
55+ pub ( crate ) fn transmit_rgba_zlib_with_id ( image : & RgbaImage , image_id : u32 ) -> Result < String > {
56+ let compressed =
57+ zlib_compress_bytes ( image. as_raw ( ) ) . context ( "compressing kitty RGBA payload" ) ?;
58+ let encoded = STANDARD . encode ( compressed) ;
59+ render_chunked_payload_str_with_action (
60+ image. width ( ) ,
61+ image. height ( ) ,
62+ None ,
63+ & encoded,
64+ "a=t" ,
65+ & format ! ( "i={image_id},f=32,o=z,q=2" ) ,
66+ false ,
67+ )
68+ }
69+
70+ pub ( crate ) fn place_image_for_size ( image_id : u32 , columns : u32 , rows : u32 ) -> String {
71+ let z_index = z_index_for_image_id ( image_id) ;
72+ format ! (
73+ "{KITTY_PREFIX}a=p,i={image_id},p=1,z={z_index},c={},r={},C=1,q=2;{KITTY_SUFFIX}" ,
74+ columns. max( 1 ) ,
75+ rows. max( 1 )
76+ )
77+ }
78+
79+ pub ( crate ) fn delete_visible_placements ( ) -> & ' static str {
80+ "\u{1b} _Ga=d,q=2;\u{1b} \\ "
81+ }
82+
83+ fn z_index_for_image_id ( image_id : u32 ) -> i32 {
84+ if image_id % 2 == 0 { 0 } else { 1 }
85+ }
86+
87+ pub ( crate ) fn delete_image_placement ( image_id : u32 , placement_id : u32 ) -> String {
88+ format ! ( "{KITTY_PREFIX}a=d,d=i,i={image_id},p={placement_id},q=2;{KITTY_SUFFIX}" )
89+ }
90+
3491fn render_with_png_chunks ( image : & DynamicImage ) -> Result < String > {
3592 render_with_png_chunks_for_size ( image, None )
3693}
@@ -90,11 +147,15 @@ fn render_chunked_payload_with_format(
90147 format_control : & str ,
91148) -> Result < String > {
92149 if chunks. is_empty ( ) {
93- return Ok ( format ! ( "{KITTY_PREFIX}f=100,t=d,m=0;\u{1b} \\ " ) ) ;
150+ return Ok ( format ! (
151+ "{}{KITTY_PREFIX}f=100,t=d,m=0;\u{1b} \\ " ,
152+ delete_visible_placements( )
153+ ) ) ;
94154 }
95155
96156 let payload_len = chunks. iter ( ) . map ( String :: len) . sum :: < usize > ( ) ;
97157 let mut output = String :: with_capacity ( payload_len + chunks. len ( ) * 64 ) ;
158+ output. push_str ( delete_visible_placements ( ) ) ;
98159
99160 for ( index, chunk) in chunks. iter ( ) . enumerate ( ) {
100161 let is_last = index + 1 == chunks. len ( ) ;
@@ -124,13 +185,41 @@ fn render_chunked_payload_str_with_format(
124185 display_cells : Option < ( u32 , u32 ) > ,
125186 base64_payload : & str ,
126187 format_control : & str ,
188+ ) -> Result < String > {
189+ render_chunked_payload_str_with_action (
190+ width,
191+ height,
192+ display_cells,
193+ base64_payload,
194+ "a=T" ,
195+ format_control,
196+ true ,
197+ )
198+ }
199+
200+ fn render_chunked_payload_str_with_action (
201+ width : u32 ,
202+ height : u32 ,
203+ display_cells : Option < ( u32 , u32 ) > ,
204+ base64_payload : & str ,
205+ action_control : & str ,
206+ format_control : & str ,
207+ clear_before_display : bool ,
127208) -> Result < String > {
128209 let chunks = chunked_base64_payload ( base64_payload, 4096 ) ;
129210 if chunks. is_empty ( ) {
130- return Ok ( format ! ( "{KITTY_PREFIX}f=100,t=d,m=0;\u{1b} \\ " ) ) ;
211+ let clear = if clear_before_display {
212+ delete_visible_placements ( )
213+ } else {
214+ ""
215+ } ;
216+ return Ok ( format ! ( "{clear}{KITTY_PREFIX}f=100,t=d,m=0;\u{1b} \\ " ) ) ;
131217 }
132218
133219 let mut output = String :: with_capacity ( base64_payload. len ( ) + chunks. len ( ) * 64 ) ;
220+ if clear_before_display {
221+ output. push_str ( delete_visible_placements ( ) ) ;
222+ }
134223 for ( index, chunk) in chunks. iter ( ) . enumerate ( ) {
135224 let is_last = index + 1 == chunks. len ( ) ;
136225 let chunk_mode = if is_last { 0 } else { 1 } ;
@@ -141,7 +230,7 @@ fn render_chunked_payload_str_with_format(
141230 . map ( |( columns, rows) | format ! ( ",c={columns},r={rows},C=1" ) )
142231 . unwrap_or_default ( ) ;
143232 output. push_str ( & format ! (
144- "a=T ,{format_control},t=d,s={width},v={height}{display},m={chunk_mode};{chunk}" ,
233+ "{action_control} ,{format_control},t=d,s={width},v={height}{display},m={chunk_mode};{chunk}" ,
145234 chunk = chunk
146235 ) ) ;
147236 } else {
@@ -164,6 +253,7 @@ mod tests {
164253 let payload = super :: render ( & image) . unwrap ( ) ;
165254 assert ! ( payload. starts_with( "\u{1b} _G" ) ) ;
166255 assert ! ( payload. ends_with( "\u{1b} \\ " ) ) ;
256+ assert ! ( payload. starts_with( super :: delete_visible_placements( ) ) ) ;
167257 assert ! ( payload. contains( "t=d" ) ) ;
168258 }
169259
@@ -176,6 +266,37 @@ mod tests {
176266 assert ! ( payload. contains( ",c=80,r=24,C=1," ) ) ;
177267 }
178268
269+ #[ test]
270+ fn kitty_can_transmit_and_place_image_by_id ( ) {
271+ let image = ImageBuffer :: from_pixel ( 2 , 2 , Rgba ( [ 0 , 0 , 0 , 255 ] ) ) ;
272+
273+ let transmit = super :: transmit_rgba_zlib_with_id ( & image, 42 ) . unwrap ( ) ;
274+ let place = super :: place_image_for_size ( 42 , 80 , 24 ) ;
275+
276+ assert ! ( transmit. contains( "a=t," ) ) ;
277+ assert ! ( transmit. contains( "i=42" ) ) ;
278+ assert ! ( transmit. contains( "f=32,o=z" ) ) ;
279+ assert ! ( transmit. contains( "q=2" ) ) ;
280+ assert ! ( !transmit. contains( ",c=80,r=24" ) ) ;
281+ assert ! ( !transmit. contains( "a=d" ) ) ;
282+ assert_eq ! ( place, "\u{1b} _Ga=p,i=42,p=1,z=0,c=80,r=24,C=1,q=2;\u{1b} \\ " ) ;
283+ }
284+
285+ #[ test]
286+ fn kitty_id_display_places_without_predelete ( ) {
287+ let image = ImageBuffer :: from_pixel ( 2 , 2 , Rgba ( [ 0 , 0 , 0 , 255 ] ) ) ;
288+
289+ let payload = super :: render_rgba_zlib_for_size_with_id ( & image, 7 , 80 , 24 ) . unwrap ( ) ;
290+
291+ assert ! ( !payload. starts_with( super :: delete_visible_placements( ) ) ) ;
292+ assert ! ( payload. contains( "a=T,i=7,p=1,z=1,f=32,o=z,q=2" ) ) ;
293+ assert ! ( !payload. contains( "a=d" ) ) ;
294+ assert_eq ! (
295+ super :: delete_image_placement( 7 , 1 ) ,
296+ "\u{1b} _Ga=d,d=i,i=7,p=1,q=2;\u{1b} \\ "
297+ ) ;
298+ }
299+
179300 #[ test]
180301 fn kitty_multichunk_payload_uses_continuation_headers_only_after_first_chunk ( ) {
181302 let image = image:: DynamicImage :: ImageRgba8 ( ImageBuffer :: from_fn ( 96 , 96 , |x, y| {
@@ -192,8 +313,9 @@ mod tests {
192313 . collect :: < Vec < _ > > ( ) ;
193314
194315 assert ! ( packets. len( ) > 1 ) ;
195- assert ! ( packets[ 0 ] . starts_with( "a=T,f=100,t=d," ) ) ;
196- for packet in packets. iter ( ) . skip ( 1 ) {
316+ assert ! ( packets[ 0 ] . starts_with( "a=d,q=2" ) ) ;
317+ assert ! ( packets[ 1 ] . starts_with( "a=T,f=100,t=d," ) ) ;
318+ for packet in packets. iter ( ) . skip ( 2 ) {
197319 assert ! ( packet. starts_with( "m=" ) ) ;
198320 assert ! ( !packet. contains( "a=T" ) ) ;
199321 let ( _, data) = packet. split_once ( ';' ) . unwrap ( ) ;
0 commit comments