diff --git a/examples/dune b/examples/dune index db36df4..0e287fd 100644 --- a/examples/dune +++ b/examples/dune @@ -1,3 +1,9 @@ (executable (name icat) + (modules icat) (libraries kittyimg stb_image)) + +(executable + (name nottycat) + (modules nottycat) + (libraries notty notty.unix kittyimg.notty stb_image)) diff --git a/examples/nottycat.ml b/examples/nottycat.ml new file mode 100644 index 0000000..7a1a99a --- /dev/null +++ b/examples/nottycat.ml @@ -0,0 +1,17 @@ +let icat filename = + let img = Result.get_ok (Stb_image.load ~channels:4 filename) in + let img_s = Kittyimg.string_of_bytes_ba img.Stb_image.data in + let img = + Nottyimg.of_string ~w:img.Stb_image.width ~h:img.Stb_image.height + ~format:`RGBA img_s + in + let open Notty in + let blanks n = I.(vcat @@ List.init n (fun _ -> string A.empty "")) in + I.((img <-> blanks 2 <-> string A.empty "Woop woop!") <|> (blanks 4 <-> img)) + |> Notty_unix.output_image + +let () = + match Array.to_list Sys.argv |> List.tl with + | [ filename ] -> icat filename + | _ -> + Printf.eprintf "usage: %s \n" Sys.argv.(0); exit 1 diff --git a/lib/dune b/lib/dune index 17d757e..f64ee23 100644 --- a/lib/dune +++ b/lib/dune @@ -1,4 +1,30 @@ (library (libraries base64) (name kittyimg) - (public_name kittyimg)) + (public_name kittyimg) + (modules kittyimg)) + +(library + (libraries kittyimg notty unix) + (name nottyimg) + (public_name kittyimg.notty) + (modules :standard \ kittyimg) + (foreign_stubs (language c) (names more_winsize))) + +(rule + (target rowcolumn-diacritics.txt) + (action + (with-stdout-to + %{target} + (bash "curl https://sw.kovidgoyal.net/kitty/_downloads/f0a0de9ec8d9ff4456206db8e0814937/rowcolumn-diacritics.txt")))) + +(rule + (target rowcolumns.ml) + (deps rowcolumn-diacritics.txt) + (action + (with-stdout-to + %{target} + (progn + (echo "let diacritics = [|\n") + (bash "tail -n +14 %{deps} | less | cut -d';' -f1 | sed 's/.*/ Uchar.of_int 0x\\0;/g'") + (echo "|]"))))) diff --git a/lib/kittyimg.ml b/lib/kittyimg.ml index b968e6d..7c0a8bc 100644 --- a/lib/kittyimg.ml +++ b/lib/kittyimg.ml @@ -42,6 +42,7 @@ end type display_opts = { placement : Placement.t option; + virtual_placement : unit option; (* source rectangle *) x : int option; y : int option; @@ -62,7 +63,7 @@ type display_opts = { } let display_opts - ?placement + ?placement ?virtual_placement ?x ?y ?w ?h ?xoff ?yoff ?cstretch ?rstretch @@ -71,12 +72,13 @@ let display_opts ?quiet () = - { placement; x; y; w; h; xoff; yoff; + { placement; virtual_placement; x; y; w; h; xoff; yoff; cstretch; rstretch; move_cursor; zindex; quiet } let items_of_display_opts opts = [ 'p', (opts.placement :> int option); + 'U', Option.map (fun () -> 1) opts.virtual_placement; 'x', opts.x; 'y', opts.y; 'w', opts.w; diff --git a/lib/kittyimg.mli b/lib/kittyimg.mli index 1d2cdca..10a06ef 100644 --- a/lib/kittyimg.mli +++ b/lib/kittyimg.mli @@ -26,6 +26,7 @@ type display_opts val display_opts : ?placement:Placement.t -> + ?virtual_placement:unit -> ?x:int -> ?y:int -> ?w:int -> ?h:int -> ?xoff:int -> ?yoff:int -> diff --git a/lib/more_winsize.c b/lib/more_winsize.c new file mode 100644 index 0000000..24139b5 --- /dev/null +++ b/lib/more_winsize.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include + +CAMLprim value caml_kittyimg_winsize (value vfd) { + CAMLparam1 (vfd); + + int fd = Int_val (vfd); + struct winsize w; + + if (ioctl (fd, TIOCGWINSZ, &w) >= 0) { + CAMLlocal1 (result); + result = caml_alloc_tuple(4); + + Store_field (result, 0, Val_int (w.ws_col)); + Store_field (result, 1, Val_int (w.ws_row)); + Store_field (result, 2, Val_int (w.ws_xpixel)); + Store_field (result, 3, Val_int (w.ws_ypixel)); + + CAMLreturn (result); + } + + caml_failwith("couldn't get winsize\0"); +} + diff --git a/lib/nottyimg.ml b/lib/nottyimg.ml new file mode 100644 index 0000000..1d83063 --- /dev/null +++ b/lib/nottyimg.ml @@ -0,0 +1,72 @@ +module K = Kittyimg +module RC = Rowcolumns + +exception Too_many_images + +module Ids = struct + type t = Notty.A.color + + let state = ref (0,0,0) + + let incr () = + let (r,g,b) = !state in + let b,g = if b = 5 then 0, g + 1 else b + 1, g in + let g,r = if g > 5 then 0, r + 1 else g, r in + if r > 5 then + raise Too_many_images + else + state := (r,g,b) + + let fresh () = + let (r,g,b) = !state in + let id = Notty.A.rgb ~r ~g ~b in + incr (); + id + + let to_kittyid i = K.Id.of_int (Obj.magic i land 0xff) +end + +let placeholder = Uchar.of_int 0x10EEEE + +external wsize : Unix.file_descr -> int * int * int * int = + "caml_kittyimg_winsize" + +(* Haaaaacky *) +let winsize = wsize (Unix.descr_of_out_channel stdout) + +let width_to_cols iw = + let (c,_,w,_) = winsize in + Float.round (float iw /. (float w /. float c)) + |> Float.to_int + +let height_to_rows ih = + let (_,r,_,h) = winsize in + Float.round (float ih /. (float h /. float r)) + |> Float.to_int + +let of_string ~w ~h ~format data = + let id = Ids.fresh () in + (* Load image *) + K.send_image ~w ~h ~format ~quiet:`OK ~mode:(`Store (Ids.to_kittyid id)) data; + (* Create virtual placement *) + let c = width_to_cols w in + let r = height_to_rows h in + let opts = + K.display_opts ~virtual_placement:() ~cstretch:c ~rstretch:r + ~quiet:`Failure () + in + K.display_image ~opts (Ids.to_kittyid id); + (* Create placeholder as a notty I.t *) + let id_as_attr = Notty.A.fg id in + let rows = + List.init r (fun row -> + let row = RC.diacritics.(row) in + let line = Array.make (c * 3) placeholder in + for i = 0 to c - 1 do + line.(i * 3 + 1) <- row; + line.(i * 3 + 2) <- RC.diacritics.(i); + done; + Notty.I.uchars id_as_attr line + ) + in + Notty.I.vcat rows