Sebastian Morawietz

Software Developer

Learning Clojure - Getting started

Without further ado, let's get something running. Some tools are assumed to be installed on your machine:

Building Clojure

Let's check out the latest version from Google code and run it

development $ svn co http://clojure.googlecode.com/svn/trunk clojure
development $ cd clojure
clojure $ ant

This builds the clojure.jar. Let's check, if everything worked fine by launching a Repl (read eval print loop).

clojure $ java -cp clojure.jar clojure.lang.Repl
Clojure 1.1.0-alpha-SNAPSHOT
user=> (println "Hello World")
Hello World
nil

Looks good. The Repl works as expected and we can check "Hello World" off our list.
We should also get the clojure-contrib package. It's stuffed with goodies and you're going to need it when you want to unit-test your code

clojure $ cd ..
development $ svn co http://clojure-contrib.googlecode.com/svn/trunk \ 
 clojure-contrib
development $ cd clojure-contrib
clojure-contrib $ ant -Dclojure.jar ../clojure/clojure.jar
clojure-contrib $ cd ../clojure

Theoretically we now got everything we need to run Clojure programs, but it's pretty clumsy to type all those classpath options java needs by hand. So let's create a clj-command to do the work for us.

clojure-contrib $ cd ../clojure
clojure $ mkdir bin
clojure $ cd bin
clojure $ touch clj
clojure $ chmod a+x clj

Open the new file clj with the text editor of your choice and enter the following

#!/bin/bash

#Don't forget to replace this with your actual path
BASE_PATH=~/development

CLOJURE_DIR=$BASE_PATH/clojure
CLOJURE_JAR=$CLOJURE_DIR/clojure.jar

CLOJURE_CONTRIB_DIR=$BASE_PATH/clojure-contrib
CLOJURE_CONTRIB_JAR=$CLOJURE_CONTRIB_DIR/clojure-contrib.jar

java -cp $CLOJURE_JAR:$CLOJURE_CONTRIB_JAR:. clojure.lang.Script $1 -- $*

Of course we want our clj command to be available everywhere, so as a last step let's open ~/.bashrc and add our newly created bin-folder to the $PATH-Variable

export PATH=$PATH:/usr/sbin:/sbin:~/development/clojure/bin

After starting a new bash session, we can issue the clj-command on any Clojure file - just like we know it from ruby or python.

Starting the Raytracer project

Okay. Time for doing what we're here for: Rendering pretty pictures! I'm going to call the raytracer cray (Clojure Raytracer).

development $ mkdir cray
development $ cd cray

First of all we need colors - and lots of 'em. We store the red, green and blue components as float values between 0.0 and 1.0.

color.clj
(ns color)

(defstruct color :r :g :b)

(defn make-color
  "Constructs a color out of its red, green and blue components"
  [r g b]
  (struct color (float r) (float g) (float b) ) )

(defn color-to-rgb
  "Converts the color into a 32-Bit integer compatible with
   java.awt.image.BufferedImage.TYPE_INT_RGB"
  [clr] 
  (+ (bit-shift-left (int (* (:r clr) 255) ) 16 ) 
     (bit-shift-left (int (* (:g clr) 255) ) 8 )
     (int (* (:b clr) 255) ) ) )

; Some predefined colors
(def black-color (make-color 0 0 0))
(def white-color (make-color 1 1 1))
(def red-color   (make-color 1 0 0))
(def green-color (make-color 0 1 0))
(def blue-color  (make-color 0 0 1))

Then we need an image structure that we can write our colors into. The process of raytracing is basically an iteration over all pixels of an image, solving a number of equations for each individual pixel with a color as the end result - so our image structure should make this easy. This is what the image-every-pixel-function is for:
It receives a function that it calls on every pixel, passing the function as parameters the image itself, the current coordinates and its width and height.

image.clj
(ns image
    (:use color))

(defstruct image :img-src)

(defn make-image
  "Constructs an image of w x h pixels"
  [w h]
  (struct image (new java.awt.image.BufferedImage w h
                     (java.awt.image.BufferedImage/TYPE_INT_RGB) ) ) )

(defn image-width
  [img]
  (.getWidth (:img-src img)) )

(defn image-height
  [img] 
  (.getHeight (:img-src img) ) )

(defn image-every-pixel
  "Iterates over every pixel in the image and calls func on it"
  [image func]
  (let [width  (image-width image)
        height (image-height image )]
    (doall (for [y (range height)]
             (doall (for [x (range width)] 
                      (func image x y width height ) ) ) ) ) ) )

(defn image-set-pixel!
  "Sets the pixel-color at position x,y"
  [image x y color]
  (doto (:img-src image) (.setRGB x y (color-to-rgb color ) ) ) )

(defn image-save
  "Saves the image to a given filepath"
  [image file-path]
  (let [file (new java.io.File file-path)]
    (javax.imageio.ImageIO/write (:img-src image) "png" file ) ) )

Well, these two structures are basically everything we need to produce images. As a last step for today, let's draw something simple.

cray.clj
(ns cray
    (:use color image))

(if (< (count *command-line-args*) 2 )
  (do
    (println "Usage:\nclj cray.clj ")
    (System/exit 0) ) )

(let [img (make-image 400 400)]
  (image-every-pixel 
   img
   (fn [img x y w h]
       (let [dx (- x (/ (image-width img) 2)) 
             dy (- y (/ (image-height img) 2))]
         (image-set-pixel!
          img
          x y
          (if (< (+ (* dx dx) (* dy dy) ) (* 150 150) )
            blue-color
            black-color ) ) ) ) )

  (image-save img (second *command-line-args* ) ) )

Okay, lets have go:

cray $ clj cray.clj out.png

blue circleThis produces a 400 by 400 pixels wide image containing a blue circle in the middle on a black background. How? We use image-every-pixel and pass it a function that paints a blue or black pixel, depending on whether the current pixel lies within a range of 150 pixels to the center of the image.

Thats it for today. We now have everything set up nicely, so next time we can do some actual raytracing. If you want to play with the sources rather than copy and paste snippets, you can check out the project from github. I'm going to update this repository every week but leave every step along the way intact as a branch.
Cheers!

3 comments
rklvon
rklvon
28 Oct 21:57

psjuwpkq
psjuwpkq
28 Oct 21:58

fzmopss
wcavqxo
14 Jan 18:04

Leave a comment: