/
color.clj
1448 lines (1038 loc) · 57.3 KB
/
color.clj
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
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
^{:nextjournal.clerk/visibility :hide-ns
:nextjournal.clerk/toc true}
(ns notebooks.color
(:require
[clerk.styles :refer [color-styles 🎨]]
[clojure2d.color :as c]
[clojure2d.color.blend :as bl]
[clojure2d.color.cssgram :as cssgram]
[clojure2d.core :as c2d]
[clojure2d.extra.utils :as utils]
[clojure2d.pixels :as pixels]
[fastmath.core :as m]
[fastmath.random :as r]
[fastmath.vector :as v]
[fastmath.interpolation]
[nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as viewer]))
^{::clerk/viewer :html ::clerk/visibility :hide ::clerk/no-cache true} color-styles
^{::clerk/visibility :hide
::clerk/viewer :hide-result}
(clerk/add-viewers!
(conj viewer/default-viewers
{:name :block
:render-fn '#(v/html (into [:div.flex.flex-col] (v/inspect-children %2) %1))}))
^{::clerk/visibility :hide
::clerk/viewer :hide-result}
(defn update-child-viewers [viewer f]
(update viewer :transform-fn (fn [trfn] (comp #(update % :nextjournal/viewers f) trfn))))
^{::clerk/visibility :hide
::clerk/viewer :hide-result}
(def unpaginated-table
(update-child-viewers viewer/table-viewer (fn [vs] (viewer/update-viewers vs {:page-size #(dissoc % :page-size)}))))
;; `clojure2d.color` namespace for Clojure is a rich collection of functions for various color, palettes and grandents manipulations. Including:
;;
;; * various color representations
;; * color spaces coversions
;; * palettes and gradients
;; * distances
;; * blending
;; Note: Only CIE standard illuminant D65, 2° observer is supported
;; I use `🎨` internal function to render given color, palette or gradient.
;; # Intro
;; There are three entities we will deal with:
;; * `color` - representation of the single color with 3 color information channels and additional alpha channel (4 in total).
^{::clerk/visibility :hide}
(clerk/example
(🎨 :darkcyan)
(🎨 (c/color :cyan)))
;; * `palette` - vector of colors
(🎨 [:docc/dark-tyrian-blue :blue [200 200 255] "#f" (c/set-alpha "#a" 100)])
(🎨 (c/palette [:green :lime :yellow]))
;; * `gradient` - continuous interpolation between colors, returning a color for given value from `0.0` to `1.0`
(def yb-gradient (c/gradient [:yellow :blue]))
(yb-gradient 0.3)
(🎨 yb-gradient)
;; # Color
;; At the beginning let's start with a `color`. A color is always a vector containing 3 channels from given color space plus one additional to describe opacity, ie. alpha channel.
;;
;; By default Java and WWW apps interpret a color as a *sRGB*. In `clojure2d.color` value of each channel in *sRGB* color space is from `[0.0-255.0]` range. Other color spaces defined here can operate on different ranges. More about this later.
;; ## Color representation
;; Internally a color is represented as `fastmath.vector.Vec4` type but we can use any other representation which eventually is coerced to a `Vec4`. Let's look at all of the possible options.
;; ### Named color
;; The quickest option is to use a keyword as a color name, there are two groups of names.
;;
;; The full list of names can be found [here](https://clojure2d.github.io/clojure2d/docs/static/colors.html) or retrieved by calling:
(sort (c/named-colors-list))
;; #### HTML color names
;; All basic and extended colors used by modern browsers as a lower-case keyword. Note that some colors can have two similar names. For example `:darkblue` and `:dark-blue` represent the same color.
(🎨 :darkblue :dark-blue)
;; #### Dictionary of Colour Combinations
;; This is the list of colors compiled in the book ["A Dictionary of Colour Combinations"](https://en.seigensha.com/books/978-4-86152-247-5/) by Sanzo Wada, digitalized by [Dain M. Blodorn Kim](https://github.com/dblodorn/sanzo-wada/) and eventually corrected and published by [Matt DesLauriers](https://github.com/mattdesl/dictionary-of-colour-combinations).
;;
;; Colors from this resource are prefixed by a `:docc/` namespace.
(🎨 :docc/burnt-sienna :docc/peacock-blue :docc/light-pinkish-cinnamon)
;; ### CSS string
;; The other representation is based on CSS. A hexadecimal string in one of the following forms: `#X`, `#XX`, `#RGB`, `#RRGGBB` or `#RRGGBBAA`. Where `X` (`[0-F]`) or `XX` (`[00-FF]`) are the grey values. The hash character is optional, lower case also can be used.
(🎨 "#7" "#4a" "#FA3" "#FFAA33AA" "f1a130")
;; To convert any color to CSS representation call:
(clerk/table
(map (juxt identity c/format-hex) [:red 0xaa112299 [123 44 55]
java.awt.Color/YELLOW
"ab"]))
;; ### Integer
;; Any integer (32bit number) is interpreted as a color, where 8 most significant bits represent alpha channel, next 8 bits, first channel, and so on: `0xAARRGGBB`
(🎨 0x91aa0000 ; with alpha set to 0x91
255
-256)
;; Note that 0x00rrggbb is the same as 0xffrrggbb, so you can't represent fully transparent color with an integer.
[(c/color 0x123456)
(c/color 0x00123456)
(c/color 0xff123456)]
;; conversion to an integer is possible with `pack` function
(c/pack [22 33 44 55])
;; ### Any `Sequable`
;; Any Clojure `Sequable` is coerced to a color with the following rules. Color channel value can be any number type (int or double). Numbers are rounded to the nearest integer and clamped to have [0-255] range.
;;
;; Rules are:
;;
;; * 1-element sequence is treated as a `grey`
;; * 2-element sequence is a `gray` with `alpha`
;; * 3-element sequence is `red`, `green` and `blue`
;; Finally, 4+-element sequence is treated as *RGBA*, any additional sequence elements are ignored
(🎨 [129]
(list 140.11 255/3)
(seq (java.util.ArrayList. [240 220 20]))
[129 190 222 230 1 2 3 4])
;; ### Fastmath vectors
;; Fastmath vectors `Vec2`, `Vec3` and `Vec4` are treated as a `Sequable`
(🎨 (v/vec2 12 200)
(v/vec3 12 99 200)
(v/vec4 12 99 122 200))
;; ### AWT Color
;; For interoperability we can use also AWT color (`java.awt.Color` class).
(🎨 (java.awt.Color. 120 33 99)
(java.awt.Color. 0.9 0.2 0.4)
(java.awt.Color/PINK))
;; ### Coercions
;; Any color representation is implicitely coerced to a `fastmath.vector.Vec4` type. You can make explicit coercion with `to-color` and `color` function. The latter can construct a color from channel values. Additionally `gray` function returns gray color (with optional alpha)
(class (c/to-color :green))
(map c/to-color [:red 0xffaa33 "fa4" [1 2 3 4]])
(🎨 (c/color :red 200)
(c/color 12.0 33.0 99.0)
(c/color 12.0 33.0 99.0 200.0)
(c/gray 99.0))
;; You can also convert to AWT Color, integer and - as previously mentioned - to a CSS Color
^{::clerk/viewer :block}
[(c/awt-color :pink)
(c/awt-gray 123.0)
(c/format-hex :pink)
(c/pack :pink)]
;; For interop with `quil` just call `c/quil` (which is the same to `pack` and converts a color to a ARGB integer. RGB Color defined by `quil/color` will work in `Clojure2d` without any conversion.
(c/quil :pink)
;; ### Representations
^{::clerk/visibility :hide ::clerk/viewer unpaginated-table}
{:head [:input :output]
:rows [[:darkgreen (🎨 :darkgreen)]
[:docc/green-blue (🎨 :docc/green-blue)]
['(c/color 127) (🎨 (c/color 127))]
['(c/color 127 100) (🎨 (c/color 127 100))]
['(c/color 33 44 55) (🎨 (c/color 33.0 44.0 55.0))]
['(c/color 33 44 55 200) (🎨 (c/color 33.0 44.0 55.0 200.0))]
['(c/gray 100) (🎨 (c/gray 100.0))]
['(c/gray 100 200) (🎨 (c/gray 100.0 200.0))]
["\"#a\"" (🎨 "#a")]
["\"ae\"" (🎨 "ae")]
["\"#3AF\"" (🎨 "#3AF")]
["\"34e9a3\"" (🎨 "34e9a3")]
["\"34A3e999\"" (🎨 "34A3e999")]
["0xa34556" (🎨 0xa34556)]
["0xAAa35645" (🎨 0xAAa35645)]
[[127] (🎨 [127])]
['(127 200) (🎨 '(127 200))]
['(double-array [33 44 122]) (🎨 (double-array [33 44 122]))]
['(int-array [33 44 99 122]) (🎨 (int-array [33 44 99 122]))]
['(v/vec2 33 144) (🎨 (v/vec2 33 144))]
['(v/vec3 22 33 144) (🎨 (v/vec3 22 33 144))]
['(v/vec4 11 22 33 144) (🎨 (v/vec4 11 22 33 144))]
['java.awt.Color/PINK (🎨 java.awt.Color/PINK)]
['(c/format-hex :pink) (🎨 (c/format-hex :pink))]
['(c/pack :pink) (🎨 (c/pack :pink))]
['(c/from-HSB [300 0.5 0.5]) (🎨 (c/from-HSB [300 0.5 0.5]))]
['(c/from-LAB [50 50 50]) (🎨 (c/from-LAB [50 50 50]))]]}
;; ### Color validation
;; To check if color is valid or invalid. You can use two functions: `possible-color?` and `valid-color?`. Former function uses some logic to determine if the imput is color or not, Latter one, just tries to coerce to a color and catches exception when it's not (returning `false` in such case).
[(c/possible-color? [11 22 33]) (c/valid-color? [11 22 33])]
[(c/possible-color? [:a]) (c/valid-color? [:a])]
;; There are also tests if color is black or not
(c/black? :black)
(c/not-black? :black)
;; ## Accessing channels
;; ### Setting channels
;; To modify given channel of the color use one of the following:
;;
;; * `set-red` or `set-ch0` - to modify first channel
;; * `set-green` or `set-ch1`- to modify second channel
;; * `set-blue` or `set-ch2` - to modify third one
;; * `set-alpha` or call `(color any-color alpha)` - to modify alpha channel
;; * `set-channel` - sets selected channel (optionally for given color space)
(🎨 (c/set-green :red 200)
(c/set-alpha :red 200)
(c/color :red 200)
(c/set-channel :red :HSL 0 300.0))
;; ### Getting channels
;; To access given channel call:
;; * `red` or `ch0` - to get first channel
;; * `green` or `ch1` - to get second channel
;; * `blue` or `ch2` - to get third channel
;; * `alpha` - to get alpha channel
;; * `get-channel` - to get selected channel (optionally for given color space)
(🎨 :docc/fawn)
((juxt c/red c/green c/blue c/alpha) :docc/fawn)
(c/get-channel :red :HSL 2)
;; #### Getting Luma and Hue
;; There is a set of functions returning additional color information, these are:
;; * `luma` - information about luma (brightness) of the color, `[0.0-255.0]`
;; * `relative-luma` - luma from linear RGB, `[0.0-255.0]`
;; * `hue` - hue value, hexagon projection, `[0.0-360.0]`
;; * `hue-polar` - hue value, polar projection, `[0.0-360.0]`,
;; * `hue-paletton` - hue value, as it is used in [paletton](https://paletton.com/), `[0.0-360.0]`
(🎨 :docc/buffy-citrine)
((juxt c/luma c/relative-luma c/hue c/hue-polar c/hue-paletton) :docc/buffy-citrine)
;; #### Clamping
;; To ensure our *sRGB* color is within the range we can use `clamp` which constrains values to a `[0.0-255.0]` range and `lclamp` which also round channel values to the nearest integer.
(🎨 (c/clamp [-23.3 123.033 293.33])
(c/lclamp [-23.3 123.033 293.33]))
;; ## Color comparison and distances
;; This part describes a number of color comparison and distance/difference functions. Most of them are based mainly on CIE delta-E* definitions:
;; * `delta-E*` - ${\Delta}E^*_{ab}$, CIE76 difference, euclidean distance in LAB color space
;; * `delta-C*` - ${\Delta}C^*_{ab}$, chroma (color) difference in LAB color space, CIE76
;; * `delta-H*` - ${\Delta}H^*_{ab}$, hue difference in LAB color space, CIE76
;; * `delta-E*-94` - ${\Delta}E^*_{94}$, CIE94 difference
;; * `delta-E*-2000` - ${\Delta}E^*_{00}$, CIEDE2000 difference, three optional weights can be provided, `l` for luma, `c` for color intensity and `h` for hue. By default they are set to `1.0`.
;; * `delta-E*-CMC` - ${\Delta}E^*_{CMC}$, CMC l:c 1984 difference, two optional weights can be provided, `l` for luma and `c` for color intensity. By default they are set to `1.0`.
;; * `delta-E-z` - ${\Delta}E_{z}$, difference in JzAzBz color space
;; * `delta-E-HyAB` - ${\Delta}E_{HyAB}$, hybrid difference in LAB color space
;; * `delta-E*-euclidean` - general, euclidean, distance in selected color space (`Oklab` by default)
;; * `delta-C-RGB` - ${\Delta}C$, difference in RGB color space ("redmean")
;; * `delta-D-HCL` - ${\Delta}D_{HCL}$, difference in HCL (Sarifuddin and Missaou) color space
;; Difference between `:yellow` :and `:blue`
^{::clerk/visibility :hide}
(clerk/table
{:head [:delta :value]
:rows [['(c/delta-E* :yellow :blue) (c/delta-E* :yellow :blue)]
['(c/delta-C* :yellow :blue) (c/delta-C* :yellow :blue)]
['(c/delta-H* :yellow :blue) (c/delta-H* :yellow :blue)]
['(c/delta-E*-94 :yellow :blue) (c/delta-E*-94 :yellow :blue)]
['(c/delta-E*-2000 :yellow :blue) (c/delta-E*-2000 :yellow :blue)]
['(c/delta-E*-2000 :yellow :blue 2.0 1.0 1.0) (c/delta-E*-2000 :yellow :blue 2.0 1.0 1.0)]
['(c/delta-E*-CMC :yellow :blue) (c/delta-E*-CMC :yellow :blue)]
['(c/delta-E*-CMC :yellow :blue 2.0 1.0) (c/delta-E*-CMC :yellow :blue 2.0 1.0)]
['(c/delta-E-z :yellow :blue) (c/delta-E-z :yellow :blue)]
['(c/delta-E-HyAB :yellow :blue) (c/delta-E-HyAB :yellow :blue)]
['(c/delta-E*-euclidean :yellow :blue) (c/delta-E*-euclidean :yellow :blue)]
['(c/delta-E*-euclidean :yellow :blue :YUV) (c/delta-E*-euclidean :yellow :blue :YUV)]
['(c/delta-C-RGB :yellow :blue) (c/delta-C-RGB :yellow :blue)]
['(c/delta-D-HCL :yellow :blue) (c/delta-D-HCL :yellow :blue)]]})
;; There are two more measures: `contrast-ratio` from WCAG (the value below 3.0 means that contrast is too low) and `noticable-different?` to check if colors are visually different, as described in this [paper](https://research.tableau.com/sites/default/files/2014CIC_48_Stone_v3.pdf).
[(c/contrast-ratio :yellow :blue)
(c/contrast-ratio :yellow :lightyellow)]
;; Let's draw a chart showing how `contrast-ratio` values change when we compare given color to grays.
(defn get-series
[reference]
(map (fn [g]
{:gray g
:contrast-ratio (c/contrast-ratio reference (c/gray g))
:reference reference}) (range 256)))
(clerk/vl {:data {:values (concat (get-series :black)
(get-series :white)
(get-series :red)
(get-series :green)
(get-series :blue)
(get-series :yellowgreen))}
:width 600
:height 300
:usermeta {:embedOptions {:renderer "svg"}}
:mark {:type :line
:tooltip {:field :contrast-ratio}}
:encoding {:x {:field :gray :type :quantitative}
:y {:field :contrast-ratio :type :quantitative}
:color {:field :reference :type :nominal
:scale {:range [:black :blue :green
:red :lightgray :yellowgreen]}}}})
;; To check if two colors are visually different we can call `noticable-different?` function.
[(c/noticable-different? :yellow :blue)
(c/noticable-different? :yellow :lightyellow)
(c/noticable-different? (c/gray 245.0) (c/gray 235.0))]
;; ## Color space conversions
;; Color can be converted to and from various of different than `sRGB` color spaces. Full list of possible color spaces is defined under `colorspaces-list` variable.
c/colorspaces-list
;; All color space converters are defined as a functions with names `to-XXX` and `from-XXX`, where `XXX` is the name of the color space. All color spaces a fully reversible, which means that converting to and from given color space yields approximately initial color.
(c/from-YDbDr (c/to-YDbDr [100 200 43]))
;; Due to the fact, that each color space has its own ranges for each channel, there are normalized versions of converters. The names of the functions end with the `*`. Normalization is linear in most cases (exception: `OSA`)
(c/to-LAB :yellow)
(c/to-LAB* :yellow)
;; Normalized color spaces are also reversible:
(c/from-LUV* (c/to-LUV* [100 200 43]))
;; Let's visualize how set of random `sRGB` colors maps to `JCH` and `LAB` color spaces.
(defn get-cs-points
[cs labels]
(let [[lx ly lz] labels
[to] (c/colorspaces cs)
colors (repeatedly 2000 #(c/color (r/drand 255.0) (r/drand 255.0) (r/drand 255.0)))
[x y z] (apply map vector (map to colors))
f #(m/approx % 3)]
{:data [{:x (map f x) :y (map f y) :z (map f z)
:type :scatter3d
:mode :markers
:marker {:color (map c/format-hex colors)}}]
:layout {:scene {:xaxis {:title {:text lx}}
:yaxis {:title {:text ly}}
:zaxis {:title {:text lz}}}
:height 700
:width 700}}))
(clerk/plotly (get-cs-points :JCH "JCH"))
(clerk/plotly (get-cs-points :LAB "LAB"))
;; ### Color space ranges
^{::clerk/visibility :hide ::clerk/viewer unpaginated-table}
{:head ["color space" "channel 1" "channel 2" "channel 3" "comment"]
:rows (sort-by first [[:CMY [0.0 255.0] [0.0 255.0] [0.0 255.0] ""]
[:Cubehelix [0.0 360.0] [0.0 4.61] [0.0 1.0] ""]
[:GLHS [0.0 1.0] [0.0 360.0] [0.0 1.0] "min=0.2, mid=0.1, max=0.7"]
[:Gray [0.0 255.0] [0.0 255.0] [0.0 255.0] ""]
[:HCL [-180.0 180.0] [0.0 170.0] [0.0 135.27] "Sarifuddin/Missaou, λ=3"]
[:HSB [0.0 360.0] [0.0 1.0] [0.0 1.0] "same as HSV"]
[:HSI [0.0 360.0] [0.0 1.0] [0.0 1.0] ""]
[:HSL [0.0 360.0] [0.0 1.0] [0.0 1.0] ""]
[:HSV [0.0 360.0] [0.0 1.0] [0.0 1.0] "same as HSB"]
[:HWB [0.0 360.0] [0.0 1.0] [0.0 1.0] ""]
[:HunterLAB [0.0 100.0] [-69.08 109.46] [-199.78 55.72] ""]
[:IPT [0.0 1.0] [-0.45 0.66] [-0.75 0.65] ""]
[:IgPgTg [0.0 0.97] [-0.35 0.39] [-0.41 0.44] ""]
[:JAB [0.0 0.17] [-0.09 0.11] [-0.16 0.12] "JzAzBz, white point=100"]
[:JCH [0.0 0.17] [0.0 0.16] [0.0 360.0] "polar JzAzBz"]
[:LAB [0.0 100.0] [-86.18 98.23] [-107.86 94.48] "D65"]
[:LCH [0.0 100.0] [0.0 133.81] [0.0 360.0] "polar LAB"]
[:LMS [0.0 100.0] [0.0 100.0] [0.0 100.0] ""]
[:LUV [0.0 100.0] [-83.08 175.02] [-134.1 107.4] ""]
[:OHTA [0.0 255.0] [-127.5 127.5] [-127.5 127.5] ""]
[:OSA [-13.51 7.14] [-20.0 20.0] [-23.0 23.0] "excluded extreme values"]
[:Oklab [0.0 1.0] [-0.23 0.28] [-0.31 0.2] ""]
[:Oklch [0.0 1.0] [0.0 0.32] [0.0 360.0] "polar Oklab"]
[:Okhsv [0.0 1.0] [0.0 1.0] [0.0 1.0] ""]
[:Okhwb [0.0 1.0] [0.0 1.0] [0.0 1.0] ""]
[:Okhsl [0.0 1.0] [0.0 1.0] [0.0 1.0] ""]
[:PalettonHSV [0.0 360.0] [0.0 2.0] [0.0 2.0] ""]
[:RYB [0.0 255.0] [0.0 255.0] [0.0 255.0] "Sugita/Takahashi"]
[:XYB [-0.02 0.03] [0.0 0.85] [0.0 0.85] "from JPEG XL"]
[:XYZ [0.0 95.47] [0.0 100.0] [0.0 108.88] "D65"]
[:XYZ1 [0.0 0.9547] [0.0 1.0] [0.0 1.0888] "D65"]
[:UCS [0.0 0.63] [0.0 1.0] [0.0 1.57] ""]
[:UVW [-82.15 171.81] [-87.16 70.82] [-17.0 99.0] ""]
[:YCbCr [0.0 255.0] [-127.5 127.5] [-127.5 127.5] ""]
[:YCgCo [0.0 255.0] [-127.5 127.5] [-127.5 127.5] ""]
[:YDbDr [0.0 255.0] [-340.0 340.0] [-340.0 340.0] ""]
[:YIQ [0.0 255.0] [-151.9 151.9] [-133.26 133.26] ""]
[:YPbPr [0.0 255.0] [-236.59 236.59] [-200.79 200.79] ""]
[:YUV [0.0 255.0] [-111.18 111.18] [-156.83 156.83] ""]
[:Yxy [0.0 100.0] [0.15 0.64] [0.06 0.6] "CIE xyY"]
[:DIN99 [0.0 100.0] [-27.45 36.18] [-33.4 31.2] ""]
[:DIN99b [0.0 100.0] [-40.11 45.52] [-40.5 44.37] ""]
[:DIN99o [0.0 100.0] [-35.43 48.88] [-50.3 45.9] "from Wikipedia"]
[:DIN99c [0.0 100.0] [-38.44 43.67] [-40.8 43.6] ""]
[:DIN99d [0.0 100.0] [-37.35 42.32] [-41.0 43.0] ""]])}
;; ### Polar coordinates
;; For every luma based color space you can create polar representation with `to-luma-color-hue` or `from-luma-color-hue`. Some of the colorspace are already predefined.
;; The following two are the same:
(c/to-luma-color-hue c/to-LUV [20 30 100])
(c/to-LCHuv [20 30 100])
;; Let's make LCH out of YUV.
(def yuv-polar (c/to-luma-color-hue c/to-YUV [20 30 100]))
(c/from-luma-color-hue c/from-YUV yuv-polar)
;; You can create conversion pair by calling `make-LCH` function.
(let [[to from] (c/make-LCH :YUV)]
[(to [20 30 100])
(from (to [20 30 100]))])
;; ### sRGB vs linear RGB
;; By default all colors are interpreted as `sRGB`. To convert to `linear RGB` call `to-linearRGB` or `from-sRGB`.
(c/to-linearRGB [124.0 125.0 254.0])
(c/from-linearRGB [51.4 52.3 252.7])
;; ### Color converter
;; You can build a color converter similar to `colorMode` in Processing/Quil. `color-converter` function returns a function which converts from given color space to `sRGB` using a channel values ranging from `0.0` to a scale (`255.0` by default) parameter. Possible options are:
;; * `(color-converter :HSB)` - returns a function converting from `HSB` color space with all channels are set to range from `0.0` to `255.0`
;; * `(color-converter :HSB 100.0)` - as above with input channels scaled between `0.0` to `100.0`
;; * `(color-converter :HSB 10.0 20.0 30.0)` - each color channel is scaled respectively to a maximum: `10`, `20` and `30`. Alpha channel is scaled from `0.0` to `255.0`
;; * `(color-converter :HSB 10 20 30 100)` - as above, with alpha scaled to a maximum `100`.
;; Let's create converter from `HSB` color space which accepts all parameters from the range `[0.0 100.0]`.
(def from-HSB-100 (c/color-converter :HSB 100.0))
(🎨 (c/from-HSB [180.0 0.5 0.5])
(c/from-HSB* [127.5 127.5 127.5])
(from-HSB-100 [50.0 50.0 50.0]))
;; Similarly, let's create converter from `LCH` where `L` channel is from the range `[0.0 1.0]`,`C` from the range `[0.0 2.0]`, `H` from the range `[0.0 3.0]` and alpha is from `0.0` to `100.0`.
(def from-LCH-123 (c/color-converter :LCH 1 2 3 100))
(🎨 (c/from-LCH [55 90 40 205])
(from-LCH-123 [0.55 1.345 1/3 80.4]))
;; ## Random color generation
;; To generate random color, call `random-color`. You can optionally select a color theme (thi.ng or paletton presets). List of color schemes:
c/color-themes
(🎨 (sort-by c/hue (repeatedly 15 c/random-color))
(sort-by c/hue (repeatedly 15 #(c/random-color :warm (r/drand 100 255))))
(sort-by c/hue (repeatedly 15 #(c/random-color :pastels-dark))))
;; ### Applying theme
;; Sometimes you may want to generate random color similar to a given one. For that you can call `apply-theme` functions. Function accepts source color and color theme as a name or scheme vector.
;; #### Color theme by name
(🎨 :docc/green-blue
(repeatedly 15 #(c/apply-theme :docc/green-blue :warm))
(repeatedly 15 #(c/apply-theme :docc/green-blue :pastels-bright))
(repeatedly 15 #(c/apply-theme :docc/green-blue :fresh)))
;; #### Color theme by scheme
;; Scheme vector consists 3 values, one for each LCH channel. If a value is:
;; * a 2-element vector, channel value is selected randomly from a given range
;; * a number, channel value is selected randomly from [channel-number, channe+number] range
;; * other values just leave original channel value
;; Operations are done in LCH* (0-255) color space
(🎨 :docc/yellow-ocher
;; hue is selectod randomly from a values +-30 around source one
(repeatedly 15 #(c/apply-theme :docc/yellow-ocher [:keep :keep 30]))
;; hue is kept but saturation (C) is selected from 220 to 255 range
(repeatedly 15 #(c/apply-theme :docc/yellow-ocher [:keep [10 255] :keep]))
;; additionally let's vary luma
(repeatedly 15 #(c/apply-theme :docc/yellow-ocher [50.0 [200 255] :keep])))
;; # Palettes
;; Palette is a vector of colors. It can be constructed by hand, by name, created from hue, gradient, image or from other palette.
(🎨 [:maroon 0xbba3b4f5 :docc/olive [112 33 200]])
;; ## Names
;; `clojure2d.color` defines plenty of ready to use palettes gathered from many sources and other libraries, Colourlovers palettes, thi.ng, dictionary of colour combinations, R Paletteer package (collection), cpt-city - an archive of colour gradients, ColorBrewer and so on.
;; The full list of names can be found [here](https://clojure2d.github.io/clojure2d/docs/static/palettes/index.html).
;; To create palette from name, call `palette` function with a number or keyword as a name. Call without an argument to select random name and hence the palette.
(🎨 (c/palette))
;; Simple keywords as a names consist ColorBrewer, Wes Anderson, common charting, and some other basic palettes
^{::clerk/visibility :hide}
(clerk/table
[[:accent (🎨 (c/palette :accent))]
[:category10 (🎨 (c/palette :category10))]
[:microsoft-2 (🎨 (c/palette :microsoft-2))]
[:grand-budapest-1 (🎨 (c/palette :grand-budapest-1))]
[:oranges-5 (🎨 (c/palette :oranges-5))]])
;; ### [Colourlovers](https://www.colourlovers.com/palettes/most-loved/all-time/meta)
;; The best 500 palettes. The name is an integer from `0` to `499`
(🎨 (c/palette 0)
(c/palette 499))
^{::clerk/visibility :hide}
(clerk/table
[[1 (🎨 (c/palette 1))]
[10 (🎨 (c/palette 10))]
[50 (🎨 (c/palette 50))]
[100 (🎨 (c/palette 100))]
[200 (🎨 (c/palette 200))]
[400 (🎨 (c/palette 400))]])
;; ### [thi.ng](https://github.com/thi-ng/umbrella/tree/develop/packages/color-palettes)
;; Set of palettes defined in thi.ng umbrella, in `color-palettes` package.
(🎨 (c/palette :thi.ng/ORLwKeosxtEeZxq))
^{::clerk/visibility :hide}
(clerk/table
[[:thi.ng/OkEXVdMQmQ1oQTp (🎨 (c/palette :thi.ng/OkEXVdMQmQ1oQTp))]
[:thi.ng/QLj3F8heV6QT4YG (🎨 (c/palette :thi.ng/QLj3F8heV6QT4YG))]
[:thi.ng/qAPJgQvoDkRkQTN (🎨 (c/palette :thi.ng/qAPJgQvoDkRkQTN))]
[:thi.ng/bYcivY8Jqx8nsiR (🎨 (c/palette :thi.ng/bYcivY8Jqx8nsiR))]
[:thi.ng/sz5Uxo4ByGDH6tQ (🎨 (c/palette :thi.ng/sz5Uxo4ByGDH6tQ))]])
;; ### [DOCC](https://github.com/mattdesl/dictionary-of-colour-combinations)
;; 348 palettes from dictionary of color combinations.
(🎨 (c/palette :docc/docc-1)
(c/palette :docc/docc-348))
^{::clerk/visibility :hide}
(clerk/table
[[:docc/docc-1 (🎨 (c/palette :docc/docc-2))]
[:docc/docc-10 (🎨 (c/palette :docc/docc-10))]
[:docc/docc-100 (🎨 (c/palette :docc/docc-100))]
[:docc/docc-200 (🎨 (c/palette :docc/docc-200))]
[:docc/docc-300 (🎨 (c/palette :docc/docc-300))]])
;; ### [Paletteer](https://github.com/EmilHvitfeldt/paletteer)
;; Collection of palettes defined in various R packages.
^{::clerk/visibility :hide}
(clerk/table
[[:nord/frost (🎨 (c/palette :nord/frost))]
[:ghibli/TotoroLight (🎨 (c/palette :ghibli/TotoroLight))]
[:cartography/red.pal-9 (🎨 (c/palette :cartography/red.pal-9))]
[:dutchmasters/anatomy (🎨 (c/palette :dutchmasters/anatomy))]
[:palettetown/abra (🎨 (c/palette :palettetown/abra))]])
;; ### [CPT City](http://soliton.vm.bytemark.co.uk/pub/cpt-city/index.html)
;; Collection of palettes from CPT-City, c3g discrete files.
^{::clerk/visibility :hide}
(clerk/table
[[:arendal/temperature (🎨 (c/palette :arendal/temperature))]
[:cl/fs2010 (🎨 (c/palette :cl/fs2010))]
[:os/os250k-metres (🎨 (c/palette :os/os250k-metres))]
[:jjg_neo10_liht/frozen-in-time (🎨 (c/palette :jjg_neo10_liht/frozen-in-time))]
[:heine/Exxon88 (🎨 (c/palette :heine/Exxon88))]])
;; ## [Paletton](https://paletton.com/)
;; Paletton is a great web tool to generate palettes based on hue. `paletton` function recreates the method used on the web and returns a palette. Possible options are:
;; * type of the palette: `:monochromatic`, `:triad` and `:tetrad`
;; * hue, an angle from 0 to 360 degrees.
;; * complementary color
;; * adjacent colors angle
;; * a color scheme
;; To generate [these colors](https://paletton.com/#uid=60k0u0kwi++bu++hX++++rd++kX), you should call:
(🎨 (c/paletton :triad 20 {:preset :shiny :compl true :adj true :angle 30}))
;; Other examples
(🎨 (c/paletton :monochromatic 200 {:preset :pale-light :compl false})
(c/paletton :monochromatic 200 {:preset :pale-light :compl true})
;; adjacent colors are taken from complementary color
(c/paletton :triad 200 {:preset :pale-light :compl false :adj false})
(c/paletton :triad 200 {:preset :pale-light :compl true :adj false})
;; adjacent colors are taken from base color
(c/paletton :triad 200 {:preset :pale-light :compl false :adj true})
(c/paletton :triad 200 {:preset :pale-light :compl true :adj true})
(c/paletton :tetrad 200 {:preset :pale-light})
(c/paletton :tetrad 200 {:preset :pale-light :angle 120}))
;; ## Resampling
;; Palette can be resampled to get less or more colors. There are two methods one based on sampling gradient created out of palette, second based on k-means clustering.
;; ### Gradient resampling
;; For given palette provide requested number of colors and optionally gradient creation arguments.
(🎨 (c/palette :moonrise-3))
;; Resampled `:moonrise-3`
(apply 🎨 (map (partial c/palette :moonrise-3)
[1 2 3 4 5 6 8 10 12 15]))
;; We can resample in different color space and with monotonic interpolation
(apply 🎨 (map #(c/palette :moonrise-3 %
{:interpolation :monotone :colorspace :JCH})
[1 2 3 4 5 6 8 10 12 15]))
;; We can also resample a gradient
(🎨 (c/gradient :pals/kovesi.cyclic_mrybm_35_75_c68))
(apply 🎨 (map #(c/palette (c/gradient :pals/kovesi.cyclic_mrybm_35_75_c68) %) [1 2 3 5 7 10]))
;; ### k-Means
;; In case you have very big palette or a sequence of colors (possibly an image), you can use `reduce-colors` to construct a smaller palette. The algorithm uses `x-means` and number of colors returned may be lower than requested.
;; Let's start with very long palette
(count (c/palette :viridis))
(apply 🎨 (map (partial c/reduce-colors (c/palette :viridis)) [2 3 5 10 30]))
;; Now let's load image
(def cat-image
(c2d/load-image "https://live.staticflickr.com/3819/11717823153_7be7b26ede_w_d.jpg"))
;; And let's get pixels as a sequence of colors
(def cat-pixels (pixels/to-pixels cat-image))
(apply 🎨 (map (partial c/reduce-colors cat-pixels) [2 3 5 10]))
;; Reduce in `OSA` color space
(apply 🎨 (map #(c/reduce-colors cat-pixels % :OSA) [2 3 5 10]))
;; ### Clarans
;; Instead of providing a color space you can also provide distance function (either own function or one for `fastmath.distance`). For a reduction `clarans` algorithm is used.
;; Warning: it can be very slow.
(require '[fastmath.distance])
(apply 🎨 (map (partial c/reduce-colors (c/palette :viridis) 10)
[c/contrast-ratio
c/delta-E*
c/delta-E-z
fastmath.distance/chebyshev
fastmath.distance/manhattan
fastmath.distance/canberra]))
;; ## Random palette.
;; When you call `random-palette` function you'll get random set of colors. These are selected randomly from preset palettes, gradients and paletton. There are no parameters.
(apply 🎨 (repeatedly 5 c/random-palette))
;; # Gradients
;; Gradient is a continuous function which returns interpolated color, a parameter should be a number from `0.0` to `1.0`. Gradient can be created by name or from palette. Additionally there is a special case for cosinus method of creating gradients (described by Inigo Quilez ,see below).
;; ## Presets
;; There is a big collection of ready to use gradients gathered (similarly to palettes) from other libraries or sites. Full list is available [here](https://clojure2d.github.io/clojure2d/docs/static/gradients/index.html). Some examples:
(🎨 (c/gradient :pals/kovesi.cyclic_mrybm_35_75_c68))
^{::clerk/visibility :hide}
(clerk/table
{:head [:name :gradient]
:rows [[:pals/ocean.thermal (🎨 (c/gradient :pals/ocean.thermal))]
[:grDevices/Inferno (🎨 (c/gradient :grDevices/Inferno))]
[:xkcd/xkcd-bath (🎨 (c/gradient :xkcd/xkcd-bath))]
[:ma_retro2/retro2_01 (🎨 (c/gradient :ma_retro2/retro2_01))]
[:neota_food/carrot (🎨 (c/gradient :neota_food/carrot))]
[:pd_art/art-nouveau-01 (🎨 (c/gradient :pd_art/art-nouveau-01))]
[:es_emerald_dragon/es_emerald_dragon_08 (🎨 (c/gradient :es_emerald_dragon/es_emerald_dragon_08))]]})
;; ## Gradient from palette
;; In case you want to create own gradient out of custom palette, you can use several options how to interpolate between colors.
;; You can select color space conversion, interpolation method and color distribution.
(🎨 (c/gradient (c/palette :prl-6)))
;; Let's play with the following gradient. By default interpolation is linear with evenly distributed colors in RGB color space. List of possible interpolations with documentation is [here](https://generateme.github.io/fastmath/fastmath.interpolation.html)
(🎨 (c/gradient [:white :red :yellow (c/color :blue 160) :lime :black]
{:domain [0.0 0.2 0.4 0.6 0.8 1.0]
:interpolation :linear-smile
:colorspace :RGB}))
;; Let's create custom `loess` interpolator
(defn loess [xs ys] (fastmath.interpolation/loess 0.7 2 xs ys))
^{::clerk/visibility :hide}
(clerk/table
{:head [:options :result]
:rows (map (juxt identity #(🎨 (c/gradient [:white :red :yellow (c/color :blue 160) :lime :black] %)))
[{} {:interpolation :monotone}
{:interpolation :cubic-spline}
{:interpolation :shepard}
{:interpolation loess}
{:colorspace :LAB}
{:colorspace :Oklab}
{:colorspace :JCH}
{:domain [0.0 0.1 0.2 0.7 0.8 1.0]}
{:domain [0.0 0.45 0.47 0.53 0.55 1.0]}])})
;; ## Inigo Quilez gradient generator
;; [Inigo Quilez cosinus method](https://iquilezles.org/www/articles/palettes/palettes.htm) is the way of creating gradients from the following formula:
;; $$c_i(t)=a_i+b_i\cos{[2\pi(c_it+d_i)]}$$
;; $$t\in[0.0,1.0], i\in\{R,G,B\}$$
;; To create a gradient, call `gradient` with `:iq` interpolation. As the first argument you have to provide a,b,c and d coefficient triplets.
(🎨 (let [a-coeffs [0.5 0.5 0.5]
b-coeffs [0.5 0.5 0.5]
c-coeffs [1 0.5 -1]
d-coeffs [0.2 0.3 0.4]]
(c/gradient [a-coeffs b-coeffs c-coeffs d-coeffs] {:interpolation :iq})))
;; All gradients from the aforementioned article are predefined:
^{::clerk/visibility :hide}
(clerk/table
{:head [:name :gradient]
:rows (map (juxt identity #(🎨 (c/gradient %))) (map #(keyword (str "iq-" %)) (range 1 8)))})
;; There is also an option to create gradient from two colors using cosine interpolation. Coefficients are calculated automatically basing on [thi.ng algorithm](https://github.com/thi-ng/color/blob/master/src/gradients.org#gradient-coefficient-calculation).
(🎨
;; compare two interpolation methods, default and cosine
(c/gradient [:red :green])
(c/gradient [:red :green] {:interpolation :iq})
;; yellow-blue
(c/gradient [:yellow :blue])
(c/gradient [:yellow :blue] {:interpolation :iq}))
;; ## Easings
;; Another way of interpolating between two colors is by using easing functions. They are defined and documented [here](https://generateme.github.io/fastmath/fastmath.easings.html).
(🎨 (c/gradient [:red :green] {:interpolation :back-in-out})
(c/gradient [:red :green] {:interpolation :bounce-in-out})
(c/gradient [:red :green] {:interpolation :poly-in-out})
(c/gradient [:red :green] {:interpolation :circle-in})
(c/gradient [:red :green] {:interpolation :circle-out}))
;; ## Merging gradients
;; Two gradients can be merged. The midpoint argument selects the point (default 0.5) where two gradients joins.
(let [g1 (c/gradient [:darkred :deeppink :light-yellow])
g2 (c/gradient [:light-yellow :lightgreen :teal])]
(🎨 g1 g2
(c/merge-gradients g1 g2)
(c/merge-gradients g1 g2 0.7)))
;; ## Random gradient
;; Random gradient can be generated by calling `random-gradient`.
(apply 🎨 (repeatedly 5 c/random-gradient))
;; # Fixing luma
;; If you want to correct luma to be linear, call `correct-luma`. Please be sure you palette is sorted by luma (ascending or descending) before calling.
;; The method is similar to one used in `chroma.js` and described [here](https://www.vis4.net/blog/2013/09/mastering-multi-hued-color-scales/#combining-bezier-interpolation-and-lightness-correction). The example showin in article is recreated below:
(let [pal [:lightyellow :orangered :deeppink :darkred]]
(🎨 (-> (c/gradient pal {:colorspace :LAB}) (c/palette 9))
(-> (c/gradient pal {:colorspace :LAB
:interpolation :b-spline}) (c/palette 9))
(-> (c/gradient pal {:colorspace :LAB}) c/correct-luma (c/palette 9))
(-> (c/gradient pal {:colorspace :LAB
:interpolation :b-spline}) c/correct-luma (c/palette 9))))
;; Let's see other examples with luma profiles.
(🎨 (sort-by c/luma (c/palette :guell))
(c/correct-luma (sort-by c/luma (c/palette :guell))))
;; Since it's done in `LAB` color space, some colors can vanish.
;; `correct-luma` works also for gradients (should be monotonic).
(🎨 (c/gradient [:black :docc/deep-slate-green :orangered :aquamarine :white])
(c/correct-luma (c/gradient [:black :docc/deep-slate-green :orangered :aquamarine :white])))
(defn get-luma-from-gradient
[gradient]
(let [cgradient (c/correct-luma gradient)
xs (m/slice-range 0.0 1.0 50)]
(map (partial zipmap [:t :luma :corrected?])
(concat (map (juxt identity
(comp first c/to-LAB gradient)
(constantly "no")) xs)
(map (juxt identity
(comp first c/to-LAB cgradient)
(constantly "yes")) xs)))))
(clerk/vl {:data {:values (get-luma-from-gradient (c/gradient [:black :docc/deep-slate-green :orangered :aquamarine :white]))}
:width 600
:height 300
:title "Luma from LAB color space"
:usermeta {:embedOptions {:renderer "svg"}}
:mark {:type :line}
:encoding {:x {:field :t :type :quantitative}
:y {:field :luma :type :quantitative}
:color {:field :corrected? :type :nominal}}})
(🎨 (c/gradient [:white :lightcyan :palegreen :navy :black])
(c/correct-luma (c/gradient [:white :lightcyan :palegreen :navy :black])))
(clerk/vl {:data {:values (get-luma-from-gradient (c/gradient [:white :lightcyan :palegreen :navy :black]))}
:width 600
:height 300
:title "Luma from LAB color space"
:usermeta {:embedOptions {:renderer "svg"}}
:mark {:type :line}
:encoding {:x {:field :t :type :quantitative}
:y {:field :luma :type :quantitative}
:color {:field :corrected? :type :nominal}}})
;; Let's create diverging multi-hue color palette as desribed in [this chapter](https://www.vis4.net/blog/2013/09/mastering-multi-hued-color-scales/#update-diverging-multi-hue-color-palettes).
(let [opts {:interpolation :b-spline :colorspace :LAB}
g1 (-> (c/gradient [:darkred :deeppink :light-yellow] opts) c/correct-luma)
g2 (-> (c/gradient [:light-yellow :lightgreen :teal] opts) c/correct-luma)]
(🎨 (c/palette (c/merge-gradients g1 g2) 13)))
^{::clerk/visibility :hide}
(let [opts {:interpolation :b-spline :colorspace :LAB}
g (c/merge-gradients
(-> (c/gradient [:darkred :deeppink :light-yellow] opts) c/correct-luma)
(-> (c/gradient [:light-yellow :lightgreen :teal] opts) c/correct-luma))]
(clerk/vl {:data {:values (map #(zipmap [:t :luma]
[% (first (c/to-LAB (g %)))]) (m/slice-range 0.0 1.0 100))}
:width 600
:height 300
:usermeta {:embedOptions {:renderer "svg"}}
:title "Luma from LAB color space"
:mark {:type :line}
:encoding {:x {:field :t :type :quantitative}
:y {:field :luma :type :quantitative}}}))
;; # Mixing and blending
;; ## Mixing
;; Colors can be mixed and blended in various ways. Let's start with linear interpoation. By default `lerp` finds mid colour.
(🎨 (c/lerp :orange :blue))
;; But we can decide about the amount of interpolation
(🎨 (c/lerp :orange :blue 0.25)
(c/lerp :orange :blue 0.75))
;; Another two functions `lerp+` and `lerp-` are trying to conserve brightness of the right or left color.
(🎨 (c/lerp- :orange :blue 0.3)
(c/lerp+ :orange :blue 0.7))
;; Brightness (Luma) is taken from the first channel of *LAB* color space.
^{::clerk/viewer :block}
[(c/get-channel :blue :LAB 0)
(c/get-channel (c/lerp+ :orange :blue 0.7) :LAB 0)
(c/get-channel :orange :LAB 0)
(c/get-channel (c/lerp- :orange :blue 0.3) :LAB 0)]
;; There also two other methods of mixing: `mix` and `mixsub`. The former one mixes squared values (and takes square root after all), the latter perform subtractive mixing.
(🎨 (c/mix :orange :blue)
(c/mixsub :orange :blue))
;; We can also lerp and mix in different color spaces
(🎨 (c/lerp :orange :blue :Oklab 0.5)
(c/mix :orange :blue :Oklab 0.5))
;; To compare above methods let's check gradients generated by them:
(🎨 (partial c/lerp :orange :blue)
(partial c/lerp :orange :blue :Oklab)
(partial c/lerp- :orange :blue)
(partial c/lerp+ :orange :blue)
(partial c/mix :orange :blue)
(partial c/mix :orange :blue :Oklab)
(partial c/mixsub :orange :blue)
(partial c/mixsub :orange :blue :Oklab))
;; `mixbox` is a pigment-based color mixing developed by [Secret Weapons](https://github.com/scrtwpns/mixbox)
(🎨 (partial c/mixbox :orange :blue)
(partial c/mixbox :yellow :blue))
;; Last method calculates average colors.
(🎨 (c/average [:orange :blue :green :yellow]))
;; We can also average with weights
(🎨 (c/weighted-average [:orange :blue :green :yellow] [1 0.5 1 5]))
;; Additionally we can average in different color spaces:
(🎨 (c/average [:orange :blue :green :yellow] :LAB)
(c/weighted-average [:orange :blue :green :yellow] [1 0.5 1 5] :LUV))
;; ## Blending
;; Two colors/palettes/gradients can be combined in various ways known from graphics editors. Bleding functions are defined in `clojure2d.color.blend`.
;; You can select one blending method for all channels or for each channel separately. Algorithms and alpha blending are described [here](https://www.w3.org/TR/compositing-1/#blending) (simple alpha compositing is used in the library).
(🎨 (bl/blend-colors bl/add [12 33 55] [55 66 77])
(bl/blend-colors bl/add [12 33 55 120] [55 66 77 200])
(bl/blend-colors bl/subtract bl/multiply bl/screen [12 33 55] [55 66 77]))