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.
(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.
(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.
(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
This 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!
Leave a comment: