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