The [%palette] PPX extension generates typed palette modules from JSON color definitions. Spectrum uses it internally to generate the standard palette modules for color quantization. It can also generate custom palette modules for non-standard terminal themes, though this is a niche use case.
A generated module includes:
Spectrum_palette_ppx.Palette.M.of_string)Spectrum_palette_ppx.Palette.M.to_code)Spectrum_palette_ppx.Palette.M.to_color, Spectrum_palette_ppx.Palette.M.color_list)Spectrum_palette_ppx.Palette.M.nearest) via LAB space octreeCreate a JSON file with color definitions. Each color needs a name, ANSI code, and RGB values:
[
{
"name": "red",
"colorId": 196,
"rgb": {"r": 255, "g": 0, "b": 0}
},
{
"name": "green",
"colorId": 46,
"rgb": {"r": 0, "g": 255, "b": 0}
},
{
"name": "blue",
"colorId": 21,
"rgb": {"r": 0, "g": 0, "b": 255}
}
]The JSON format:
name: Color name (converted to kebab-case for the variant constructor)colorId: ANSI color code (terminal escape sequence value)rgb: RGB components with r, g, b fields (0-255)Use the [%palette] extension in your OCaml code:
module MyPalette : Spectrum_palette_ppx.Palette.M = [%palette "colors.json"]Or without a signature constraint:
module MyPalette = [%palette "colors.json"]The path is relative to the source file location.
The generated module has this interface:
let red = MyPalette.of_string "red"
(* val red : MyPalette.t *)
let code = MyPalette.to_code red
(* val code : int = 196 *)
let color = MyPalette.to_color red
(* val color : Gg.v4 — RGBA float values (1.0, 0.0, 0.0, 1.0) *)
let all_colors = MyPalette.color_list
(* val all_colors : Gg.v4 list — one entry per palette color *)
let orange = Gg.Color.v_srgb 0.9 0.4 0.3
(* val orange : Gg.v4 — RGBA (0.9, 0.4, 0.3, 1.0) *)
let nearest_color = MyPalette.nearest orange
(* val nearest_color : Gg.v4 — nearest palette color by perceptual distance *)The [%palette] extension generates modules implementing Spectrum_palette_ppx.Palette.M:
module type M = sig
(** Palette color type (variant with one constructor per color) *)
type t
(** Convert a color name string to the palette type.
@raise InvalidColorName if the name is not in the palette *)
val of_string : string -> t
(** Get the ANSI color code for a palette color *)
val to_code : t -> int
(** Convert a palette color to its RGB representation *)
val to_color : t -> Gg.v4
(** List of all colors in the palette (in order) *)
val color_list : Gg.v4 list
(** Find the nearest palette color to the given color using
perceptual distance (LAB color space) *)
val nearest : Gg.v4 -> Gg.v4
endColor names in JSON are converted to valid OCaml variant constructors:
"SpringGreen" becomes spring-green"Dark Orange" becomes dark-orange"Color123" becomes color-123The Spectrum_palette_ppx.Palette.M.nearest function converts colors to LAB space (where numerical distance matches perceived difference) and uses an octree to find the nearest neighbor in O(log n) average time. The index is built once at module initialization and reused for all queries.
The Spectrum_palette_ppx.Palette.M.of_string function raises Spectrum_palette_ppx.Palette.InvalidColorName for unknown color names:
try
let color = MyPalette.of_string "notacolor" in
(* ... *)
with Spectrum_palette_ppx.Palette.InvalidColorName name ->
Printf.printf "Unknown color: %s\n" nameIf you know your users' terminals use non-standard color mappings, create a palette that matches:
module MyTheme = [%palette "my_theme.json"]
(* Use in terminal output *)
let code = MyTheme.of_string "background" |> MyTheme.to_code in
Printf.printf "\027[48;5;%dm Custom background \027[0m\n" codeQuantize arbitrary colors to a limited palette:
let quantize_to_palette color =
let nearest = MyPalette.nearest color in
(* Find which palette entry this is *)
List.find (fun c -> Gg.Color.equal c nearest) MyPalette.color_listSpectrum_palette_ppx.Palette Palette module type and runtime utilities.