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