Hugo: Designing with dynamic image colors
In a Nutshell
- I analyze image colors returned by Hugo’s
.Colors
method. - Then I use the lightest and darkest colors to set complementary border colors.
- The result is enhanced visual harmony between images and their surrounding content.
The Results
Here are several of the Forkful images as they appear using Hextra theme’s dark and light mode. The image borders are set to the darkest and lightest colors returned by RESOURCE.Colors
. I added test strips showing the full output:
Example 1
Example 2
Example 3
The test strips look so nice, I think I’m going to add them to pages as design elements.
How I do it
I’ll describe it from the top-down.
1. Displaying an image
Here’s the kind of code I write in the templates to display an image.
It’s already been saved in the variable $image
from an earlier call to resources.Get
:
{{ $colors := partial "func/colors/LightestAndDarkest" $image.Colors }}
{{ $dark := index $colors "darkest" }}
{{ $light := index $colors "lightest" }}
<img data-dark = "{{ $dark }}"
data-light = "{{ $light }}"
style = "border: 2px solid {{ $dark }}"
src = "{{ $image.Permalink }}">
You can see, I call a “function” (a partial ending with a return
statement) to get the lightest and darkest colors. Then I use the colors to create the border. I also save them as data-
attributes for later use in JavaScript:
const setDarkTheme = () => {
// ...
document.querySelectorAll("img.prog-lang").forEach(function(img) {
img.style.border = "2px solid " + img.dataset.dark;
});
}
const setLightTheme = () => {
// ...
document.querySelectorAll("img.prog-lang").forEach(function(img) {
img.style.border = "2px solid " + img.dataset.light;
});
}
This could really use some refactoring.
2. Calculating the lightest and darkest colors
I wrote two partials for this. Here’s the file layout:
- Luminance.html
- LightestAndDarkest.html
My current style is to create folders named after the type of input each partial expects. So, func/color
is for functions that take a color string as input.
LightestAndDarkest.html
Now, this first one: what can I say, it’s not the most beautiful code. I’m new to Hugo template programming. And I wasn’t able to find a way to sort a list of colors by their luminance in Hugo’s templating language. I.e., in Ruby I’d do something like this:
sorted_colors = colors.sort_by { |c| luminance(c) }
light_and_dark = { darkest: sorted_colors.first, lightest: sorted_colors.last }
But for the Hugo template I had to write this procedural code in a loop:
{{/*
Given a list of colors, return the lightest and darkest.
*/}}
{{ $darkest_color := "" }}
{{ $darkest_lum := 1 }}
{{ $lightest_color := "" }}
{{ $lightest_lum := 0 }}
{{ with . }}
{{ range . }}
{{ $luminance := partial "func/color/Luminance" . }}
{{ if lt $luminance $darkest_lum }}
{{ $darkest_color = . }}
{{ $darkest_lum = $luminance }}
{{ end }}
{{ if gt $luminance $lightest_lum }}
{{ $lightest_color = . }}
{{ $lightest_lum = $luminance }}
{{ end }}
{{ end }}
{{ end }}
{{ return dict "lightest" $lightest_color "darkest" $darkest_color }}
Maybe there’s a cleaner way?
Luminance.html
This was a fun one to write. I found the formula for luminance on Stack Overflow that pointed to the original excellent article by Darel Rex Finley. It’s a weighted sum of the RGB components. Here’s my translation into Hugo template syntax:
{{/*
Input is expected to be a CSS-style hex color string (e.g. #RRGGBB).
Output is the luminance from 0 to 1.
Luminance = sqrt( 0.299*R^2 + 0.587*G^2 + 0.114*B^2 )
See: https://alienryderflex.com/hsp.html
https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color
https://gohugo.io/methods/resource/colors/
*/}}
{{ $r := (printf "0x%s" (substr . 1 2)) | int }}
{{ $g := (printf "0x%s" (substr . 3 2)) | int }}
{{ $b := (printf "0x%s" (substr . 5 2)) | int }}
{{ $r_norm := div $r 255.0 }}
{{ $g_norm := div $g 255.0 }}
{{ $b_norm := div $b 255.0 }}
{{ return (math.Sqrt (add (pow $r_norm 2 | mul 0.299) (pow $g_norm 2 | mul 0.587) (pow $b_norm 2 | mul 0.114))) }}
And that’s it. Really just three small pieces of code. if you don’t have light/dark switching, it’s even simpler. I hope this helps you in your Hugo projects. I’m still learning, so if you have any suggestions, please let me know. Thanks for reading!