Decoupling rendering and colouring.
authorClaude Heiland-Allen <claude@mathr.co.uk>
Sat, 21 Dec 2013 03:10:43 +0000 (03:10 +0000)
committerClaude Heiland-Allen <claude@mathr.co.uk>
Sat, 21 Dec 2013 03:10:43 +0000 (03:10 +0000)
commit24e8b81a7b49818148e6a0c719e70ad53a8fca9c
treefc6083adc21522fb809eeda635a9de049d217307
parent702198f081c208957d77323fac0b713c131ab422
Decoupling rendering and colouring.

Unfortunately the colour scheme we chose looked quite bad, and it took
a few minutes to render all the images.  To reduce waiting time, we can
save the raw data from rendering to a file, and colour it later.

We rename our mandelbrot.c program to render.c, and strip out the colour
type handling.  We delete the render_grey() from mandelbrot_imp.c, and
rename render_colour() back to render(), using a colour mapping that
preserves information.  With 3 bytes, each with 8 bits, we can represent
24bit integers, enough range to represent over 16 million iterations.
The >> operator is bitwise shift right, here we use it to extract the
individual bytes from the escape time.  We rely on putchar() truncating
to the least significant byte in the resulting integer.

We write a new program to remap the colours in the PPM output from the
render program.  Our colour.c reads the image header from its standard
input using scanf(), which works a bit like printf() in reverse.  We
check that we read the header successfully by confirming that we parsed
two items, the width and height, and that the next character after the
maxval of 255 is a newline.  Then we loop over all the pixels, repacking
the RGB values into a single integer.

We now use the HSV colour space, allowing us to specify colours by hue,
saturation and value.  Hue is circular, from red at 0, with increasing
values running through the rainbow yellow green blue violet, and then
back through purple and pink to red again at 1, where the cycle repeats.
Saturation blends colour intensity from greyscale at 0 to full blast at
1, and value controls brightness: black at 0 and full strength at 1.
We use single precision float for the calculations: 24bits is more than
enough, given each colour channel has only 8bits.  We need to scale the
output values from hsv2rgb() from [0..1] to [0..255], and we clamp them
to the output range to make sure no overflow glitches occur.

We add the new rules to the Makefile, and the new binaries to the
.gitignore file, and rebuild to check everything works:

    $ make
    $ time for exp in $(seq -w 0 15)
      do
        ./render \
          -1.759937295271429505091916757 \
           0.012591790526496335594898047 \
           2e-${exp} 2 > ${exp}-render.ppm
      done
    real    3m51.110s
    user    3m50.706s
    sys     0m0.288s
    $ time for i in ??-render.ppm
      do
        ./colour < ${i} > ${i}-colour.ppm
      done
    real    0m2.190s
    user    0m2.080s
    sys     0m0.072s
    $

As we suspected, rendering takes a lot longer than colouring, and having
separated the two processes we no longer have to rerender when we adjust
the colour scheme.
.gitignore
Makefile
colour.c [new file with mode: 0644]
mandelbrot.c [deleted file]
mandelbrot.h
mandelbrot_imp.c
render.c [new file with mode: 0644]