|
1 | | -(ns clojureprogramming.mandlebrot |
| 1 | +(ns com.clojurebook.mandelbrot |
2 | 2 | (:import java.awt.image.BufferedImage |
3 | 3 | (java.awt Color RenderingHints))) |
4 | 4 |
|
5 | 5 | (defn- escape |
6 | 6 | "Returns an integer indicating how many iterations were required |
7 | 7 | before the value of z (using the components `a` and `b`) could |
8 | | - be determined to have escaped the Mandlebrot set. If z |
| 8 | + be determined to have escaped the Mandelbrot set. If z |
9 | 9 | will not escape, -1 is returned." |
10 | 10 | [a0 b0 depth] |
11 | 11 | (loop [a a0 |
|
18 | 18 | (+ b0 (* 2 (* a b))) |
19 | 19 | (inc iteration))))) |
20 | 20 |
|
21 | | -(defn mandlebrot |
| 21 | +(defn- fast-escape |
| 22 | + "A primitive-hinted variant of `escape` that can result in an |
| 23 | + order-of-magnitude performance improvement when used instead." |
| 24 | + [^double a0 ^double b0 depth] |
| 25 | + (loop [a a0 |
| 26 | + b b0 |
| 27 | + iteration 0] |
| 28 | + (cond |
| 29 | + (< 4 (+ (* a a) (* b b))) iteration |
| 30 | + (>= iteration depth) -1 |
| 31 | + :else (recur (+ a0 (- (* a a) (* b b))) |
| 32 | + (+ b0 (* 2 (* a b))) |
| 33 | + (inc iteration))))) |
| 34 | + |
| 35 | +(defn mandelbrot |
22 | 36 | "Calculates membership within and number of iterations to escape |
23 | | - from the Mandlebrot set for the region defined by `rmin`, `rmax` |
| 37 | + from the Mandelbrot set for the region defined by `rmin`, `rmax` |
24 | 38 | `imin` and `imax` (real and imaginary components of z, respectively). |
25 | 39 | |
26 | 40 | Optional kwargs include `:depth` (maximum number of iterations |
|
32 | 46 | the corresponding point escaped from the set. -1 indicates points |
33 | 47 | that did not escape in fewer than `depth` iterations, i.e. they |
34 | 48 | belong to the set. These integers can be used to drive most common |
35 | | - Mandlebrot set visualizations." |
| 49 | + Mandelbrot set visualizations." |
36 | 50 | [rmin rmax imin imax & {:keys [width height depth] |
37 | 51 | :or {width 80 height 40 depth 1000}}] |
38 | 52 | (let [rmin (double rmin) |
|
51 | 65 | depth))))))) |
52 | 66 |
|
53 | 67 | (defn render-text |
54 | | - "Prints a basic textual rendering of mandlebrot set membership, |
55 | | - as returned by a call to `mandlebrot`." |
56 | | - [mandlebrot-grid] |
57 | | - (doseq [row mandlebrot-grid] |
| 68 | + "Prints a basic textual rendering of mandelbrot set membership, |
| 69 | + as returned by a call to `mandelbrot`." |
| 70 | + [mandelbrot-grid] |
| 71 | + (doseq [row mandelbrot-grid] |
58 | 72 | (doseq [escape-iter row] |
59 | 73 | (print (if (neg? escape-iter) \* \space))) |
60 | 74 | (println))) |
61 | 75 |
|
62 | 76 | (defn render-image |
63 | | - "Given a mandlebrot set membership grid as returned by a call to |
64 | | - `mandlebrot`, returns a BufferedImage with the same resolution as the |
| 77 | + "Given a mandelbrot set membership grid as returned by a call to |
| 78 | + `mandelbrot`, returns a BufferedImage with the same resolution as the |
65 | 79 | grid that uses a discrete grayscale color palette." |
66 | | - [mandlebrot-grid] |
| 80 | + [mandelbrot-grid] |
67 | 81 | (let [palette (vec (for [c (range 500)] |
68 | 82 | (Color/getHSBColor 0.0 0.0 (/ (Math/log c) (Math/log 500))))) |
69 | | - height (count mandlebrot-grid) |
70 | | - width (count (first mandlebrot-grid)) |
| 83 | + height (count mandelbrot-grid) |
| 84 | + width (count (first mandelbrot-grid)) |
71 | 85 | img (BufferedImage. width height BufferedImage/TYPE_INT_RGB) |
72 | 86 | ^java.awt.Graphics2D g (.getGraphics img)] |
73 | | - (doseq [[y row] (map-indexed vector mandlebrot-grid) |
| 87 | + (doseq [[y row] (map-indexed vector mandelbrot-grid) |
74 | 88 | [x escape-iter] (map-indexed vector row)] |
75 | 89 | (.setColor g (if (neg? escape-iter) |
76 | 90 | (palette 0) |
77 | 91 | (palette (mod (dec (count palette)) (inc escape-iter))))) |
78 | 92 | (.drawRect g x y 1 1)) |
79 | 93 | (.dispose g) |
80 | 94 | img)) |
| 95 | + |
| 96 | +(defn- coerce-mandelbrot-args |
| 97 | + [args] |
| 98 | + (for [x args] |
| 99 | + (if (= \: (first x)) |
| 100 | + (keyword (subs x 1)) |
| 101 | + (try |
| 102 | + (if (.contains x ".") |
| 103 | + (Double/parseDouble x) |
| 104 | + (Long/parseLong x)) |
| 105 | + (catch NumberFormatException e |
| 106 | + (println "Invalid number" x)))))) |
| 107 | + |
| 108 | +(defn- print-usage [] |
| 109 | + (println "Mandelbrot set visualization from 'Clojure Programming', chapter 11.") |
| 110 | + (println "Please refer to documentation for com.clojurebook.mandelbrot/mandelbrot for information on what rmin, rmax, etc. mean.") |
| 111 | + (println) |
| 112 | + (println "Usage: lein run [:fast] output-path rmin rmax imin imax [:width XXX] [:height YYY] [:depth DDD]") |
| 113 | + (println " e.g.: lein run mandelbrot.png -2.25 0.75 -1.5 1.5 :width 800 :height 800 :depth 500") |
| 114 | + (println) |
| 115 | + (println "Using the :fast option will result in the primitive-optimized `fast-escape` function being used instead of the default (boxing) `escape`.")) |
| 116 | + |
| 117 | +(defn -main |
| 118 | + [& [output-path & opts]] |
| 119 | + (let [args (coerce-mandelbrot-args opts)] |
| 120 | + (when (or (not output-path) |
| 121 | + (seq (filter nil? args)) |
| 122 | + (not (even? (count args)))) |
| 123 | + (print-usage) |
| 124 | + (System/exit 1)) |
| 125 | + (javax.imageio.ImageIO/write |
| 126 | + (render-image (apply mandelbrot args)) |
| 127 | + "png" (java.io.File. output-path)) |
| 128 | + (System/exit 0))) |
| 129 | + |
| 130 | +(defn -fast-main |
| 131 | + "Same as -main, but uses `with-redefs` to replace `escape` with its |
| 132 | + optimized variant `fast-escape`." |
| 133 | + [& args] |
| 134 | + (with-redefs [escape fast-escape] |
| 135 | + (apply -main args))) |
0 commit comments