I will conclude the current series of posts about doing graphics programming in Clojure using Java 2D by making a compilation of all the basic shapes, but I expect I will be talking a lot about computer graphics topics a lot from now on.
An image creating macro
I will now be creating a wide variety of images all at once. That would yield too much boilerplate if I didn't first declare a macro.
(defmacro argb-image
[[width height] & body]
(let [image-name (gensym "img")
graphics-name (gensym "graphics")]
`(let [~image-name (BufferedImage. ~width ~height BufferedImage/TYPE_INT_ARGB)
~graphics-name (.createGraphics ~image-name)]
(doto ~graphics-name
~@body)
~image-name)))
Most of what we want to do to images can be encapsulated in a single doto block applied to its graphics. This macro encapsulates all that.
Creating the images
I ported the basic regular polygon creation function from the Java we saw previously to Clojure.
(defn create-polygon
[vertices offset rect]
(let [step (/ (* 2 Math/PI)
vertices)
x (int-array vertices)
y (int-array vertices)
xradius (/ (.-width rect) 2)
yradius (/ (.-height rect) 2)]
(dotimes [i vertices]
(let [current-x (+ (.-x rect) xradius (int (* (Math/cos (+ offset (* i step))) xradius)))
current-y (+ (.-y rect) yradius (int (* (Math/sin (+ offset (* i step))) yradius)))]
(aset x i current-x)
(aset y i current-y)))
(Polygon. x y vertices)))
This enables us to now create our array of basic images created with Clojure.
(def line-image
(argb-image
[100 100]
(.setColor Color/BLACK)
(.drawLine 0 0 100 100)))
(def square-image
(argb-image
[100 100]
(.setColor Color/RED)
(.fill (new Rectangle 10 10 80 80))))
(def circle-image
(argb-image
[100 100]
(.setColor Color/BLUE)
(.fill (new Ellipse2D$Double 0 0 100 100))))
(def rectangle-image
(argb-image
[100 100]
(.setColor Color/ORANGE)
(.fill (new Rectangle 10 30 80 40))))
(def right-triangle-image
(let [p (new GeneralPath)]
(.moveTo p 0.0 0.0)
(.lineTo p 100.0 100.0)
(.lineTo p 0.0 100.0)
(.lineTo p 0.0 0.0)
(.closePath p)
(argb-image
[100 100]
(.setColor Color/GREEN)
(.fill p))))
(def trapezoid-image
(let [p (new GeneralPath)]
(.moveTo p 0.0 100.0)
(.lineTo p 100.0 100.0)
(.lineTo p 70.0 0.0)
(.lineTo p 30.0 0.0)
(.lineTo p 0.0 100.0)
(argb-image
[100 100]
(.setColor Color/CYAN)
(.fill p))))
(def ellipse-image
(argb-image
[200 100]
(.setColor (new Color 0xBCD4E6))
(.fill (new Ellipse2D$Double 0 0 200 100))))
(def ring-image
(let [core-circle (new Ellipse2D$Double 0 0 100 100)
subcircle (new Ellipse2D$Double 20 20 60 60)
area (new Area core-circle)]
(.subtract area (new Area subcircle))
(argb-image
[100 100]
(.setColor Color/MAGENTA)
(.setStroke (new BasicStroke 5))
(.fill area)
(.draw area))))
(def polygon-image
(let [polygon (Polygon.
(int-array [40 100 80 90 55 10 0])
(int-array [100 80 60 10 5 30 50])
7)]
(argb-image
[100 100]
(.setColor Color/ORANGE)
(.fill polygon))))
(def cross-image
(let [polygon (Polygon.
(int-array [20 80 80 100 100 80 80 20 20 0 0 20] )
(int-array [100 100 80 80 20 20 0 0 20 20 80 80])
12)]
(argb-image
[100 100]
(.setColor Color/ORANGE)
(.fill polygon))))
(def diamond-image
(let [polygon (Polygon.
(int-array [50 100 50 0])
(int-array [100 50 0 50])
4)]
(argb-image
[100 100]
(.setColor Color/GRAY)
(.fill polygon))))
(def semicircle-image
(argb-image
[100 100]
(.setColor Color/BLUE)
(.fillArc 0 25 100 100 180 -180)))
(def pentagon-image
(let [polygon (create-polygon 5 0 (new Rectangle 0 0 100 100))]
(argb-image
[100 100]
(.setColor Color/RED)
(.fill polygon))))
(def hexagon-image
(let [polygon (create-polygon 6 0 (new Rectangle 0 0 100 100))]
(argb-image
[100 100]
(.setColor (Color. 0xBFFF00))
(.fill polygon))))
(def octagon-image
(let [polygon (create-polygon 8 0 (new Rectangle 0 0 100 100))]
(argb-image
[100 100]
(.setColor (Color. 0xEEDC82))
(.fill polygon))))
(def pic-image
(argb-image
[100 100]
(.setColor Color/BLACK)
(.fillArc 0 0 100 100 20 320)))
As is this is a lot of images being created we might as well display them all at once.
Displaying the images
In order to display all the images at once I will create a JFrame and then put each image in a ImageIcon of JLabel, laying them out using a GridLayout. In particular, this uses the constructor for GridLayouts with two extra arguments so we can have added padding.
(def image-grid
[[line-image right-triangle-image rectangle-image cross-image]
[square-image circle-image pentagon-image diamond-image]
[trapezoid-image ring-image polygon-image semicircle-image]
[hexagon-image octagon-image pic-image ellipse-image]])
(defn display-image-grid!
[images]
(let [frame (JFrame. "Image grid")
panel (JPanel.)]
(.add frame panel)
(.setLayout panel (new GridLayout (count images) (count (first images)) 10 10))
(doseq [image-row images]
(doseq [image image-row]
(.add panel (new JLabel (new ImageIcon image)))))
(.pack frame)
(.setVisible frame true)))
This code produces a JFrame that looks like below.
There is so much more that can be done with graphics and Clojure. I will be talking more about graphical applications in the future and the key role the JVM can play in them making them.
No comments:
Post a Comment