Saturday, 27 October 2012

Creating Bitmaps from Lisp

Having learned about programming computers later in my education (during university), the internals of computers and binary file formats have been very mysterious to me. Text files (and files with a text format) were usually understandable (or conceivably understandable), but binary files were impenetrable to me. At the time, I had no understanding of bytes, let alone concepts such as little/big-endianness. So, to get a feel for how binary file formats work, I wrote a bitmap writer function.

As reference, I used the Wikipedia page about the BMP file format. The code is released under a BSD license and is available for download here. It can make 24-bit colour bitmaps. It also contains some functions to manipulate colours. Bitmaps are created by calling a write-bmp function.

It was an interesting experience to work with binary streams, and I learned a lot. The code's not that nice, and probably not that efficient either, but it does what I want, which is output pictures.

One of the arguments to write-bmp is used to determine the colour of each pixel, and can be either a list of lists, or a function of two arguments. Here's a call that produces a twelve colour diagonal rainbow:
(write-bmp (path "bitmaps/diagonal-rainbow3.bmp") 80 80
  #'(lambda (row col) (rainbow12 (/ (+ row col) 2))))

The function rainbow12 selects a colour based on the floor of it's input mod 12. The above call produces*:
Dyeagonal rainbow!
Here's a fun example using MathP:
(write-bmp (path "bitmaps/picture-perfect.bmp") 80 80
    #M(fn(r c)
       {applR = 2.5
        inRad(x y rad) = (c-x)^2 + (r-y)^2 < rad^2
        if inRad(15 65 6) yellow; inner sun
        if inRad(15 65 8) orange; outer sun
        if inRad(48 55 applR) || inRad(65 45 applR) || inRad(52 40 applR) red
        if (r-50)^2+(c-55)^2<15^2 darkgreen
        if r<20+3*cos(c/10) green; grass
        if 50<c<60 && r<50 brown blue})

It produces*:
But since computers are good at calculations, let's try something a little more intense (and random, and ... psychedelic):
(write-bmp (path "bitmaps/trigonometric2.bmp")
    360 300
    #'(lambda (row col)
            #M{(row^1.1 + 0.6*col + 35*cos(col/15)*sin(row/16)
                + 2 * sqrt((row-250)^2 + (col-200)^2)
                + 20 * cos((col-0.3*row)/70) * (2-sin(row/80))
                + 20 * (1-cos(0.7+col/25)) * cos(0.6-row/50))/15}

I hope your eyes aren't bleeding too much after that one. ;-)

I've also implemented some anti-aliasing, here's are some anti-aliased pictures:
The last one above is an attempt at creating a 'world' map with things like seas (blue), beaches (yellow), vegetation (green) and mountains (brown and white). The jump from vegetation to beach is sharp, but the anti-aliasing smooths it nicely.

* I've converted all of the pictures displayed here to PNG format using Paint.Net. It feels like cheating, but I was able to get things done. One of these days I'll look into ZPNG and Salza2. The compression used in the PNG format seems very mysterious to me.

No comments:

Post a Comment