1 (uiop:define-package
#:lw2.colors
2 (:use
#:cl
#:iterate
#:lw2.utils
)
3 (:import-from
#:alexandria
#:when-let
#:emptyp
)
4 (:import-from
#:parse-float
#:parse-float
)
5 (:export
#:decode-css-color
#:encode-css-color
#:safe-color-name
#:perceptual-invert-rgba
#:perceptual-invert-color-string
))
7 (in-package #:lw2.colors
)
9 ;;;; Refer to https://drafts.csswg.org/css-color/#numeric-srgb
11 (defparameter *web-colors-list
*
12 (with-open-file (stream (asdf:system-relative-pathname
:lw2-viewer
"data/webcolors.json") :direction
:input
)
15 (cons (cdr (assoc :name color-data
))
16 (map 'list
(lambda (x) (/ (the (integer 0 255) (cdr x
)) 255.0d0
)) (cdr (assoc :rgb color-data
)))))
17 (json:decode-json stream
))))
19 (defun parse-multi-hex (string count length
&key
(start 0) (key #'identity
))
21 (iter (for i from start below
(+ start
(* count length
)) by length
)
22 (collect (funcall key
(parse-integer string
:start i
:end
(+ i length
) :radix
16))))))
24 (defun parse-css-rgb-value (string)
25 (let ((number (parse-integer string
:junk-allowed t
)))
26 (if (ppcre:scan
"%$" string
)
30 (defun parse-css-hue-value (string)
31 (let ((number (parse-float string
:junk-allowed t
)))
33 ("grad$" (* number
(/ 360.0d0
400.0d0
)))
34 ("rad$" (* number
(/ 360.0d0
(* 2 pi
))))
35 ("turn$" (* number
360.0d0
))
36 ("%$" (* number
(/ 360.0d0
100.0d0
)))
39 (defun parse-css-alpha-value (string)
40 (let ((number (and (not (emptyp string
)) (parse-float string
:junk-allowed t
))))
43 ("%$" (/ number
100d0
))
47 (defun decode-css-color (color-string)
48 (regex-case color-string
49 ("#[0-9a-fA-F]{3}\\s*$" (values* (parse-multi-hex color-string
3 1 :start
1 :key
(lambda (x) (declare (type (integer 0 15) x
)) (/ (+ x
(* x
16)) 255.0d0
))) 1.0d0
))
50 ("#[0-9a-fA-F]{4}\\s*$" (parse-multi-hex color-string
4 1 :start
1 :key
(lambda (x) (declare (type (integer 0 15) x
)) (/ (+ x
(* x
16)) 255.0d0
))))
51 ("#[0-9a-fA-F]{6}\\s*$" (values* (parse-multi-hex color-string
3 2 :start
1 :key
(lambda (x) (declare (type (integer 0 255) x
)) (/ x
255.0d0
))) 1.0d0
))
52 ("#[0-9a-fA-F]{8}\\s*$" (parse-multi-hex color-string
4 2 :start
1 :key
(lambda (x) (declare (type (integer 0 255) x
)) (/ x
255.0d0
))))
54 (multiple-value-bind (rgb-list a-list
) (firstn (ppcre:split
"[ ,]+" (reg 0)) 3)
55 (values* (values-list (map 'list
#'parse-css-rgb-value rgb-list
))
56 (parse-css-alpha-value (first a-list
)))))
58 (destructuring-bind (h s l
&optional a
) (ppcre:split
"[ ,/]+" (reg 0))
59 (values* (dufy/core
:hsl-to-rgb
(parse-css-hue-value h
)
60 (parse-css-rgb-value s
)
61 (parse-css-rgb-value l
))
62 (parse-css-alpha-value a
))))
64 (when-let ((color-list (cdr (assoc color-string
*web-colors-list
* :test
#'string-equal
))))
65 (values* (values-list color-list
) 1.0d0
)))))
67 (defun encode-css-color (r g b a
)
68 (format nil
"#~6,'0X~2,'0X" (dufy/core
:rgb-to-rgbpack r g b
) (round (* a
255))))
70 (defun safe-color-name (r g b a
)
71 (format nil
"~6,'0X~2,'0X" (dufy/core
:rgb-to-rgbpack r g b
) (round (* a
255))))
73 (defun gamma-invert-lightness (l)
76 (* 100d0
(expt (- 1d0
(/ l
100d0
)) (/ 1.25d0
)))))
78 (defun perceptual-invert-rgba (r g b alpha
)
79 (multiple-value-bind (l a b
)
80 (multiple-value-call #'dufy
/core
:xyz-to-lab
(dufy/core
:rgb-to-xyz r g b
))
81 (multiple-value-bind (nr ng nb
)
82 (multiple-value-call #'dufy
/core
:xyz-to-rgb
83 (dufy/core
:lab-to-xyz
(gamma-invert-lightness l
) a b
))
84 (values nr ng nb alpha
))))
86 (defun perceptual-invert-color-string (color-string)
87 (multiple-value-call #'encode-css-color
(multiple-value-call #'perceptual-invert-rgba
(decode-css-color color-string
))))