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 print_newline () done; 70 flush stdout 71 72let () = 73 let reader = stdin in 74 ignore reader; 75 76 clear_screen (); 77 print_endline "Kitty Graphics Protocol - OCaml Demo"; 78 print_endline "====================================="; 79 print_newline (); 80 print_endline "Press Enter to proceed through each demo..."; 81 print_newline (); 82 83 (* Demo 1: Basic formats - PNG *) 84 clear_screen (); 85 print_endline "Demo 1: Image Formats - PNG format"; 86 (* Read camel.png and display a small portion as demo *) 87 (try 88 let png_data = read_file "camel.png" in 89 send 90 (K.transmit_and_display 91 ~image_id:1 92 ~format:`Png 93 ~quiet:`Errors_only 94 ~placement:(K.Placement.make ~columns:15 ~rows:8 ()) 95 ()) 96 ~data:png_data; 97 print_endline "camel.png displayed using PNG format" 98 with _ -> 99 (* Fallback: red square as RGBA *) 100 let red_data = solid_color_rgba ~width:100 ~height:100 ~r:255 ~g:0 ~b:0 ~a:255 in 101 send 102 (K.transmit_and_display 103 ~image_id:1 104 ~format:`Rgba32 105 ~width:100 ~height:100 106 ~quiet:`Errors_only 107 ()) 108 ~data:red_data; 109 print_endline "Red square displayed (camel.png not found)"); 110 print_newline (); 111 wait_for_enter (); 112 113 (* Demo 2: Basic formats - RGBA *) 114 clear_screen (); 115 print_endline "Demo 2: Image Formats - RGBA format (32-bit)"; 116 let blue_data = solid_color_rgba ~width:100 ~height:100 ~r:0 ~g:0 ~b:255 ~a:255 in 117 send 118 (K.transmit_and_display 119 ~image_id:2 120 ~format:`Rgba32 121 ~width:100 ~height:100 122 ~quiet:`Errors_only 123 ()) 124 ~data:blue_data; 125 print_endline "Blue square displayed using raw RGBA format"; 126 print_newline (); 127 wait_for_enter (); 128 129 (* Demo 3: Basic formats - RGB *) 130 clear_screen (); 131 print_endline "Demo 3: Image Formats - RGB format (24-bit)"; 132 let green_data = solid_color_rgb ~width:100 ~height:100 ~r:0 ~g:255 ~b:0 in 133 send 134 (K.transmit_and_display 135 ~image_id:3 136 ~format:`Rgb24 137 ~width:100 ~height:100 138 ~quiet:`Errors_only 139 ()) 140 ~data:green_data; 141 print_endline "Green square displayed using raw RGB format (no alpha channel)"; 142 print_newline (); 143 wait_for_enter (); 144 145 (* Demo 4: Compression - Note: would need zlib library for actual compression *) 146 clear_screen (); 147 print_endline "Demo 4: Large Image (compression requires zlib library)"; 148 let orange_data = solid_color_rgba ~width:200 ~height:200 ~r:255 ~g:165 ~b:0 ~a:255 in 149 send 150 (K.transmit_and_display 151 ~image_id:4 152 ~format:`Rgba32 153 ~width:200 ~height:200 154 ~quiet:`Errors_only 155 ()) 156 ~data:orange_data; 157 Printf.printf "Orange square (200x200) - %d bytes uncompressed\n" (String.length orange_data); 158 print_newline (); 159 wait_for_enter (); 160 161 (* Demo 5: Load and display external PNG file *) 162 clear_screen (); 163 print_endline "Demo 5: Loading external PNG file (camel.png)"; 164 (try 165 let png_data = read_file "camel.png" in 166 send 167 (K.transmit_and_display 168 ~image_id:10 169 ~format:`Png 170 ~quiet:`Errors_only 171 ()) 172 ~data:png_data; 173 print_endline "camel.png loaded and displayed" 174 with Sys_error msg -> 175 Printf.printf "camel.png not found: %s\n" msg); 176 print_newline (); 177 wait_for_enter (); 178 179 (* Demo 6: Cropping and scaling *) 180 clear_screen (); 181 print_endline "Demo 6: Cropping and Scaling - Display part of an image"; 182 let gradient = gradient_rgba ~width:200 ~height:200 in 183 send 184 (K.transmit_and_display 185 ~image_id:20 186 ~format:`Rgba32 187 ~width:200 ~height:200 188 ~placement:(K.Placement.make 189 ~source_x:50 ~source_y:50 190 ~source_width:100 ~source_height:100 191 ~columns:10 ~rows:10 192 ()) 193 ~quiet:`Errors_only 194 ()) 195 ~data:gradient; 196 print_endline "Cropped to center 100x100 region of a 200x200 gradient"; 197 print_newline (); 198 wait_for_enter (); 199 200 (* Demo 7: Multiple placements *) 201 clear_screen (); 202 print_endline "Demo 7: Multiple Placements - One image, multiple displays"; 203 let cyan_data = solid_color_rgba ~width:80 ~height:80 ~r:0 ~g:255 ~b:255 ~a:255 in 204 (* Transmit once with an ID *) 205 send 206 (K.transmit 207 ~image_id:100 208 ~format:`Rgba32 209 ~width:80 ~height:80 210 ~quiet:`Errors_only 211 ()) 212 ~data:cyan_data; 213 (* Create first placement *) 214 send 215 (K.display 216 ~image_id:100 217 ~placement:(K.Placement.make ~columns:10 ~rows:5 ()) 218 ~quiet:`Errors_only 219 ()) 220 ~data:""; 221 (* Create second placement *) 222 send 223 (K.display 224 ~image_id:100 225 ~placement:(K.Placement.make ~columns:5 ~rows:3 ()) 226 ~quiet:`Errors_only 227 ()) 228 ~data:""; 229 print_newline (); 230 wait_for_enter (); 231 232 (* Demo 8: Multiple placements with spacing *) 233 clear_screen (); 234 print_endline "Demo 8: Multiple Placements with Different Sizes"; 235 print_newline (); 236 print_endline "Showing same image at different sizes:"; 237 print_newline (); 238 (* Create a gradient square *) 239 let grad_small = gradient_rgba ~width:100 ~height:100 in 240 (* Transmit once *) 241 send 242 (K.transmit 243 ~image_id:160 244 ~format:`Rgba32 245 ~width:100 ~height:100 246 ~quiet:`Errors_only 247 ()) 248 ~data:grad_small; 249 (* Place same image three times at different sizes *) 250 send 251 (K.display 252 ~image_id:160 253 ~placement:(K.Placement.make ~columns:5 ~rows:5 ()) 254 ~quiet:`Errors_only 255 ()) 256 ~data:""; 257 print_string " "; 258 send 259 (K.display 260 ~image_id:160 261 ~placement:(K.Placement.make ~columns:8 ~rows:8 ()) 262 ~quiet:`Errors_only 263 ()) 264 ~data:""; 265 print_string " "; 266 send 267 (K.display 268 ~image_id:160 269 ~placement:(K.Placement.make ~columns:12 ~rows:12 ()) 270 ~quiet:`Errors_only 271 ()) 272 ~data:""; 273 print_newline (); 274 print_newline (); 275 print_endline "Small (5x5 cells), Medium (8x8 cells), Large (12x12 cells)"; 276 print_newline (); 277 wait_for_enter (); 278 279 (* Demo 9: Z-index layering *) 280 clear_screen (); 281 print_endline "Demo 9: Z-Index Layering - Images above/below text"; 282 let bg_data = solid_color_rgba ~width:200 ~height:100 ~r:255 ~g:165 ~b:0 ~a:128 in 283 send 284 (K.transmit_and_display 285 ~image_id:200 286 ~format:`Rgba32 287 ~width:200 ~height:100 288 ~placement:(K.Placement.make ~z_index:(-1) ~cursor:`Static ()) 289 ~quiet:`Errors_only 290 ()) 291 ~data:bg_data; 292 print_endline "This orange square should appear behind the text!"; 293 print_newline (); 294 wait_for_enter (); 295 296 (* Demo 10: Query support *) 297 clear_screen (); 298 print_endline "Demo 10: Query Support - Check terminal capabilities"; 299 let query_str = K.Detect.make_query () in 300 print_string query_str; 301 flush stdout; 302 print_endline "(Check if your terminal responds with OK)"; 303 print_newline (); 304 wait_for_enter (); 305 306 (* Demo 11: Animation - color-changing square *) 307 clear_screen (); 308 print_endline "Demo 11: Animation - Color-changing square"; 309 print_endline "Creating animated sequence with 4 colors..."; 310 311 let width, height = 80, 80 in 312 let image_id = 300 in 313 314 (* Create base frame (red) - transmit without displaying *) 315 let red_frame = solid_color_rgba ~width ~height ~r:255 ~g:0 ~b:0 ~a:255 in 316 send 317 (K.transmit 318 ~image_id 319 ~format:`Rgba32 320 ~width ~height 321 ~quiet:`Errors_only 322 ()) 323 ~data:red_frame; 324 325 (* Add frames with composition replace *) 326 let orange_frame = solid_color_rgba ~width ~height ~r:255 ~g:165 ~b:0 ~a:255 in 327 send 328 (K.frame 329 ~image_id 330 ~format:`Rgba32 331 ~width ~height 332 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 333 ~quiet:`Errors_only 334 ()) 335 ~data:orange_frame; 336 337 let yellow_frame = solid_color_rgba ~width ~height ~r:255 ~g:255 ~b:0 ~a:255 in 338 send 339 (K.frame 340 ~image_id 341 ~format:`Rgba32 342 ~width ~height 343 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 344 ~quiet:`Errors_only 345 ()) 346 ~data:yellow_frame; 347 348 let green_frame = solid_color_rgba ~width ~height ~r:0 ~g:255 ~b:0 ~a:255 in 349 send 350 (K.frame 351 ~image_id 352 ~format:`Rgba32 353 ~width ~height 354 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 355 ~quiet:`Errors_only 356 ()) 357 ~data:green_frame; 358 359 (* Create placement and start animation *) 360 send 361 (K.display 362 ~image_id 363 ~placement:(K.Placement.make 364 ~placement_id:1 365 ~cell_x_offset:0 366 ~cell_y_offset:0 367 ~cursor:`Static 368 ()) 369 ~quiet:`Errors_only 370 ()) 371 ~data:""; 372 373 (* Set root frame gap - root frame has no gap by default per Kitty protocol *) 374 send 375 (K.animate ~image_id (K.Animation.set_gap ~frame:1 ~gap_ms:100)) 376 ~data:""; 377 378 (* Start animation with infinite looping *) 379 send 380 (K.animate ~image_id (K.Animation.set_state ~loops:1 `Run)) 381 ~data:""; 382 383 print_newline (); 384 print_endline "Animation playing with colors: Red -> Orange -> Yellow -> Green"; 385 print_newline (); 386 387 (* Simulate movement by deleting and recreating placement at different positions *) 388 for i = 1 to 7 do 389 Unix.sleepf 0.4; 390 391 (* Delete the current placement *) 392 send 393 (K.delete ~quiet:`Errors_only (`By_id (image_id, Some 1))) 394 ~data:""; 395 396 (* Create new placement at next position *) 397 send 398 (K.display 399 ~image_id 400 ~placement:(K.Placement.make 401 ~placement_id:1 402 ~cell_x_offset:(i * 5) 403 ~cell_y_offset:0 404 ~cursor:`Static 405 ()) 406 ~quiet:`Errors_only 407 ()) 408 ~data:"" 409 done; 410 411 (* Stop the animation *) 412 send 413 (K.animate ~image_id (K.Animation.set_state `Stop)) 414 ~data:""; 415 416 print_endline "Animation stopped."; 417 print_newline (); 418 print_newline (); 419 print_endline "Demo complete!"; 420 print_newline (); 421 print_endline "For more examples, see the library documentation."; 422 wait_for_enter ()