package matrix
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Fast, modern terminal toolkit for OCaml
Install
dune-project
Dependency
Authors
Maintainers
Sources
mosaic-0.1.0.tbz
sha256=9e4e90d17f9b2af1b07071fe425bc2c519c849c4f1d1ab73cde512be2d874849
sha512=06e9c4a741590942e81a27738d0b5c0413fafec8cf3b7dae047ad69f155e7b718aa4223818dc161b7d028efffcfd3365905e264d6fd31d453910ddfa91dcf9b9
doc/src/matrix.ansi/color.ml.html
Source file color.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554type t = | Black | Red | Green | Yellow | Blue | Magenta | Cyan | White | Bright_black | Bright_red | Bright_green | Bright_yellow | Bright_blue | Bright_magenta | Bright_cyan | Bright_white | Extended of int | Rgb of { r : int; g : int; b : int } | Rgba of { r : int; g : int; b : int; a : int } let clamp_byte v = max 0 (min 255 v) let clamp_channel_f v = max 0. (min 1. v) let float_of_byte v = float_of_int v /. 255. let byte_of_float v = int_of_float (Float.round (clamp_channel_f v *. 255.)) (* Standard xterm-256 ANSI 16 color palette (RGB in [0,255]) *) let ansi_16_rgb = [| (0, 0, 0); (* 0: Black *) (205, 0, 0); (* 1: Red *) (0, 205, 0); (* 2: Green *) (205, 205, 0); (* 3: Yellow *) (0, 0, 238); (* 4: Blue *) (205, 0, 205); (* 5: Magenta *) (0, 205, 205); (* 6: Cyan *) (229, 229, 229); (* 7: White *) (127, 127, 127); (* 8: Bright_black *) (255, 0, 0); (* 9: Bright_red *) (0, 255, 0); (* 10: Bright_green *) (255, 255, 0); (* 11: Bright_yellow *) (92, 92, 255); (* 12: Bright_blue *) (255, 0, 255); (* 13: Bright_magenta *) (0, 255, 255); (* 14: Bright_cyan *) (255, 255, 255); (* 15: Bright_white *) |] let cube_level = [| 0; 95; 135; 175; 215; 255 |] (* Pre-computed flat palette: 256 colors * 3 channels = 768 values. Avoids tuple allocation when looking up palette colors. *) let palette_flat = let arr = Array.make 768 0 in for i = 0 to 255 do let base = i * 3 in if i < 16 then begin let r, g, b = ansi_16_rgb.(i) in arr.(base) <- r; arr.(base + 1) <- g; arr.(base + 2) <- b end else if i < 232 then begin let n = i - 16 in arr.(base) <- cube_level.(n / 36); arr.(base + 1) <- cube_level.(n / 6 mod 6); arr.(base + 2) <- cube_level.(n mod 6) end else begin let gray = 8 + ((i - 232) * 10) in arr.(base) <- gray; arr.(base + 1) <- gray; arr.(base + 2) <- gray end done; arr let palette_index = function | Black -> 0 | Red -> 1 | Green -> 2 | Yellow -> 3 | Blue -> 4 | Magenta -> 5 | Cyan -> 6 | White -> 7 | Bright_black -> 8 | Bright_red -> 9 | Bright_green -> 10 | Bright_yellow -> 11 | Bright_blue -> 12 | Bright_magenta -> 13 | Bright_cyan -> 14 | Bright_white -> 15 | Extended idx -> idx (* palette_rgb_int clamps if needed *) | Rgb _ | Rgba _ -> -1 let palette_rgb_int idx = let idx = clamp_byte idx in if idx < 16 then ansi_16_rgb.(idx) else if idx < 232 then let n = idx - 16 in let r = cube_level.(n / 36) in let g = cube_level.(n / 6 mod 6) in let b = cube_level.(n mod 6) in (r, g, b) else let gray = 8 + ((idx - 232) * 10) in (gray, gray, gray) let to_rgb color = match color with | Rgb { r; g; b } -> (r, g, b) | Rgba { r; g; b; _ } -> (r, g, b) | _ -> let idx = palette_index color in if idx >= 0 then palette_rgb_int idx else (0, 0, 0) let to_rgba color = let r, g, b = to_rgb color in let a = match color with Rgba { a; _ } -> a | _ -> 255 in (r, g, b, a) let to_rgba_f color = let r, g, b, a = to_rgba color in (float_of_byte r, float_of_byte g, float_of_byte b, float_of_byte a) let default = Rgba { r = 0; g = 0; b = 0; a = 0 } let black = Black let red = Red let green = Green let yellow = Yellow let blue = Blue let magenta = Magenta let cyan = Cyan let white = White let bright_black = Bright_black let bright_red = Bright_red let bright_green = Bright_green let bright_yellow = Bright_yellow let bright_blue = Bright_blue let bright_magenta = Bright_magenta let bright_cyan = Bright_cyan let bright_white = Bright_white let grayscale ~level = let level = max 0 (min 23 level) in Extended (232 + level) let of_rgb r g b = Rgb { r = clamp_byte r; g = clamp_byte g; b = clamp_byte b } let of_rgba r g b a = Rgba { r = clamp_byte r; g = clamp_byte g; b = clamp_byte b; a = clamp_byte a } let of_rgba_f r g b a = of_rgba (byte_of_float r) (byte_of_float g) (byte_of_float b) (byte_of_float a) (* Pre-allocated Extended colors (16-255) - avoids allocation in hot paths *) let extended_colors = Array.init 240 (fun i -> Extended (i + 16)) let of_palette_index idx = let idx = clamp_byte idx in if idx < 16 then match idx with | 0 -> Black | 1 -> Red | 2 -> Green | 3 -> Yellow | 4 -> Blue | 5 -> Magenta | 6 -> Cyan | 7 -> White | 8 -> Bright_black | 9 -> Bright_red | 10 -> Bright_green | 11 -> Bright_yellow | 12 -> Bright_blue | 13 -> Bright_magenta | 14 -> Bright_cyan | _ -> Bright_white else Array.unsafe_get extended_colors (idx - 16) let of_hsl ~h ~s ~l ?a () = let h = Float.rem h 360. in let h = if h < 0. then h +. 360. else h in let s = clamp_channel_f s in let l = clamp_channel_f l in let a = Option.value a ~default:1.0 |> clamp_channel_f in let c = (1. -. abs_float ((2. *. l) -. 1.)) *. s in let h' = h /. 60. in let x = c *. (1. -. abs_float (Float.rem h' 2. -. 1.)) in let m = l -. (c /. 2.) in let r', g', b' = if h < 60. then (c, x, 0.) else if h < 120. then (x, c, 0.) else if h < 180. then (0., c, x) else if h < 240. then (0., x, c) else if h < 300. then (x, 0., c) else (c, 0., x) in let r = byte_of_float (r' +. m) in let g = byte_of_float (g' +. m) in let b = byte_of_float (b' +. m) in let alpha = byte_of_float a in if alpha = 255 then of_rgb r g b else of_rgba r g b alpha let to_hsl color = let rf, gf, bf, af = to_rgba_f color in let max_val = max rf (max gf bf) in let min_val = min rf (min gf bf) in let l = (max_val +. min_val) /. 2. in if max_val = min_val then (0., 0., l, af) else let d = max_val -. min_val in let s = if l > 0.5 then d /. (2. -. max_val -. min_val) else d /. (max_val +. min_val) in let h = if max_val = rf then ((gf -. bf) /. d) +. if gf < bf then 6. else 0. else if max_val = gf then ((bf -. rf) /. d) +. 2. else ((rf -. gf) /. d) +. 4. in let h = h *. 60. in (h, s, l, af) (* Internal helper: extract RGBA components without tuple allocation. Returns components via out parameters encoded as a single int: (r << 24) | (g << 16) | (b << 8) | a *) let[@inline] rgba_packed color = match color with | Rgb { r; g; b } -> (r lsl 24) lor (g lsl 16) lor (b lsl 8) lor 255 | Rgba { r; g; b; a } -> (r lsl 24) lor (g lsl 16) lor (b lsl 8) lor a | _ -> let idx = palette_index color in let base = idx * 3 in let r = Array.unsafe_get palette_flat base in let g = Array.unsafe_get palette_flat (base + 1) in let b = Array.unsafe_get palette_flat (base + 2) in (r lsl 24) lor (g lsl 16) lor (b lsl 8) lor 255 let equal a b = rgba_packed a = rgba_packed b let compare a b = Int.compare (rgba_packed a) (rgba_packed b) let hash color = let h = rgba_packed color in h lxor (h lsr 16) land max_int let alpha color = match color with Rgba { a; _ } -> float_of_byte a | _ -> 1. let[@inline] with_rgba_f color f = let packed = rgba_packed color in let r = float_of_byte ((packed lsr 24) land 0xFF) in let g = float_of_byte ((packed lsr 16) land 0xFF) in let b = float_of_byte ((packed lsr 8) land 0xFF) in let a = float_of_byte (packed land 0xFF) in f r g b a let blend ?(mode = `Perceptual) ~src ~dst () = with_rgba_f src (fun sr sg sb sa_f -> with_rgba_f dst (fun dr dg db da_f -> let sa = clamp_channel_f sa_f in if sa >= 0.999 then Rgb { r = byte_of_float sr; g = byte_of_float sg; b = byte_of_float sb; } else if sa <= Float.epsilon then dst else let sa_blend = match mode with | `Linear -> sa | `Perceptual -> if sa >= 0.8 then let norm = (sa -. 0.8) *. 5. in 0.8 +. (Float.pow norm 0.2 *. 0.2) else Float.pow sa 0.9 in let blend sc dc = (sa_blend *. sc) +. ((1. -. sa_blend) *. dc) in let r = byte_of_float (blend sr dr) in let g = byte_of_float (blend sg dg) in let b = byte_of_float (blend sb db) in let a = byte_of_float (sa +. da_f -. (sa *. da_f)) in if a = 255 then Rgb { r; g; b } else Rgba { r; g; b; a })) (* Check if string contains a substring. Zero-allocation. *) let contains_substring s sub = let len = String.length s in let sublen = String.length sub in if sublen = 0 then true else if sublen > len then false else let rec match_at i j = if j >= sublen then true else if String.unsafe_get s (i + j) = String.unsafe_get sub j then match_at i (j + 1) else false in let rec check i = if i > len - sublen then false else if match_at i 0 then true else check (i + 1) in check 0 let detected_level = lazy (match Sys.getenv_opt "COLORTERM" with | Some "truecolor" | Some "24bit" -> `Truecolor | _ -> ( match Sys.getenv_opt "TERM" with | Some term when contains_substring term "256" -> `Ansi256 | Some term when contains_substring term "truecolor" -> `Truecolor | _ -> `Ansi16)) let detect_level () = Lazy.force detected_level let downgrade ?level color = (* Transparent colors (alpha=0) represent "use terminal default" — preserve them through downgrading since they carry no meaningful RGB to quantize. *) match color with | Rgba { a = 0; _ } -> color | _ -> ( let effective_level = Option.value level ~default:(detect_level ()) in match effective_level with | `Truecolor -> color | `Ansi256 | `Ansi16 -> let target_size = if effective_level = `Ansi256 then 256 else 16 in let r, g, b = to_rgb color in let min_dist = ref max_int in let nearest = ref 0 in for i = 0 to target_size - 1 do let base = i * 3 in let pr = Array.unsafe_get palette_flat base in let pg = Array.unsafe_get palette_flat (base + 1) in let pb = Array.unsafe_get palette_flat (base + 2) in let dr = r - pr in let dg = g - pg in let db = b - pb in let dist = (dr * dr) + (dg * dg) + (db * db) in if dist < !min_dist then ( min_dist := dist; nearest := i) done; of_palette_index !nearest) (* Emit SGR codes via push callback. Zero-allocation. *) let emit_sgr_codes ~bg push color = match color with | Rgba { a = 0; _ } -> push (if bg then 49 else 39) | Black -> push (if bg then 40 else 30) | Red -> push (if bg then 41 else 31) | Green -> push (if bg then 42 else 32) | Yellow -> push (if bg then 43 else 33) | Blue -> push (if bg then 44 else 34) | Magenta -> push (if bg then 45 else 35) | Cyan -> push (if bg then 46 else 36) | White -> push (if bg then 47 else 37) | Bright_black -> push (if bg then 100 else 90) | Bright_red -> push (if bg then 101 else 91) | Bright_green -> push (if bg then 102 else 92) | Bright_yellow -> push (if bg then 103 else 93) | Bright_blue -> push (if bg then 104 else 94) | Bright_magenta -> push (if bg then 105 else 95) | Bright_cyan -> push (if bg then 106 else 96) | Bright_white -> push (if bg then 107 else 97) | Extended idx -> push (if bg then 48 else 38); push 5; push (clamp_byte idx) | Rgb { r; g; b } -> push (if bg then 48 else 38); push 2; push r; push g; push b | Rgba { r; g; b; _ } -> push (if bg then 48 else 38); push 2; push r; push g; push b let to_sgr_codes ~bg color = let acc = ref [] in emit_sgr_codes ~bg (fun code -> acc := code :: !acc) color; List.rev !acc let invert color = let r, g, b = to_rgb color in of_rgb (255 - r) (255 - g) (255 - b) module Packed = struct let () = assert (Sys.int_size >= 62) let tag_shift = 58 let tag_basic = 1 lsl tag_shift let tag_extended = 2 lsl tag_shift let tag_rgb = 3 lsl tag_shift let tag_rgba = 4 lsl tag_shift let tag_mask = 7 lsl tag_shift let data_mask = (1 lsl tag_shift) - 1 let encode color = match color with | Black | Red | Green | Yellow | Blue | Magenta | Cyan | White | Bright_black | Bright_red | Bright_green | Bright_yellow | Bright_blue | Bright_magenta | Bright_cyan | Bright_white -> let idx = palette_index color in tag_basic lor idx | Extended idx -> tag_extended lor clamp_byte idx | Rgb { r; g; b } -> let data = (r lsl 16) lor (g lsl 8) lor b in tag_rgb lor data | Rgba { r; g; b; a } -> let data = (r lsl 24) lor (g lsl 16) lor (b lsl 8) lor a in tag_rgba lor data let decode packed = let tag = packed land tag_mask in let data = packed land data_mask in match tag lsr tag_shift with | 0 -> Rgba { r = 0; g = 0; b = 0; a = 0 } | 1 -> of_palette_index data | 2 -> Extended data | 3 -> let r = (data lsr 16) land 0xFF in let g = (data lsr 8) land 0xFF in let b = data land 0xFF in Rgb { r; g; b } | 4 -> let r = (data lsr 24) land 0xFF in let g = (data lsr 16) land 0xFF in let b = (data lsr 8) land 0xFF in let a = data land 0xFF in Rgba { r; g; b; a } | _ -> Rgba { r = 0; g = 0; b = 0; a = 0 } end let pack = Packed.encode let unpack = Packed.decode let string_of_color = function | Black -> "Black" | Red -> "Red" | Green -> "Green" | Yellow -> "Yellow" | Blue -> "Blue" | Magenta -> "Magenta" | Cyan -> "Cyan" | White -> "White" | Bright_black -> "Bright_black" | Bright_red -> "Bright_red" | Bright_green -> "Bright_green" | Bright_yellow -> "Bright_yellow" | Bright_blue -> "Bright_blue" | Bright_magenta -> "Bright_magenta" | Bright_cyan -> "Bright_cyan" | Bright_white -> "Bright_white" | Extended idx -> Printf.sprintf "Extended(%d)" idx | Rgb { r; g; b } -> Printf.sprintf "Rgb(%d,%d,%d)" r g b | Rgba { r; g; b; a } -> Printf.sprintf "Rgba(%d,%d,%d,%d)" r g b a let pp fmt color = Format.pp_print_string fmt (string_of_color color) let hex_value c = match c with | '0' .. '9' -> Some (Char.code c - Char.code '0') | 'a' .. 'f' -> Some (10 + Char.code c - Char.code 'a') | 'A' .. 'F' -> Some (10 + Char.code c - Char.code 'A') | _ -> None let parse_hex_component s start len = let rec aux acc idx remaining = if remaining = 0 then Some acc else match hex_value s.[idx] with | None -> None | Some v -> aux ((acc lsl 4) lor v) (idx + 1) (remaining - 1) in aux 0 start len let expand_short_hex s = let len = String.length s in let buf = Bytes.create (len * 2) in for i = 0 to len - 1 do let c = String.unsafe_get s i in Bytes.unsafe_set buf (i * 2) c; Bytes.unsafe_set buf ((i * 2) + 1) c done; (* unsafe_to_string is safe here: buf is local and not used after conversion *) Bytes.unsafe_to_string buf let sanitize_hex s = let s = if String.length s > 0 && s.[0] = '#' then String.sub s 1 (String.length s - 1) else s in match String.length s with | 3 | 4 -> expand_short_hex s | 6 | 8 -> s | _ -> "" let of_hex hex = let s = sanitize_hex hex in let len = String.length s in if len = 0 then None else if len = 6 then match ( parse_hex_component s 0 2, parse_hex_component s 2 2, parse_hex_component s 4 2 ) with | Some r, Some g, Some b -> Some (of_rgb r g b) | _ -> None else if len = 8 then match ( parse_hex_component s 0 2, parse_hex_component s 2 2, parse_hex_component s 4 2, parse_hex_component s 6 2 ) with | Some r, Some g, Some b, Some a -> Some (of_rgba r g b a) | _ -> None else None let of_hex_exn hex = match of_hex hex with | Some color -> color | None -> invalid_arg "Color.of_hex_exn: invalid hex string" let to_hex color = let r, g, b = to_rgb color in Printf.sprintf "#%02x%02x%02x" r g b
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>