The primary reason is the use of
BufferedImage. This is fine when you're loading an image from a file, but it's a big useless to go through a sequence setting each and every pixel with
When you've already got all the data in memory,
MemoryImageSourceis a much better bet. You can create one with a 1D array of pixel data (with a specified width/height).
ImageProducerinterface which means that any AWT derived component can use
createImageto make an
Firstly, I converted over the (x,y) set to just a list of numbers where (given the height and width) you can work out the corresponding pixel index, then I can just apply
pmap(see also Ray Tracing)to calculate all the pixels in parallel.
(defn calculate-pixels 
(let [pixels (range 0 (* *width* *height*))]
(pmap (fn [p]
(let [row (rem p *width*) col (int (/ p *height*))]
(get-color (process-pixel (/ row (double *width*)) (/ col (double *height*))))))
Next we need the interop to go from my sequence of pixel values to an integer array.
(defn simple-mandlebrot [w h]
(let [x (int-array (calculate-pixels))]
int-arrayconverts between a list of integers to a plain old Java array. I could probably use a Java Array for the whole thing, and not use seq, but I doubt this is an appreciable performance difference.
These changes have made a huge difference, instead of taking minutes to render a 512x512 image it takes a few seconds and all my CPU cores are occupied.