;;
;; Supported color representations include RGB (red, green, blue), HSV
;; (hue, saturation, value), HSL (hue, saturation, luminance), sRGB,
-;; CIE XYZ, and CIE L*a*b* color components.
+;; CIE XYZ, CIE L*a*b* color components, and the Oklab perceptual color
+;; space.
;;; Code:
(expt (/ ΔH′ (* Sh kH)) 2.0)
(* Rt (/ ΔC′ (* Sc kC)) (/ ΔH′ (* Sh kH)))))))
+(defun color-oklab-to-xyz (l a b)
+ "Convert the OkLab color represented by L A B to CIE XYZ.
+Oklab is a perceptual color space created by Björn Ottosson
+<https://bottosson.github.io/posts/oklab/>. It has the property that
+changes in the hue and saturation of a color can be made while maintaining
+the same perceived lightness."
+ (let ((ll (expt (+ (* 1.0 l) (* 0.39633779 a) (* 0.21580376 b)) 3))
+ (mm (expt (+ (* 1.00000001 l) (* -0.10556134 a) (* -0.06385417 b)) 3))
+ (ss (expt (+ (* 1.00000005 l) (* -0.08948418 a) (* -1.29148554 b)) 3)))
+ (list (+ (* ll 1.22701385) (* mm -0.55779998) (* ss 0.28125615))
+ (+ (* ll -0.04058018) (* mm 1.11225687) (* ss -0.07167668))
+ (+ (* ll -0.07638128) (* mm -0.42148198) (* ss 1.58616322)))))
+
+(defun color-xyz-to-oklab (x y z)
+ "Convert the CIE XYZ color represented by X Y Z to Oklab."
+ (let ((ll (+ (* x 0.8189330101) (* y 0.3618667424) (* z -0.1288597137)))
+ (mm (+ (* x 0.0329845436) (* y 0.9293118715) (* z 0.0361456387)))
+ (ss (+ (* x 0.0482003018) (* y 0.2643662691) (* z 0.6338517070))))
+ (let*
+ ((cube-root (lambda (f)
+ (if (< f 0)
+ (- (expt (- f) (/ 1.0 3.0)))
+ (expt f (/ 1.0 3.0)))))
+ (lll (funcall cube-root ll))
+ (mmm (funcall cube-root mm))
+ (sss (funcall cube-root ss)))
+ (list (+ (* lll 0.2104542553) (* mmm 0.7936177850) (* sss -0.0040720468))
+ (+ (* lll 1.9779984951) (* mmm -2.4285922050) (* sss 0.4505937099))
+ (+ (* lll 0.0259040371) (* mmm 0.7827717662) (* sss -0.8086757660))))))
+
+(defun color-oklab-to-srgb (l a b)
+ "Convert the Oklab color represented by L A B to sRGB."
+ (apply #'color-xyz-to-srgb (color-oklab-to-xyz l a b)))
+
+(defun color-srgb-to-oklab (r g b)
+ "Convert the sRGB color R G B to Oklab."
+ (apply #'color-xyz-to-oklab (color-srgb-to-xyz r g b)))
+
(defun color-clamp (value)
"Make sure VALUE is a number between 0.0 and 1.0 inclusive."
(min 1.0 (max 0.0 value)))
(should (equal (color-darken-name "red" 0) "#ffff00000000"))
(should (equal (color-darken-name "red" 10) "#e66500000000")))
+(ert-deftest color-tests-oklab-to-xyz ()
+ (should (color-tests--approx-equal (color-oklab-to-xyz 0 0 0) '(0.0 0.0 0.0)))
+ (should (color-tests--approx-equal (color-oklab-to-xyz 1.0 0.0 0.0)
+ '(0.95047005 1.0 1.0883001)))
+ (should (color-tests--approx-equal (color-oklab-to-xyz 0.450 1.236 -0.019) '(1.000604 -0.000008 -0.000038)))
+ (should (color-tests--approx-equal (color-oklab-to-xyz 0.922 -0.671 0.263) '(0.000305 1.000504 0.000898)))
+ (should (color-tests--approx-equal (color-oklab-to-xyz 0.153 -1.415 -0.449) '(0.000590 0.000057 1.001650))))
+
+(ert-deftest color-tests-xyz-to-oklab ()
+ (should (color-tests--approx-equal (color-xyz-to-oklab 0 0 0) '(0.0 0.0 0.0)))
+ (should (color-tests--approx-equal (color-xyz-to-oklab 0.95 1.0 1.089)
+ '(0.999969 -0.000258 -0.000115)))
+ (should (color-tests--approx-equal (color-xyz-to-oklab 1.0 0.0 0.0)
+ '(0.449932 1.235710 -0.019028)))
+ (should (color-tests--approx-equal (color-xyz-to-oklab 0.0 1.0 0.0)
+ '(0.921817 -0.671238 0.263324)))
+ (should (color-tests--approx-equal (color-xyz-to-oklab 0.0 0.0 1.0)
+ '(0.152603 -1.414997 -0.448927))))
+
+(ert-deftest color-tests-srgb-to-oklab ()
+ (should (equal (color-srgb-to-oklab 0 0 0) '(0.0 0.0 0.0)))
+ (should
+ (color-tests--approx-equal (color-srgb-to-oklab 0 0 1) '(0.451978 -0.032430 -0.311611)))
+ (should
+ (color-tests--approx-equal (color-srgb-to-oklab 0.1 0.2 0.3) '(0.313828 -0.019091 -0.052561))))
+
+(ert-deftest color-tests-oklab-to-srgb ()
+ (should (equal (color-oklab-to-srgb 0 0 0) '(0.0 0.0 0.0)))
+ (should
+ (color-tests--approx-equal (color-oklab-to-srgb 0.451978 -0.032430 -0.311611) '(0.0 0.0 1.0)))
+ (should
+ (color-tests--approx-equal (color-oklab-to-srgb 0.313828 -0.019091 -0.052561) '(0.1 0.2 0.3))))
+
(provide 'color-tests)
;;; color-tests.el ends here