How I used ct (= color tools) APIs to choose background highlighting colors for use with hi-lock library OR Pick colors in a quantitative way using ct (= color tools) APIs


How I used ct (= color tools) APIs to choose background highlighting colors for use with hi-lock library

Or

Pick colors in a quantitative way using ct (= color tools) APIs

A word about hi-lock, and how I use it

I am a heavy user of hi-lock , the interative highlighting library, and use it to highlight symbols while reading and editing code. As a heavy user, I have my own set of demands on and expectations from the library.

I have documented how I use the hi-lock library, in an earlier article titled Highlight portions of your Text in Emacs.

This article is a continuation of the above article, and talks about how one may go about picking colors for highlighting.

What my highlighting needs are
  • I prefer the plain vanilla “black on white” theme that Emacs ships with
  • I need a large selection of highlighters.I mostly work with 3rd-party code, and in some programming sessions, I end up tracking as much as 7 symbols at any given point in time. Let us say, I prefer to have a choice of around 10 highlighting colors.
  • I prefer background highlighting.This choice is deliberate. It helps me distinguish my own ad-hoc highlighting from the font-lock-ed text. Note that, font-lock-ing in vanilla Emacs almost always changes the foreground color.
  • I want the highlighting to happen on the basis of background color alone.Specifically, I abhor looking at text that mixes faces with varying weight, height etc.
  • I prefer auto-selection of highlighter i.e., hi-lock-auto-select-face = t.This means that a color in and of itsef conveys no meaning. What matters most is that any highlighting color be distinct from the default foreground and background colors of my frame (= “black against white”, in my case), and that one highlighting color be distinguishable from another highlighting color.
What hi-lock offers
hi-lock comes with 11 numbers of faces.There are 6 background highlighters and 6 foreground highlighters.

The foreground highlighters are eclectic. By this, I mean that the foreground highlighters change the face along 3 different attributes–the color, the weight and the height.

The name of an hi-* face, encodes the content of the face. A hi-* faces includes color in its name. A -b suffix in a name, implies that the face is a foreground face, and is a bold variant. A -hb suffix, implies that the face is tall as well as bold.

A bird’s eye view of hi-lock faces

A perceived need for 10 or so highighting colors, and a country bumpkin who cannot tell one color apart from another

The previous list suggests that there is gap between and what I need and what is offered. One of the (imagined?) gaps lies in coming up with around 10 number of colors for highlighting. And, I found it challenging to come up with a good number of “good” highlighting colors. I had spent countless amount of time, over the last few years, to come up with a 10 number of distinct colours only to be disappointed with the result.

Discovery of ct🔗 and a skillful way to deal with colors

It is in the midst of this unsolved and yet a nagging need that I came across the article Reasoning about colors and the associated Emacs Lisp library neeasade’s color tools for Emacs. The article and the ct library piqued my interest because it was talking about the math and the art of picking colors. I was (pleasantly!) shocked to come across someone who could end up with 16 numbers of colors for his own theme, while the poor me was losing my sanity to settle on just 10 colors. The article introduced me to the notion that there are mutliple color spaces, and that color spaces like RGB are unfit for reasoning about colors and that some color spaces like HSLuv have “human-friendliness” and “perceptual uniformity” as one of its key goals.

… and finally the discovery of a path that leads to the Broadway

Armed with the new information, and a half-baked understanding of how colors need to be approached, I decided to come up with 10 numbers of highlighting colors. Needless to say, I am pleased with the result. I am surprised at how easy the trail is.

I invite you to look at how I have used two simple, and “easy to understand” quantitative measures

  1. distance between colors (= ct-distance) and
  2. the contrast ratio (= ct-contrast-ratio)

to arrive at a quick and pleasing result.

A set of 10 colors that is “good” for background highlighting

If you want to grab the colors in the screenshot, see next section titled “The Recipe”.

“Good” colors for use with background highlighting in vanilla Emacs

The recipe

;;;; my-highlighting-bg-colors --- My Highlighting Bg Colors -*- lexical-binding: t; coding: utf-8-emacs; -*-

