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 *)
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 ()