Blending colors in Elisp28 Apr 2015
This is a slightly obscure feature that I've added today to ivy-mode. It's obscure, since very few packages give their candidates a face with a custom background. Still, when that happens, a conflict arises:
Which face to apply:
ivy-current-matchor the existing one?
In the image below, I've blended them both in a 50-50 mixture:
By the way, if you're wondering where the backgrounds come from in the
first place, it's lispy-goto
function (if you know what
imenu is, it's an advanced version of
that). This function collects all the Elisp tags in the current
directory, and offers to jump to them. The functions that have
interactive in them, are highlighted with
In the above screenshot, the first 7 functions are interactive, and the other 2 are not.
If you're interested in how color blending is done, I'll describe it shortly below.
First, a smaller and more clear example (I've used
(colir-blend (color-values "red") (color-values "blue")) ;; => "#800080"
colir-blend is a very small function that I'll describe below,
color-values is a function from faces.el that forwards to a C
xw-color-values with this doc:
Return a list of three integers, (RED GREEN BLUE), each between 0 and either 65280 or 65535 (the maximum depends on the system).
Which is a bit weird, since the last time I checked, the standard for colors was one-byte per channel, not two. Granted, it was about 10 years ago that I checked, and indeed, the entry on Wikipedia says:
High-end digital image equipment are often able to deal with larger integer ranges for each primary color, such as 0..1023 (10 bits), 0..65535 (16 bits) or even larger, by extending the 24-bits (three 8-bit values) to 32-bit, 48-bit, or 64-bit units
Apparently, Emacs is outfitted to deal high-end digital image equipment. But I'm still pretty sure that most graphics cards that you can buy only give 8 bits per each channel. Anyway, here's the code for the basic functions:
(defun colir-join (r g b) "Build a color from R G B. Inverse of `color-values'." (format "#%02x%02x%02x" (ash r -8) (ash g -8) (ash b -8))) (defun colir-blend (c1 c2 &optional alpha) "Blend the two colors C1 and C2 with ALPHA. C1 and C2 are in the format of `color-values'. ALPHA is a number between 0.0 and 1.0 which corresponds to the influence of C1 on the result." (setq alpha (or alpha 0.5)) (apply #'colir-join (cl-mapcar (lambda (x y) (round (+ (* x alpha) (* y (- 1 alpha))))) c1 c2)))
All pretty simple:
- Split each color into red, green, and blue integers with
- Compute a mean for each color channel.
- Build the new color by concatenating the hex components into a string.
In the example above:
- The result of
(65535 0 0).
- The result of
(0 0 65535).
- Their arithmetic mean is
(32768 0 32768).
- Divided by
ash, we get
(128 0 128).
- Converted to hex,
(128 0 128)is
(80 0 80).
- And the final color is
I hope that you've enjoyed this little venture into Elisp and colors.
People say rough things about Elisp sometimes, but hey: it offers
functions ranging from the assembly-level
ash, all the way up to
Photoshop-grade color channel handling.