;;; Commentary:

;;; Code:

(require 'cl-lib)
(require 'dash)
(require 's)

(require 'seq)
(require 'pcase)

;; See [Reasoning about
;; colors](https://web.archive.org/web/20240127045924/https://notes.neeasade.net/color-spaces.html),
;; and [neeasade's Color Tools for Emacs](https://github.com/neeasade/ct.el)
(require 'ct)
(require 'faces)

;; This snippet is compiled against seq-2.24 available as part of GNU
;; Emacs 30.0.50 built on date 2024-01-11.  `seq' is an young library,
;; and if the snippet doesn't work for you, it would most likely could
;; be because of `seq'.  Just install `seq' from GNU ELPA or GNU ELPA
;; Devel.

(defvar my-highlighting-bg-colors
  (let* (
         ;; Foreground color is BLACK
         (FG "black")
         ;; Background color is WHITE
         (BG "white")
         ;; We don't want the highlighter to be too close to the
         ;; background color
         (MIN-DISTANCE-FROM-BG 10)
         ;; We want the text to be readable even if it is highlighted.
         ;; IOW, the highlighter should contrast well against the
         ;; foreground color.
         (MIN-CONTRAST-FROM-FG 6)
         ;; We want the highlighters to be different from each other.
         (MIN-DISTANCE-BETWEEN-HI-LOCK-COLORS 20))
    (->>
     ;; ---------------- STEP 1 ----------------
     ;; I am running GUI Emacs on Debian GNOME Flashback + Xorg.
     (defined-colors)
     ;; There are 752 colors here.
     ;;
     ;; ---------------- STEP 2 ----------------
     ;; One color may have different names.
     ;;
     ;; For example, orange red,OrangeRed are essentially the same.
     ;; So, uniquify the colors.
     (pcase--flip seq-uniq (-on #'string= (-compose #'hsluv-rgb-to-hex #'color-name-to-rgb)))
     ;; After de-duplicaton we end up with 502 colors.
     ;;
     ;; ---------------- STEP 3 ----------------
     ;; The following test prevents colors too close to the background
     ;; color (= "white") from turning up in the results.
     (-filter (-compose (-partial #'< MIN-DISTANCE-FROM-BG) (-partial #'ct-distance BG)))
     ;; We now have 463 colors.
     ;;
     ;; ---------------- STEP 4 ----------------
     ;; The colors that we are looking for should contrast well
     ;; against the foreground color (= black).
     ;;
     (-filter (-compose (-partial #'< MIN-CONTRAST-FROM-FG) (-partial #'ct-contrast-ratio FG)))
     ;; After this step, we now have 260 colors.

     ;; ---------------- STEP 5 ----------------
     ;; The first color in our set always ends up in the final
     ;; shortlist.  What if we are unfortunate, and the first color is
     ;; too close to most of the colors and we end up with a smaller
     ;; subset.  So, shuffle what is in our bag, and hope that luck is
     ;; in favour.  See `nshufle' at
     ;; https://rosettacode.org/wiki/Knuth_shuffle#Common_Lisp
     (funcall (lambda (sequence)
                (cl-loop for i from (length sequence) downto 2
                         do (cl-rotatef (elt sequence (random i))
                                        (elt sequence (1- i))))
                sequence))
     ;; We still have around 260 colors, but they have been shuffled
     ;; around.

     ;; ---------------- STEP 6 ----------------
     ;; We should be able to tell the highlighting colors from each
     ;; other.  So, ensure that they are some min distance away from
     ;; each other.
     (pcase--flip seq-uniq ;; (lambda (a b)
                  ;;   (< (ct-distance a b) MIN-DISTANCE-BETWEEN-HI-LOCK-COLORS))
                  (-compose (-partial #'> MIN-DISTANCE-BETWEEN-HI-LOCK-COLORS) #'ct-distance))
     ;; I typically see around 10 colors or so.  The shuffling above
     ;; makes the final set of colors and their count a bit variable.
     ;;
     ;; ---------------- STEP 7 ----------------
     ;; Sort the colors based on how well they contrast against the
     ;; foreground (= black) color.
     (-sort (-on #'> (-partial #'ct-contrast-ratio FG)))
     ;; We still have around 10 colors or so, and they are in sorted
     ;; order.
     ;;
     ;; ---------------- STEP 8 ----------------
     ;; Pad the colors on the right so that can be presented well.
     ;; (-map (-partial #'s-pad-right 20 " "))
     ;;
     ;; ---------------- STEP 9 ----------------
     ;; Enjoy the choicest set of highlighting colors!
     ;;
     ;; You can see the final set of colors below on 5 trials.
     ;;
     ;; Don't you think this analytical approach for shortlisting
     ;; colors through color distances and color contrast ratios is a
     ;; much enjoyable and quicker approach to shortlisting colors?
     )))

;; ---------------- TRIAL 0 ----------------
;; ("yellow              " "cyan                " "wheat               "
;;  "green               " "orange              " "plum                "
;;  "DeepSkyBlue         " "DarkSeaGreen        " "DarkGrey            "
;;  "magenta             " "OrangeRed           ")
;;
;; ---------------- TRIAL ----------------
;; ("PaleGreen           " "yellow3             " "grey76              "
;;  "DarkSlateGray3      " "plum                " "orange2             "
;;  "salmon1             " "magenta             " "DeepSkyBlue3        ")
;;
;; ---------------- TRIAL 2 ---------------- 
;; ("CadetBlue1          " "DarkOliveGreen2     " "plum1               "
;;  "snow3               " "DarkGoldenrod1      " "chocolate1          "
;;  "MediumSeaGreen      " "IndianRed1          " "MediumOrchid2       "
;;  "DeepSkyBlue3        " "LightYellow4        ")
;;
;; ---------------- TRIAL 3 ----------------
;; ("DarkOliveGreen1     " "PaleTurquoise2      " "wheat               "
;;  "pink1               " "SpringGreen3        " "gold3               "
;;  "grey67              " "chocolate1          " "MediumPurple1       "
;;  "DodgerBlue          " "maroon1             " "brown1              ")
;;
;; ---------------- TRIAL 4 ----------------
;; ("DarkSeaGreen1       " "yellow2             " "NavajoWhite         "
;;  "cyan2               " "plum1               " "SkyBlue1            "
;;  "green3              " "DarkGrey            " "sienna2             "
;;  "MediumPurple1       " "DarkGoldenrod       " "VioletRed1          ")
;;
;; ---------------- TRIAL 5 ----------------
;; ("AntiqueWhite        " "GreenYellow         " "LightSteelBlue1     "
;;  "cyan3               " "plum                " "goldenrod           "
;;  "chocolate1          " "grey63              " "MediumSeaGreen      "
;;  "CornflowerBlue      " "magenta             " "brown1              ")

(provide 'my-highlighting-bg-colors)

;;; my-highlighting-bg-colors.el ends here

Conclusion

I suggest that any one who has ever went about picking colors for himself or others, must have a passing understanding of color spaces, and enthusiastically embrace libraries like color tools for Emacs. I also feel that the companion article Reasoning about colors not only sets the context for how one may use the ct library, but also talks about how a skilled artisan would go about this “color-picking” business. Unlike many other articles on color spaces, the above artilce is at once accessible and packs sufficient punch. Most importantly, it is by a fellow Emacs user.

As a long time Emacs user, I have across native libraries that picks colors analytically on the go. For example, the command M-x vc-annotate, paints the whole buffer in rainbow colors, and most notably the resulting buffer is pleasing to look at. Of course, they do it without the help of libraries like ct. This implies that one can travel far with just the Emacs APIs, and not get so far as to install libraries like ct. The fine Emacs manual and the fine native Emacs Lisp libraries, however fine they may be, are dumb. They do not talk and have no stories to tell. But libraries like ct come with interesting stories, and it feels better to listen to a fellow human-being talk about tools of his trade. For these emotional reasons alone, one should embrace 3rd-party tools, and give Emacs the cold shoulder.

Leave a comment

search previous next tag category expand menu location phone mail time cart zoom edit close