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(* Tiny animation test - no chunking needed *) 7(* Uses 20x20 images which are ~1067 bytes base64 (well under 4096) *) 8 9module K = Kgp 10 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 22let send cmd ~data = 23 print_string (K.to_string cmd ~data); 24 flush stdout 25 26let () = 27 (* Use 20x20 to avoid chunking: 20*20*4 = 1600 bytes, base64 ~2134 bytes *) 28 let width, height = 20, 20 in 29 let image_id = 999 in 30 31 (* Clear any existing images *) 32 send (K.delete ~free:true ~quiet:`Errors_only `All_visible) ~data:""; 33 34 (* Step 1: Transmit base frame (red) - matching Go's sequence *) 35 let red_frame = solid_color_rgba ~width ~height ~r:255 ~g:0 ~b:0 ~a:255 in 36 send 37 (K.transmit 38 ~image_id 39 ~format:`Rgba32 40 ~width ~height 41 ~quiet:`Errors_only 42 ()) 43 ~data:red_frame; 44 45 (* Step 2: Add frame (orange) with 100ms gap - like Go *) 46 let orange_frame = solid_color_rgba ~width ~height ~r:255 ~g:165 ~b:0 ~a:255 in 47 send 48 (K.frame 49 ~image_id 50 ~format:`Rgba32 51 ~width ~height 52 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 53 ~quiet:`Errors_only 54 ()) 55 ~data:orange_frame; 56 57 (* Step 3: Add frame (yellow) *) 58 let yellow_frame = solid_color_rgba ~width ~height ~r:255 ~g:255 ~b:0 ~a:255 in 59 send 60 (K.frame 61 ~image_id 62 ~format:`Rgba32 63 ~width ~height 64 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 65 ~quiet:`Errors_only 66 ()) 67 ~data:yellow_frame; 68 69 (* Step 4: Add frame (green) *) 70 let green_frame = solid_color_rgba ~width ~height ~r:0 ~g:255 ~b:0 ~a:255 in 71 send 72 (K.frame 73 ~image_id 74 ~format:`Rgba32 75 ~width ~height 76 ~frame:(K.Frame.make ~gap_ms:100 ~composition:`Overwrite ()) 77 ~quiet:`Errors_only 78 ()) 79 ~data:green_frame; 80 81 (* Step 5: Create placement - exactly like Go *) 82 send 83 (K.display 84 ~image_id 85 ~placement:(K.Placement.make 86 ~placement_id:1 87 ~cell_x_offset:0 88 ~cell_y_offset:0 89 ~cursor:`Static 90 ()) 91 ~quiet:`Errors_only 92 ()) 93 ~data:""; 94 95 (* Step 6: Start animation - exactly like Go (NO root frame gap) *) 96 send 97 (K.animate ~image_id (K.Animation.set_state ~loops:1 `Run)) 98 ~data:""; 99 100 print_endline ""; 101 print_endline "Tiny animation (20x20) - Red -> Orange -> Yellow -> Green"; 102 print_endline "This uses no chunking. Press Enter to stop..."; 103 flush stdout; 104 let _ = read_line () in 105 106 (* Stop animation *) 107 send 108 (K.animate ~image_id (K.Animation.set_state `Stop)) 109 ~data:""; 110 111 (* Clean up *) 112 send (K.delete ~free:true ~quiet:`Errors_only `All_visible) ~data:""; 113 print_endline "Done."