aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias EngdegÄrd2020-06-10 19:18:58 +0200
committerMattias EngdegÄrd2020-06-10 20:11:13 +0200
commit68ae6faa7f1b4c348740667f98fbf1d1ce5a7979 (patch)
tree2a1e1c310ad97110a9f691d29b5e51505fa7418b
parentb19259c8412ee2e715c4bd145711e23729411fd0 (diff)
downloademacs-68ae6faa7f1b4c348740667f98fbf1d1ce5a7979.tar.gz
emacs-68ae6faa7f1b4c348740667f98fbf1d1ce5a7979.zip
Improved light/dark colour predicate (bug#41544)
Add a predicate, color-dark-p, for deciding whether a colour is more readable with black or white as contrast. It has experimentally been shown to be more accurate and robust than the various methods currently employed. The new predicate compares the relative luminance of the colour to an empirically determined cut-off value, and it seems to get it right in almost all cases, with no value leading to outright bad results. * lisp/faces.el (readable-foreground-color): Use color-dark-p. (color-dark-p): New function. * lisp/facemenu.el (list-colors-print): Use readable-foreground-color, improving readability of list-colors-display. * lisp/textmodes/css-mode.el (css--contrasty-color): Remove. (css--fontify-region): Use readable-foreground-color.
-rw-r--r--lisp/facemenu.el11
-rw-r--r--lisp/faces.el39
-rw-r--r--lisp/textmodes/css-mode.el14
3 files changed, 36 insertions, 28 deletions
diff --git a/lisp/facemenu.el b/lisp/facemenu.el
index b10d874b21b..419b76101b5 100644
--- a/lisp/facemenu.el
+++ b/lisp/facemenu.el
@@ -621,12 +621,11 @@ color. The function should accept a single argument, the color name."
621 (downcase b)))))) 621 (downcase b))))))
622 (setq color (list color))) 622 (setq color (list color)))
623 (let* ((opoint (point)) 623 (let* ((opoint (point))
624 (color-values (color-values (car color))) 624 (fg (readable-foreground-color (car color))))
625 (light-p (>= (apply 'max color-values)
626 (* (car (color-values "white")) .5))))
627 (insert (car color)) 625 (insert (car color))
628 (indent-to 22) 626 (indent-to 22)
629 (put-text-property opoint (point) 'face `(:background ,(car color))) 627 (put-text-property opoint (point) 'face `(:background ,(car color)
628 :foreground ,fg))
630 (put-text-property 629 (put-text-property
631 (prog1 (point) 630 (prog1 (point)
632 (insert " ") 631 (insert " ")
@@ -639,7 +638,7 @@ color. The function should accept a single argument, the color name."
639 (insert (propertize 638 (insert (propertize
640 (apply 'format "#%02x%02x%02x" 639 (apply 'format "#%02x%02x%02x"
641 (mapcar (lambda (c) (ash c -8)) 640 (mapcar (lambda (c) (ash c -8))
642 color-values)) 641 (color-values (car color))))
643 'mouse-face 'highlight 642 'mouse-face 'highlight
644 'help-echo 643 'help-echo
645 (let ((hsv (apply 'color-rgb-to-hsv 644 (let ((hsv (apply 'color-rgb-to-hsv
@@ -651,7 +650,7 @@ color. The function should accept a single argument, the color name."
651 opoint (point) 650 opoint (point)
652 'follow-link t 651 'follow-link t
653 'mouse-face (list :background (car color) 652 'mouse-face (list :background (car color)
654 :foreground (if light-p "black" "white")) 653 :foreground fg)
655 'color-name (car color) 654 'color-name (car color)
656 'action callback-fn))) 655 'action callback-fn)))
657 (insert "\n")) 656 (insert "\n"))
diff --git a/lisp/faces.el b/lisp/faces.el
index f4a9dedd799..5ecc256f077 100644
--- a/lisp/faces.el
+++ b/lisp/faces.el
@@ -1785,16 +1785,35 @@ with the color they represent as background color."
1785 (defined-colors frame))) 1785 (defined-colors frame)))
1786 1786
1787(defun readable-foreground-color (color) 1787(defun readable-foreground-color (color)
1788 "Return a readable foreground color for background COLOR." 1788 "Return a readable foreground color for background COLOR.
1789 (let* ((rgb (color-values color)) 1789The returned value is a string representing black or white, depending
1790 (max (apply #'max rgb)) 1790on which one provides better contrast with COLOR."
1791 (black (car (color-values "black"))) 1791 ;; We use #ffffff instead of "white", because the latter is sometimes
1792 (white (car (color-values "white")))) 1792 ;; less than white. That way, we get the best contrast possible.
1793 ;; Select black or white depending on which one is less similar to 1793 (if (color-dark-p (mapcar (lambda (c) (/ c 65535.0)) color))
1794 ;; the brightest component. 1794 "#ffffff" "black"))
1795 (if (> (abs (- max black)) (abs (- max white))) 1795
1796 "black" 1796(defun color-dark-p (rgb)
1797 "white"))) 1797 "Whether RGB is more readable against white than black.
1798RGB is a 3-element list (R G B), each component in the range [0,1].
1799This predicate can be used both for determining a suitable (black or white)
1800contrast colour with RGB as background and as foreground."
1801 (unless (<= 0 (apply #'min rgb) (apply #'max rgb) 1)
1802 (error "RGB components %S not in [0,1]" rgb))
1803 ;; Compute the relative luminance after gamma-correcting (assuming sRGB),
1804 ;; and compare to a cut-off value determined experimentally.
1805 ;; See https://en.wikipedia.org/wiki/Relative_luminance for details.
1806 (let* ((sr (nth 0 rgb))
1807 (sg (nth 1 rgb))
1808 (sb (nth 2 rgb))
1809 ;; Gamma-correct the RGB components to linear values.
1810 ;; Use the power 2.2 as an approximation to sRGB gamma;
1811 ;; it should be good enough for the purpose of this function.
1812 (r (expt sr 2.2))
1813 (g (expt sg 2.2))
1814 (b (expt sb 2.2))
1815 (y (+ (* r 0.2126) (* g 0.7152) (* b 0.0722))))
1816 (< y (eval-when-compile (expt 0.6 2.2)))))
1798 1817
1799(declare-function xw-color-defined-p "xfns.c" (color &optional frame)) 1818(declare-function xw-color-defined-p "xfns.c" (color &optional frame))
1800 1819
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 0035c5e7b05..2cd99787e8a 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -1149,17 +1149,6 @@ returns, point will be at the end of the recognized color."
1149 ;; Evaluate to the color if the name is found. 1149 ;; Evaluate to the color if the name is found.
1150 ((css--named-color start-point match)))) 1150 ((css--named-color start-point match))))
1151 1151
1152(defun css--contrasty-color (name)
1153 "Return a color that contrasts with NAME.
1154NAME is of any form accepted by `color-distance'.
1155The returned color will be usable by Emacs and will contrast
1156with NAME; in particular so that if NAME is used as a background
1157color, the returned color can be used as the foreground and still
1158be readable."
1159 ;; See bug#25525 for a discussion of this.
1160 (if (> (color-distance name "black") 292485)
1161 "black" "white"))
1162
1163(defcustom css-fontify-colors t 1152(defcustom css-fontify-colors t
1164 "Whether CSS colors should be fontified using the color as the background. 1153 "Whether CSS colors should be fontified using the color as the background.
1165When non-`nil', a text representing CSS color will be fontified 1154When non-`nil', a text representing CSS color will be fontified
@@ -1199,7 +1188,8 @@ START and END are buffer positions."
1199 (add-text-properties 1188 (add-text-properties
1200 start (point) 1189 start (point)
1201 (list 'face (list :background color 1190 (list 'face (list :background color
1202 :foreground (css--contrasty-color color) 1191 :foreground (readable-foreground-color
1192 color)
1203 :box '(:line-width -1)))))))))))) 1193 :box '(:line-width -1))))))))))))
1204 extended-region)) 1194 extended-region))
1205 1195