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