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."