mathr / blog / #

Making seasonal cards with Snowglobe

You may remember Snowglobe, a screensaver alike graphical demo of generative fractal snowflakes interacting in a particle system. This week I uploaded two new versions to Hackage, snowglobe-2.0.0.2 which is compatible with the latest Haskell OpenGL libraries, and snowglobe-3 which removes an awkward dependency on hmatrix (replaced with simple small matrix maths copied from an old project of mine) and adds a new feature: saving each generated snowflake to a file (hit shift-S to activate and deactivate saving the files to the current working directory). Which makes snowglobe-2.0.0.2 obsolete already. You can get the latest (when you have ghc and cabal-install available) by:

cabal update && cabal install snowglobe

Having the flake images makes it possible to do all kinds of other fun stuff. They look like this:

I thought it would be nice to send all my neighbours a unique home made seasonal card, using these flake images. I got some card blanks from a local shop, they are 5x7 inches when folded, and from experience my printer doesn't print right to the edge (and margins are good too for aesthetic reasons), so I thought a 4x6 grid of flakes each 1 inch square would be nice.

The flake images are saved as PGM, part of the NetPBM family of formats, also known as PNM. There's a large suite of command line tools to do all kinds of image processing, here's the one's I'll be using:

pngtopnm
convert PNG to PNM, fairly self explanatory.
pnmcat
stack images next to each other, either from top to bottom (-tb) or left to right (-lr).
pnmsplit
takes a stream of PNM images and writes each to a separate file.
pgmtoppm
I use it to invert the colours, but it gives PPM (RGB) output, so...
ppmtopgm
...this converts RGB back to greyscale.
pnmgamma
gamma adjustment, 0.25 makes it darker.
pnmscale
the flakes are 1024px square, so we've been working at 1024dpi - but the output should be 300dpi, with fewer pixels
pnmtopng
converts to PNG, -force makes the PNG greyscale instead of possibly indexed palette, -interlace makes the PNG interlaced (useful for previewing over a slow network connection, mainly), and the -phys flag sets the DPI (300dpi is 11811 pixels per metre).
xargs
not a NetPBM package at all, but I use it here to group same-sized batches of input lines into the pnmcat command

Here's the whole process, maybe run it one line at a time in case something explodes, you need a fair bit of free disk space too (all the NetPBM formats are uncompressed, each flake is 1MB and there are multiple copies in the temporary files):

#!/bin/bash
mkdir cards
cd cards
gimp
# make 5120x512  black bakcground greyscale image with no alpha > edge.png
# make 1500x2100 white background greyscale image with no alpha > back.png
snowglobe
# hit shift-S to start saving images to current directory, q to quit
# in another window keep track of how many files there are
# you need 24 per card and a few extra because:
geeqie
# delete any you find ugly, there's always a few huge dense ones...
pngtopnm < edge.png > edge.pgm
pngtopnm < back.png > back.pgm
mkdir 4x1
ls snowglobe-*.pgm | xargs -n 4 pnmcat -lr | ( cd 4x1 ; pnmsplit )
mkdir 4x6
ls 4x1/* | xargs -n 6 pnmcat -tb | ( cd 4x6 ; pnmsplit )
mkdir 5x7
cd 4x6
for i in *
do
  pnmcat -tb -black ../edge.pgm $i ../edge.pgm |
  pgmtoppm white-black | ppmtopgm | pnmgamma 0.25 |
  pnmscale -xysize 1500 2100 | pnmcat -lr ../back.pgm - |
  pnmtopng -force -interlace -phys 11811 11811 1 > ../5x7/$i.png
done
cd ../5x7
geeqie
# delete any you find ugly, often the last one is incomplete

# finally, print them - load up your printer with card blanks first
lp -d R220 -o landscape *.png

The final output images look something like this:

And when printed and folded, like this:

I hope my neighbours like them!