Parametrically Defined Wallpaper

If there's one thing that defines how I work, it's that I'd rather deal with hard numbers than trust my hand at drawing things. The only creative software I've ever had success with is FreeCAD, because it lets you sketch out drawings and then constrain them to specific sizes. Unfortunately Emacs doesn't have a wallpaper-mode that I know of.

I like my wallpapers simple, and with minimal distractions. A solid background, with a small pattern of box-shadowed cubes in the top-right. Here's an example, scaled down slightly. I made the original in Inkscape, and spent far too much time making something so simple.

Coding a solution

FreeCAD is a parametric modeller. It's shapes are defined by easily adjustable numbers. The most touted example is if you model a wheel, and you want to adjust the number of spokes in that wheel, you change the spoke number. You don't have to worry about remodelling anything, because you told FreeCAD to repeat a shape around a given axis, and all you're doing is telling it to add more of that shape. I decided to take that idea and use it to make a small program to generate myself a parametrically defined wallpaper. You can find the result here

The first thing I did was define the parameters. I'm a Racket guy, so I did so in an s-expression:

(define options
  `((width . 1920) ; screen width
    (height . 1080) ; screen height
    (horizontal-margin . 0) ; push pattern to left this much
    (vertical-margin . 0)) ; push pattern down this much

; Usage example
(assoc 'width options)
; = (width . 1920)

(cdr (assoc 'width options))
; = 1920

This defines a variable called 'options' as a list of lists containing key-value pairs. We've got keys to control the size of the resulting wallpaper, and keys to move the pattern in the top-right away from the sides a bit if they get covered by a bar or dock. How convenient.

From there, I followed the following algorithm: – Draw the background as a solid rectangle of defined width and height – Draw a single cube – Draw a cube under that cube to make a drop shadow – Stick the cubes together to make the pattern – Put this pattern on the background, taking the margins into account – Output as a .png

Quirks and Considerations

Every procedure in the code related to drawing takes the options variable as an argument. This is less efficient, because every called procedure has to look up the values they care about, but it means every part of the drawing process can be run independently just by giving it the list of options. The entire thing runs in a second or two anyway, so this isn't much of a concern.

Racket's pict library defines a (freeze _) procedure, which takes a generated pict and turns it into a bitmap. This means that the program only has to generate the pict once, after which it just reuses the result.

The most annoying part of coding something involving colour is the difference between British English and American English. I decided to use the American spelling throughout the code, because having a color function adjust colours would be too confusing.

Improvements

Once I had the wallpaper generator working, I exposed more of it to the options variable. I added the ability to adjust the opacity of the shadows, the size of the cubes, the size of the gap between the cubes and the colour palette.

The colour palette was the most interesting one. I 'borrowed' my colours from Google's Material Design color palettes, but they define the colours in the more common #rrggbb format, and Racket only accepts colours as a list of 3 decimal numbers. I got fed up of using KCalc to manually convert each individual value to Racket's format, so I wrote this:

(define (->rgb s)
  (list
   (string->number (substring s 1 3) 16)
   (string->number (substring s 3 5) 16)
   (string->number (substring s 5 7) 16)))

; Usage example
(->rgb "#ffebee")
 ; = (255 235 238)

Conclusion

The wallpaper generator was a very interesting project to work with. It isn't the most advanced idea to implement, but it was fun to come up with and is convenient to use. Perhaps one day I'll write one that can output more than one pattern, but I'm satisfied with the results for now.