package color-brewery

  1. Overview
  2. Docs

Source file color_brewery.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
open Printf

type rgba = Gg.color
type cmyk = Gg.v4

(* Convert x ∈ [0,1] into an integer in [0, 255]. *)
let to_hex x =
  if x <= 0. then 0
  else if x >= 1. then 0xFF
  else truncate(x *. 255. +. 0x1.FFFFFFFFFFFFFp-2)

let to_int c =
  (to_hex(Gg.Color.r c) lsl 16) lor (to_hex(Gg.Color.g c) lsl 8)
  lor (to_hex(Gg.Color.b c))

let to_string c =
  sprintf "#%02x%02x%02x"
    (to_hex(Gg.Color.r c)) (to_hex(Gg.Color.g c)) (to_hex(Gg.Color.b c))

let of_int_exn ?(a=0.) i =
  if i < 0 || i > 0xFFFFFF then invalid_arg "Color_brewery.of_int_exn";
  let r = float(i lsr 16) /. 255. in
  let g = float((i lsr 8) land 0xFF) /. 255. in
  let b = float(i land 0xFF) /. 255. in
  Gg.Color.v r g b a

let of_int ?a i = try Some(of_int_exn ?a i) with _ -> None


let to_gray c =
  let x = 0.299 *. Gg.Color.r c +. 0.587 *. Gg.Color.g c
          +. 0.114 *. Gg.Color.b c in
  Gg.Color.v x x x (Gg.Color.a c)

let[@inline] cmyk_of_rgb c =
  let r, g, b = Gg.Color.(r c, g c, b c) in (* ∈ [0,1] *)
  let k' = max r (max g b) in
  let k = 1. -. k' in
  let c = (1. -. r -. k) /. k' in
  let m = (1. -. g -. k) /. k' in
  let y = (1. -. b -. k) /. k' in
  Gg.V4.v c m y k


