Kitty Graphics Protocol in OCaml
terminal graphics ocaml
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(* Kitty Graphics Protocol Demo - Matching kgp/examples/demo *) 7 8module K = Kgp 9 10(* Helper: Generate a solid color RGBA image *) 11let solid_color_rgba ~width ~height ~r ~g ~b ~a = 12 let pixels = Bytes.create (width * height * 4) in 13 for i = 0 to (width * height) - 1 do 14 let idx = i * 4 in 15 Bytes.set pixels idx (Char.chr r); 16 Bytes.set pixels (idx + 1) (Char.chr g); 17 Bytes.set pixels (idx + 2) (Char.chr b); 18 Bytes.set pixels (idx + 3) (Char.chr a) 19 done; 20 Bytes.to_string pixels 21 22(* Helper: Generate a solid color RGB image (no alpha) *) 23let solid_color_rgb ~width ~height ~r ~g ~b = 24 let pixels = Bytes.create (width * height * 3) in 25 for i = 0 to (width * height) - 1 do 26 let idx = i * 3 in 27 Bytes.set pixels idx (Char.chr r); 28 Bytes.set pixels (idx + 1) (Char.chr g); 29 Bytes.set pixels (idx + 2) (Char.chr b) 30 done; 31 Bytes.to_string pixels 32 33(* Helper: Generate a gradient RGBA image *) 34let gradient_rgba ~width ~height = 35 let pixels = Bytes.create (width * height * 4) in 36 for y = 0 to height - 1 do 37 for x = 0 to width - 1 do 38 let idx = ((y * width) + x) * 4 in 39 let r = 255 * x / width in 40 let b = 255 * (width - x) / width in 41 Bytes.set pixels idx (Char.chr r); 42 Bytes.set pixels (idx + 1) (Char.chr 128); 43 Bytes.set pixels (idx + 2) (Char.chr b); 44 Bytes.set pixels (idx + 3) '\xff' 45 done 46 done; 47 Bytes.to_string pixels 48 49(* Helper: Read a file *) 50let read_file filename = 51 let ic = open_in_bin filename in 52 let n = in_channel_length ic in 53 let s = really_input_string ic n in 54 close_in ic; 55 s 56 57let send cmd ~data = 58 print_string (K.to_string cmd ~data); 59 flush stdout 60 61let wait_for_enter () = 62 print_string "Press Enter to continue..."; 63 flush stdout; 64 let _ = read_line () in 65 print_newline () 66 67let clear_screen () = 68 print_string "\x1b[2J\x1b[H"; 69 for _ = 1 to 5 do 70 print_newline () 71 done; 72 flush stdout 73 74let () = 75 let reader = stdin in 76 ignore reader; 77 78 clear_screen (); 79 print_endline "Kitty Graphics Protocol - OCaml Demo"; 80 print_endline "====================================="; 81 print_newline (); 82 print_endline "Press Enter to proceed through each demo..."; 83 print_newline (); 84 85 (* Demo 1: Basic formats - PNG *) 86 clear_screen (); 87 print_endline "Demo 1: Image Formats - PNG format"; 88 (* Read camel.png and display a small portion as demo *) 89 (try 90 let png_data = read_file "camel.png" in 91 send 92 (K.transmit_and_display ~image_id:1 ~format:`Png ~quiet:`Errors_only 93 ~placement:(K.Placement.make ~columns:15 ~rows:8 ()) 94 ()) 95 ~data:png_data; 96 print_endline "camel.png displayed using PNG format" 97 with _ -> 98 (* Fallback: red square as RGBA *) 99 let red_data = 100 solid_color_rgba ~width:100 ~height:100 ~r:255 ~g:0 ~b:0 ~a:255 101 in 102 send 103 (K.transmit_and_display ~image_id:1 ~format:`Rgba32 ~width:100 104 ~height:100 ~quiet:`Errors_only ()) 105 ~data:red_data; 106 print_endline "Red square displayed (camel.png not found)"); 107 print_newline (); 108 wait_for_enter (); 109 110 (* Demo 2: Basic formats - RGBA *) 111 clear_screen (); 112 print_endline "Demo 2: Image Formats - RGBA format (32-bit)"; 113 let blue_data = 114 solid_color_rgba ~width:100 ~height:100 ~r:0 ~g:0 ~b:255 ~a:255 115 in 116 send 117 (K.transmit_and_display ~image_id:2 ~format:`Rgba32 ~width:100 ~height:100 118 ~quiet:`Errors_only ()) 119 ~data:blue_data; 120 print_endline "Blue square displayed using raw RGBA format"; 121 print_newline (); 122 wait_for_enter (); 123 124 (* Demo 3: Basic formats - RGB *) 125 clear_screen (); 126 print_endline "Demo 3: Image Formats - RGB format (24-bit)"; 127 let green_data = solid_color_rgb ~width:100 ~height:100 ~r:0 ~g:255 ~b:0 in 128 send 129 (K.transmit_and_display ~image_id:3 ~format:`Rgb24 ~width:100 ~height:100 130 ~quiet:`Errors_only ()) 131 ~data:green_data; 132 print_endline "Green square displayed using raw RGB format (no alpha channel)"; 133 print_newline (); 134 wait_for_enter (); 135 136 (* Demo 4: Compression - Note: would need zlib library for actual compression *) 137 clear_screen (); 138 print_endline "Demo 4: Large Image (compression requires zlib library)"; 139 let orange_data = 140 solid_color_rgba ~width:200 ~height:200 ~r:255 ~g:165 ~b:0 ~a:255 141 in 142 send 143 (K.transmit_and_display ~image_id:4 ~format:`Rgba32 ~width:200 ~height:200 144 ~quiet:`Errors_only ()) 145 ~data:orange_data; 146 Printf.printf "Orange square (200x200) - %d bytes uncompressed\n" 147 (String.length orange_data); 148 print_newline (); 149 wait_for_enter (); 150 151 (* Demo 5: Load and display external PNG file *) 152 clear_screen (); 153 print_endline "Demo 5: Loading external PNG file (camel.png)"; 154 (try 155 let png_data = read_file "camel.png" in 156 send 157 (K.transmit_and_display ~image_id:10 ~format:`Png ~quiet:`Errors_only ()) 158 ~data:png_data; 159 print_endline "camel.png loaded and displayed" 160 with Sys_error msg -> Printf.printf "camel.png not found: %s\n" msg); 161 print_newline (); 162 wait_for_enter (); 163 164 (* Demo 6: Cropping and scaling *) 165 clear_screen (); 166 print_endline "Demo 6: Cropping and Scaling - Display part of an image"; 167 let gradient = gradient_rgba ~width:200 ~height:200 in 168 send 169 (K.transmit_and_display ~image_id:20 ~format:`Rgba32 ~width:200 ~height:200 170 ~placement: 171 (K.Placement.make ~source_x:50 ~source_y:50 ~source_width:100 172 ~source_height:100 ~columns:10 ~rows:10 ()) 173 ~quiet:`Errors_only ()) 174 ~data:gradient; 175 print_endline "Cropped to center 100x100 region of a 200x200 gradient"; 176 print_newline (); 177 wait_for_enter (); 178 179 (* Demo 7: Multiple placements *) 180 clear_screen (); 181 print_endline "Demo 7: Multiple Placements - One image, multiple displays"; 182 let cyan_data = 183 solid_color_rgba ~width:80 ~height:80 ~r:0 ~g:255 ~b:255 ~a:255 184 in 185 (* Transmit once with an ID *) 186 send 187 (K.transmit ~image_id:100 ~format:`Rgba32 ~width:80 ~height:80 188 ~quiet:`Errors_only ()) 189 ~data:cyan_data; 190 (* Create first placement *) 191 send 192 (K.display ~image_id:100 193 ~placement:(K.Placement.make ~columns:10 ~rows:5 ()) 194 ~quiet:`Errors_only ()) 195 ~data:""; 196 (* Create second placement *) 197 send 198 (K.display ~image_id:100 199 ~placement:(K.Placement.make ~columns:5 ~rows:3 ()) 200 ~quiet:`Errors_only ()) 201 ~data:""; 202 print_newline (); 203 wait_for_enter (); 204 205 (* Demo 8: Multiple placements with spacing *) 206 clear_screen (); 207 print_endline "Demo 8: Multiple Placements with Different Sizes"; 208 print_newline (); 209 print_endline "Showing same image at different sizes:"; 210 print_newline (); 211 (* Create a gradient square *) 212 let grad_small = gradient_rgba ~width:100 ~height:100 in 213 (* Transmit once *) 214 send 215 (K.transmit ~image_id:160 ~format:`Rgba32 ~width:100 ~height:100 216 ~quiet:`Errors_only ()) 217 ~data:grad_small; 218 (* Place same image three times at different sizes *) 219 send 220 (K.display ~image_id:160 221 ~placement:(K.Placement.make ~columns:5 ~rows:5 ()) 222 ~quiet:`Errors_only ()) 223 ~data:""; 224 print_string " "; 225 send 226 (K.display ~image_id:160 227 ~placement:(K.Placement.make ~columns:8 ~rows:8 ()) 228 ~quiet:`Errors_only ()) 229 ~data:""; 230 print_string " "; 231 send 232 (K.display ~image_id:160 233 ~placement:(K.Placement.make ~columns:12 ~rows:12 ()) 234 ~quiet:`Errors_only ()) 235 ~data:""; 236 print_newline (); 237 print_newline (); 238 print_endline "Small (5x5 cells), Medium (8x8 cells), Large (12x12 cells)"; 239 print_newline (); 240 wait_for_enter (); 241 242 (* Demo 9: Z-index layering *) 243 clear_screen (); 244 print_endline "Demo 9: Z-Index Layering - Images above/below text"; 245 let bg_data = 246 solid_color_rgba ~width:200 ~height:100 ~r:255 ~g:165 ~b:0 ~a:128 247 in 248 send 249 (K.transmit_and_display ~image_id:200 ~format:`Rgba32 ~width:200 ~height:100 250 ~placement:(K.Placement.make ~z_index:(-1) ~cursor:`Static ()) 251 ~quiet:`Errors_only ()) 252 ~data:bg_data; 253 print_endline "This orange square should appear behind the text!"; 254 print_newline (); 255 wait_for_enter (); 256 257 (* Demo 10: Query support *) 258 clear_screen (); 259 print_endline "Demo 10: Query Support - Check terminal capabilities"; 260 let query_str = K.Detect.make_query () in 261 print_string query_str; 262 flush stdout; 263 print_endline "(Check if your terminal responds with OK)"; 264 print_newline (); 265 wait_for_enter (); 266 267 (* Demo 11: Animation - color-changing square *) 268 clear_screen (); 269 print_endline "Demo 11: Animation - Color-changing square"; 270 print_endline "Creating animated sequence with 4 colors..."; 271 272 let width, height = (80, 80) in 273 let image_id = 300 in 274 275 (* Create base frame (red) - transmit without displaying *) 276 let red_frame = solid_color_rgba ~width ~height ~r:255 ~g:0 ~b:0 ~a:255 in 277 send 278 (K.transmit ~image_id ~format:`Rgba32 ~width ~height ~quiet:`Errors_only ()) 279 ~data:red_frame; 280 281 (* Add frames with composition replace *) 282 let orange_frame = 283 solid_color_rgba ~width ~height ~r:255 ~g:165 ~b:0 ~a:255 284 in 285 send 286 (K.frame ~image_id ~format:`Rgba32 ~width ~height 287 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 288 ~quiet:`Errors_only ()) 289 ~data:orange_frame; 290 291 let yellow_frame = 292 solid_color_rgba ~width ~height ~r:255 ~g:255 ~b:0 ~a:255 293 in 294 send 295 (K.frame ~image_id ~format:`Rgba32 ~width ~height 296 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 297 ~quiet:`Errors_only ()) 298 ~data:yellow_frame; 299 300 let green_frame = solid_color_rgba ~width ~height ~r:0 ~g:255 ~b:0 ~a:255 in 301 send 302 (K.frame ~image_id ~format:`Rgba32 ~width ~height 303 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 304 ~quiet:`Errors_only ()) 305 ~data:green_frame; 306 307 (* Create placement and start animation *) 308 send 309 (K.display ~image_id 310 ~placement: 311 (K.Placement.make ~placement_id:1 ~cell_x_offset:0 ~cell_y_offset:0 312 ~cursor:`Static ()) 313 ~quiet:`Errors_only ()) 314 ~data:""; 315 316 (* Set root frame gap - root frame has no gap by default per Kitty protocol *) 317 send (K.animate ~image_id (K.Animation.set_gap ~frame:1 ~gap_ms:100)) ~data:""; 318 319 (* Start animation with infinite looping *) 320 send (K.animate ~image_id (K.Animation.set_state ~loops:1 `Run)) ~data:""; 321 322 print_newline (); 323 print_endline 324 "Animation playing with colors: Red -> Orange -> Yellow -> Green"; 325 print_newline (); 326 327 (* Simulate movement by deleting and recreating placement at different positions *) 328 for i = 1 to 7 do 329 Unix.sleepf 0.4; 330 331 (* Delete the current placement *) 332 send (K.delete ~quiet:`Errors_only (`By_id (image_id, Some 1))) ~data:""; 333 334 (* Create new placement at next position *) 335 send 336 (K.display ~image_id 337 ~placement: 338 (K.Placement.make ~placement_id:1 ~cell_x_offset:(i * 5) 339 ~cell_y_offset:0 ~cursor:`Static ()) 340 ~quiet:`Errors_only ()) 341 ~data:"" 342 done; 343 344 (* Stop the animation *) 345 send (K.animate ~image_id (K.Animation.set_state `Stop)) ~data:""; 346 347 print_endline "Animation stopped."; 348 print_newline (); 349 print_newline (); 350 print_endline "Demo complete!"; 351 print_newline (); 352 print_endline "For more examples, see the library documentation."; 353 wait_for_enter ()