painting clouds with clojure

Over the 4th of July weekend I made this little program to generate images of clouds

 

(ns clouds.core
  (:gen-class)
  (:import [java.awt.image BufferedImage]
           [java.io File]
           [javax.imageio ImageIO]
           [javax.swing JPanel JFrame SwingUtilities]
           [java.awt Graphics Color Dimension RenderingHints]))

(def width 500)
(def height 500)
(def num-particles 1000000)
(def color-cache (atom {}))
(def output-image? true)

(defn- rand-between [min max]
  (+ (rand-int (- max min)) min))

(defn- make-gray-color [color-val alpha]
  (let [color-key (keyword (str color-val "-" alpha))
        cached-color (color-key @color-cache)]
    (if cached-color
      cached-color
      (let [^Color new-color (Color. color-val
                                     color-val
                                     color-val
                                     alpha)]
        (swap! color-cache assoc color-key new-color)
        new-color))))

(defn- paint-clouds [^Graphics graphics]
  (loop [n 0
         last-x (rand-int width)
         last-y (rand-int height)]
    (let [rand-op (if (< (rand) 0.5) inc dec)
          rand-axis (if (< (rand) 0.5) :vert :horiz)
          new-x (if (= rand-axis :horiz)
                  (rand-op last-x)
                  last-x)
          new-y (if (= rand-axis :vert)
                  (rand-op last-y)
                  last-y)
          rand-gray (rand-between 250 255)
          rand-alpha (rand-int 75)
          neighbor-alpha-modifier 0.11
          particle-color (make-gray-color rand-gray rand-alpha)
          neighbor-color (make-gray-color rand-gray
                                          (int (* rand-alpha
                                                  neighbor-alpha-modifier)))]
      (doall
       (for [x-offset (range -1 2)
             y-offset (range -1 2)
             :let [x (+ new-x x-offset)
                   y (+ new-y y-offset)]
             :when (and (<= 0 x width)
                        (<= 0 y height)
                        (or (= x-offset y-offset 0)
                            (not= x-offset y-offset)))]
         (let [^Color color (if (= x-offset y-offset 0)
                              particle-color
                              neighbor-color)]
           (doto graphics
             (.setColor color)
             (.drawLine x y x y)))))
      (when (< n num-particles)
        (recur (inc n) new-x new-y)))))

(defn- painter []
  (proxy [JPanel] []
    (paint [^Graphics graphics]
      (let [^int width (proxy-super getWidth)
            ^int height (proxy-super getHeight)]
        (doto graphics
          (.setRenderingHint RenderingHints/KEY_ANTIALIASING
                             RenderingHints/VALUE_ANTIALIAS_ON)
          (.setRenderingHint RenderingHints/KEY_INTERPOLATION
                             RenderingHints/VALUE_INTERPOLATION_BICUBIC)
          (.setColor (Color. 135 206 250))
          (.fillRect 0 0 width height))
        (paint-clouds graphics)))))

(defn- gen []
  (let [^JPanel painting-panel (painter)
        ^Dimension dim (Dimension. width height)]
    (doto painting-panel
      (.setSize dim)
      (.setPreferredSize dim))
    (if output-image?
      (let [^BufferedImage bi (BufferedImage. width
                                              height
                                              BufferedImage/TYPE_INT_ARGB)
            ^Graphics graphics (.createGraphics bi)]
        (.paint painting-panel graphics)
        (ImageIO/write bi "png" (File. (str "output/"
                                            (System/currentTimeMillis)
                                            ".png"))))
      (let [^JFrame frame (JFrame. "clouds")]
        (.add (.getContentPane frame) painting-panel)
        (doto frame
          (.pack)
          (.setVisible true))))))

(defn -main
  [& args]
  (gen))