let hue_pct h =
  (* h ∈ [0, 1[ instead of the usual h ∈ [0, 360[. *)
  let f, hi = modf (abs_float h *. 6.) in
  match mod_float hi 6. with
  | 0. -> Gg.Color.v 1.        f        0.       1. (* alpha = 1 *)
  | 1. -> Gg.Color.v (1. -. f)  1.       0.       1.
  | 2. -> Gg.Color.v 0.        1.       f        1.
  | 3. -> Gg.Color.v 0.        (1. -. f) 1.       1.
  | 4. -> Gg.Color.v f         0.       1.       1.
  | 5. -> Gg.Color.v 1.        0.       (1. -. f) 1.
  | _ -> assert false

let hue h = hue_pct(h *. 360.)

module Gradient = struct
  (** The functions can assume that the float ∈ [0,1]. *)
  type t = { rgba: float -> rgba;
             cmyk: float -> cmyk }
  type two_colors = { l0: float;  c0: float;  h0: float;  a0: float;
                      dl: float;  dc: float;  dh: float;  da: float }

  let pi = 0x1.921fb54442d18p+1
  let two_pi = 0x1.921fb54442d18p+2

  let from_2_colors c0 c1 =
    let open Gg in
    let lch0 = Color.to_lch_ab c0 in
    let lch1 = Color.to_lch_ab c1 in
    let dl = V4.x lch1 -. V4.x lch0 in
    let dc = V4.y lch1 -. V4.y lch0 in
    let h0 = V4.z lch0 in
    let h1 = V4.z lch1 in
    let dh = if h1 > h0 && h1 -. h0 > pi then h1 -. (h0 +. two_pi)
             else if h1 < h0 && h0 -. h1 > pi then h1 +. two_pi -. h0
             else h1 -. h0 in
    let da = V4.w lch1 -. V4.w lch0 in
    { l0 = V4.x lch0;  c0 = V4.y lch0;  h0;  a0 = V4.w lch0;
      dl; dh; dc; da }

  let rgba_unsafe g t =
    let lch = Gg.V4.v (g.l0 +. t *. g.dl) (g.c0 +. t *. g.dc)
                (g.h0 +. t *. g.dh) (g.a0 +. t *. g.da) in
    Gg.Color.(clamp(of_lch_ab lch))

  let v c0 c1 =
    let g = from_2_colors c0 c1 in
    let rgba = rgba_unsafe g in
    let cmyk t = cmyk_of_rgb(rgba t) in
    { rgba; cmyk }

  let rgba g t =
    let t = if t < 0. then 0. else if t > 1. then 1. else t in
    g.rgba t

  let cmyk g t =
    let t = if t < 0. then 0. else if t > 1. then 1. else t in
    g.cmyk t
end

(* FIXME: generate color ranges between arbitrary colors.  *)
let range ?grad ~n a b =
  let d1 = 1. /. float(n - 1) in
  let dx = (b -. a) *. d1 in
  let l = ref [] in
  let color = match grad with None -> hue_pct
                            | Some g -> g.Gradient.rgba in
  for i = n - 1 downto 0 do
    l := (a +. float i *. dx,  color(float i *. d1)) :: !l
  done;
  !l

let with_colors ?grad l =
  let n = float(List.length l) in
  let color = match grad with None -> hue_pct
                            | Some g -> g.Gradient.rgba in
  List.mapi (fun i a -> (a, color (float i /. n))) l



module Palette = struct
  include Palette_t
  include Palettes (* Generated by make_brewer.ml *)
  (* The important types are defined in the included module. *)

  let length m = m.length
  let ty m = m.ty
  let blind m = m.blind
  let print m = m.print
  let copy m = m.copy
  let lcd m = m.screen

  let rgb m =
    Array.to_list m.rgb (* do not allow modifying the array *)

  let get_rgb m i =
    if i < 0 || i > m.length then invalid_arg "Color_brewery.Palette.get_rgb";
    m.rgb.(i)

  let cmyk m = Array.to_list m.cmyk

  let get_cmyk m i =
    if i < 0 || i > m.length then invalid_arg "Color_brewery.Palette.get_cmyk";
    m.cmyk.(i)

  let unsafe_gradient_truncate colors t =
    let n = Array.length colors in
    let i = truncate (t *. float n) in
    if i < n then colors.(i) else colors.(n - 1)

  let interpolate_palette colors =
    Array.init (Array.length colors - 1) (fun i ->
        Gradient.from_2_colors colors.(i) colors.(i+1))

  let gradient_interpolate interp t =
    let n = Array.length interp in
    let t', i = modf (t *. float n) in
    let i = truncate i in
    if i < n then Gradient.rgba_unsafe interp.(i) t'
    else Gradient.rgba_unsafe interp.(n - 1) 1.

  let gradient ?(interpolate=false) m =
    if interpolate then
      let interp = interpolate_palette m.rgb in
      let rgba t = gradient_interpolate interp t in
      let cmyk t = cmyk_of_rgb(rgba t) in
      { Gradient.rgba = rgba;  Gradient.cmyk = cmyk }
    else
      { Gradient.rgba = unsafe_gradient_truncate m.rgb;
        Gradient.cmyk = unsafe_gradient_truncate m.cmyk }

  let satisfy specified prop =
    match specified with
    | `Yes -> (match prop with
               | `Yes -> true
               | `No | `Maybe -> false)
    | `Maybe -> (match prop with
                 | `Yes | `Maybe -> true
                 | `No -> false)
    | `No -> true

  let find ?ty ?(blind = `No) ?(print = `No) ?(copy = `No)
        ?(lcd = `No) length =
    if length <= 0 then []
    else (
      let is_of_type = match ty with
        | Some ty -> (fun m -> m.ty = ty)
        | None -> (fun _ -> true) in
      let filter m =
        m.length >= length
        && is_of_type m
        && satisfy blind m.blind
        && satisfy print m.print
        && satisfy copy m.copy
        && satisfy lcd m.screen in
      List.filter filter all_maps
    )
end

(** {2 Color blindness}  *)

(* http://vision.psychol.cam.ac.uk/jdmollon/papers/colourmaps.pdf
   https://www.mapbox.com/blog/colorblind-simulation/
   http://colororacle.org/ — https://github.com/nvkelso/colora-oracle-java
 *)

;;
OCaml

Innovation. Community. Security.