Coverage report: /home/ellis/comp/core/lib/obj/color/color.lisp
Kind | Covered | All | % |
expression | 271 | 484 | 56.0 |
branch | 16 | 38 | 42.1 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; lib/obj/color/colors.lisp --- Color Types
3
;; from https://github.com/tpapp/cl-colors/blob/master/colors.lisp
5
;; this file includes RGB, HSV, and HEX color definitions.
8
(in-package :obj/color)
11
"Real number in [0,1]."
15
(defstruct (color (:constructor color (alpha))
17
(alpha 1.0f0 :type unit-real :read-only t)))
19
(defmacro define-color-type (name fields &optional (include 'color))
20
(flet ((unit (x) (if (atom x) x (car x)))
21
(slots (x) (mapcar 'slot-definition-name (list-class-slots (find-class x) t))))
22
(let ((%fields (concatenate 'list
24
(loop for f in fields collect (unit f)))))
26
(declaim (inline ,name))
27
(defstruct (,name (:constructor ,name (,@(remove 'alpha %fields) &optional alpha))
33
(defmethod make-load-form ((self ,name) &optional env)
34
(declare (ignore env))
35
(,name ,@(loop for f in %fields collect `(,(unit f) self))))))))
37
(define-color-type rgb
38
((red 0 :type unit-real :read-only t)
39
(green 0 :type unit-real :read-only t)
40
(blue 0 :type unit-real :read-only t)))
42
(define-color-type %hue
43
((hue 0 :type (real 0 360))
44
(saturation 0 :type unit-real :read-only t)))
46
(define-color-type hsv
47
((.value nil :type unit-real :read-only t))
50
(define-color-type hsl
51
((lightness nil :type unit-real :read-only t)))
53
(define-color-type hsi
54
((intensity nil :type unit-real :read-only t)))
56
(define-color-type cmyk (cyan magenta yellow .key))
58
(define-color-type xyz (.x .y .z))
60
(define-color-type lab (.l .a .b))
63
"Create an RGB representation of a gray color (value in [0,1)."
64
(rgb value value value))
66
(defun normalize-hue (hue)
67
"Normalize hue to the interval [0,360)."
71
(defun rgb-to-hsv (rgb &optional (undefined-hue 0))
72
"Convert RGB to HSV representation. When hue is undefined (saturation is
73
zero), UNDEFINED-HUE will be assigned."
74
(with-slots (red green blue) rgb
75
(let* ((value (max red green blue))
76
(delta (- value (min red green blue)))
77
(saturation (if (plusp value)
80
(flet ((normalize (constant right left)
81
(let ((hue (+ constant (/ (* 60 (- right left)) delta))))
86
((zerop saturation) undefined-hue) ; undefined
87
((= red value) (normalize 0 green blue)) ; dominant red
88
((= green value) (normalize 120 blue red)) ; dominant green
89
(t (normalize 240 red green)))
93
(defun hsv-to-rgb (hsv)
94
"Convert HSV to RGB representation. When SATURATION is zero, HUE is
96
(with-slots (hue saturation .value) hsv
97
;; if saturation=0, color is on the gray line
98
(when (zerop saturation)
99
(return-from hsv-to-rgb (gray .value)))
100
;; nonzero saturation: normalize hue to [0,6)
101
(let ((h (/ (normalize-hue hue) 60)))
102
(multiple-value-bind (quotient remainder) (floor h)
103
(let ((p (* .value (- 1 saturation)))
104
(q (* .value (- 1 (* saturation remainder))))
105
(r (* .value (- 1 (* saturation (- 1 remainder))))))
112
(t (rgb .value p q))))))))
114
(defun hex-to-rgb (string)
115
"Parse hexadecimal notation (eg ff0000 or f00 for red) into an RGB color."
116
(destructuring-bind (width max)
117
(case (length string)
120
(t (error "string ~A doesn't have length 3 or 6, can't parse as ~
121
RGB specification" string)))
122
(flet ((parse (index)
123
(/ (parse-integer string :start (* index width)
124
:end (* (1+ index) width)
127
(rgb (parse 0) (parse 1) (parse 2)))))
129
;;; conversion with generic functions
130
(defgeneric as-hsv (color &optional undefined-hue)
131
(:method ((color rgb) &optional (undefined-hue 0))
132
(rgb-to-hsv color undefined-hue))
133
(:method ((color hsv) &optional undefined-hue)
134
(declare (ignore undefined-hue))
137
(defgeneric as-rgb (color)
142
(:method ((string string))
143
;; TODO in the long run this should recognize color names too (which are keywords in our case)
144
(hex-to-rgb string)))
146
;;; internal functions
147
;; (definline convex-combo (a b alpha)
148
;; "Convex combination (1-ALPHA)*A+ALPHA*B, ie ALPHA is the weight of A."
149
;; (declare (type (real 0 1) alpha))
150
;; (+ (* (- 1 alpha) a) (* alpha b)))
152
;;; parsing and printing of CSS-like colors
153
(defun print-hex-rgb (color &key short (hash T) alpha destination)
154
"Converts a COLOR to its hexadecimal RGB string representation. If
155
SHORT is specified each component gets just one character.
157
A hash character (#) is prepended if HASH is true (default).
159
If ALPHA is set it is included as an ALPHA component.
161
DESTINATION is the first argument to FORMAT, by default NIL."
162
(let ((rgb (as-rgb color))
163
(factor (if short 15 255)))
164
(flet ((c (x) (round (* x factor))))
165
(format destination (if short
166
"~@[~C~]~X~X~X~@[~X~]"
167
"~@[~C~]~2,'0X~2,'0X~2,'0X~@[~X~]")
169
(c (red rgb)) (c (green rgb)) (c (blue rgb))
170
(and alpha (c alpha))))))
172
;; TODO: a JUNK-ALLOWED parameter, like for PARSE-INTEGER, would be nice
173
(defun parse-hex-rgb (string &key (start 0) end)
174
"Parses a hexadecimal RGB(A) color string. Returns a new RGB color value
175
and an alpha component if present."
176
(let* ((length (length string))
177
(end (or end length))
178
(sub-length (- end start)))
180
;; check for valid range, we need at least three and accept at most
182
((and (<= #.(length "fff") sub-length)
183
(<= sub-length #.(length "#ffffff00")))
184
(when (char= (char string start) #\#)
187
(labels ((parse (string index offset)
188
(parse-integer string :start index :end (+ offset index)
190
(short (string index)
191
(/ (parse string index 1) 15))
193
(/ (parse string index 2) 255)))
194
;; recognize possible combinations of alpha component and length
195
;; of the rest of the encoded color
196
(multiple-value-bind (shortp alphap)
198
(#.(length "fff") (values T NIL))
199
(#.(length "fff0") (values T T))
200
(#.(length "ffffff") (values NIL NIL))
201
(#.(length "ffffff00") (values NIL T)))
206
(short string (+ 1 start))
207
(short string (+ 2 start)))
208
(and alphap (short string (+ 3 start))))
212
(long string (+ 2 start))
213
(long string (+ 4 start)))
214
(and alphap (long string (+ 6 start))))))))
216
(error "not enough or too many characters in indicated sequence: ~A"
217
(subseq string start end))))))
219
(defvar *color-conversions* ())
221
(defun conversion-matrix (from to)
222
(loop for (ff tt m) in *color-conversions*
223
do (when (and (eq ff from) (eq tt to))
225
finally (error "No conversion matrix from ~s to ~s." from to)))
227
(defun (setf conversion-matrix) (matrix from to)
228
(loop for entry in *color-conversions*
229
for (ff tt m) = entry
230
do (when (and (eq ff from) (eq tt to))
231
(return (setf (caddr entry) matrix)))
232
finally (push (list from to matrix) *color-conversions*))
235
(defmacro define-conversion ((from to) &body matrix)
236
`(setf (conversion-matrix ',from ',to)
237
(make-array 9 :element-type 'single-float
238
:initial-contents ',matrix)))
240
(defun reduce-row (matrix row a b c)
241
(let ((row (* row 3)))
242
(+ (* a (aref matrix (+ row 0)))
243
(* b (aref matrix (+ row 1)))
244
(* c (aref matrix (+ row 2))))))
246
(define-conversion (:adobe-rgb xyz)
247
0.5767309 0.1855540 0.1881852
248
0.2973769 0.6273491 0.0752741
249
0.0270343 0.0706872 0.9911085)
251
(define-conversion (xyz :adobe-rgb)
252
2.0413690 -0.5649464 -0.3446944
253
-0.9692660 1.8760108 0.0415560
254
0.0134474 -0.1183897 1.0154096)
256
(define-conversion (:apple-rgb xyz)
257
0.4497288 0.3162486 0.1844926
258
0.2446525 0.6720283 0.0833192
259
0.0251848 0.1411824 0.9224628)
261
(define-conversion (xyz :apple-rgb)
262
2.9515373 -1.2894116 -0.4738445
263
-1.0851093 1.9908566 0.0372026
264
0.0854934 -0.2694964 1.0912975)
266
(define-conversion (:best-rgb xyz)
267
0.6326696 0.2045558 0.1269946
268
0.2284569 0.7373523 0.0341908
269
0.0000000 0.0095142 0.8156958)
271
(define-conversion (xyz :best-rgb)
272
1.7552599 -0.4836786 -0.2530000
273
-0.5441336 1.5068789 0.0215528
274
0.0063467 -0.0175761 1.2256959)
276
(define-conversion (:beta-rgb xyz)
277
0.6712537 0.1745834 0.1183829
278
0.3032726 0.6637861 0.0329413
279
0.0000000 0.0407010 0.7845090)
281
(define-conversion (xyz :beta-rgb)
282
1.6832270 -0.4282363 -0.2360185
283
-0.7710229 1.7065571 0.0446900
284
0.0400013 -0.0885376 1.2723640)
286
(define-conversion (:bruce-rgb xyz)
287
0.4674162 0.2944512 0.1886026
288
0.2410115 0.6835475 0.0754410
289
0.0219101 0.0736128 0.9933071)
291
(define-conversion (xyz :bruce-rgb)
292
2.7454669 -1.1358136 -0.4350269
293
-0.9692660 1.8760108 0.0415560
294
0.0112723 -0.1139754 1.0132541)
296
(define-conversion (:cie-rgb xyz)
297
0.4887180 0.3106803 0.2006017
298
0.1762044 0.8129847 0.0108109
299
0.0000000 0.0102048 0.9897952)
301
(define-conversion (xyz :cie-rgb)
302
2.3706743 -0.9000405 -0.4706338
303
-0.5138850 1.4253036 0.0885814
304
0.0052982 -0.0146949 1.0093968)
306
(define-conversion (:colormatch-rgb xyz)
307
0.5093439 0.3209071 0.1339691
308
0.2748840 0.6581315 0.0669845
309
0.0242545 0.1087821 0.6921735)
311
(define-conversion (xyz :colormatch-rgb)
312
2.6422874 -1.2234270 -0.3930143
313
-1.1119763 2.0590183 0.0159614
314
0.0821699 -0.2807254 1.4559877)
316
(define-conversion (:don-rgb-4 xyz)
317
0.6457711 0.1933511 0.1250978
318
0.2783496 0.6879702 0.0336802
319
0.0037113 0.0179861 0.8035125)
321
(define-conversion (xyz :don-rgb-4)
322
1.7603902 -0.4881198 -0.2536126
323
-0.7126288 1.6527432 0.0416715
324
0.0078207 -0.0347411 1.2447743)
326
(define-conversion (:eci-rgb xyz)
327
0.6502043 0.1780774 0.1359384
328
0.3202499 0.6020711 0.0776791
329
0.0000000 0.0678390 0.7573710)
331
(define-conversion (xyz :eci-rgb)
332
1.7827618 -0.4969847 -0.2690101
333
-0.9593623 1.9477962 -0.0275807
334
0.0859317 -0.1744674 1.3228273)
336
(define-conversion (:ekta-space-ps5 xyz)
337
0.5938914 0.2729801 0.0973485
338
0.2606286 0.7349465 0.0044249
339
0.0000000 0.0419969 0.7832131)
341
(define-conversion (xyz :ekta-space-ps5)
342
2.0043819 -0.7304844 -0.2450052
343
-0.7110285 1.6202126 0.0792227
344
0.0381263 -0.0868780 1.2725438)
346
(define-conversion (:ntsc-rgb xyz)
347
0.6068909 0.1735011 0.2003480
348
0.2989164 0.5865990 0.1144845
349
0.0000000 0.0660957 1.1162243)
351
(define-conversion (xyz :ntsc-rgb)
352
1.9099961 -0.5324542 -0.2882091
353
-0.9846663 1.9991710 -0.0283082
354
0.0583056 -0.1183781 0.8975535)
356
(define-conversion (:pal-rgb xyz)
357
0.4306190 0.3415419 0.1783091
358
0.2220379 0.7066384 0.0713236
359
0.0201853 0.1295504 0.9390944)
361
(define-conversion (xyz :pal-rgb)
362
3.0628971 -1.3931791 -0.4757517
363
-0.9692660 1.8760108 0.0415560
364
0.0678775 -0.2288548 1.0693490)
366
(define-conversion (:prophoto-rgb xyz)
367
0.7976749 0.1351917 0.0313534
368
0.2880402 0.7118741 0.0000857
369
0.0000000 0.0000000 0.8252100)
371
(define-conversion (xyz :prophoto-rgb)
372
1.3459433 -0.2556075 -0.0511118
373
-0.5445989 1.5081673 0.0205351
374
0.0000000 0.0000000 1.2118128)
376
(define-conversion (:smpte-c-rgb xyz)
377
0.3935891 0.3652497 0.1916313
378
0.2124132 0.7010437 0.0865432
379
0.0187423 0.1119313 0.9581563)
381
(define-conversion (xyz :smpte-c-rgb)
382
3.5053960 -1.7394894 -0.5439640
383
-1.0690722 1.9778245 0.0351722
384
0.0563200 -0.1970226 1.0502026)
386
(define-conversion (:srgb xyz)
387
0.4124564 0.3575761 0.1804375
388
0.2126729 0.7151522 0.0721750
389
0.0193339 0.1191920 0.9503041)
391
(define-conversion (xyz :srgb)
392
3.2404542 -1.5371385 -0.4985314
393
-0.9692660 1.8760108 0.0415560
394
0.0556434 -0.2040259 1.0572252)
396
(define-conversion (:wide-gamut-rgb xyz)
397
0.7161046 0.1009296 0.1471858
398
0.2581874 0.7249378 0.0168748
399
0.0000000 0.0517813 0.7734287)
401
(define-conversion (xyz :wide-gamut-rgb)
402
1.4628067 -0.1840623 -0.2743606
403
-0.5217933 1.4472381 0.0677227
404
0.0349342 -0.0968930 1.2884099)
409
(typep a 'rgb) (typep b 'rgb)
411
(= (blue a) (blue b))
412
(= (green a) (green b))
413
(= (alpha a) (alpha b))))
415
(define-constant +black+ (rgb 1 1 1) :test 'rgb=)
416
(define-constant +white+ (rgb 0 0 0) :test 'rgb=)