Search code examples
javaclojuregraphics2d

Graphics2D.drawString with float arguments from Clojure


I created a Clojure implementation of this Java sample code that draws a PNG image. My Clojure version is below. It works, but the Clojure compiler throws an error if I don't coerce drawString()'s arguments to integers. The API docs show that Graphics2D.drawString() accepts float arguments but its superclass, Graphics.drawString(), does not. How can I convince/trick/hint Clojure to use the float version of drawString()?

(defn render-image [message]
  (let [width 200 height 200
        bi (java.awt.image.BufferedImage. width height java.awt.image.BufferedImage/TYPE_INT_ARGB)
        font (java.awt.Font. "TimesRoman" java.awt.Font/BOLD 20)
        ig (doto ^java.awt.Graphics2D (.createGraphics bi) (.setFont font))
        metrics (.getFontMetrics ig)
        str-width (.stringWidth metrics message)
        str-height (.getAscent metrics)]
    (doto ig
      (.setPaint java.awt.Color/BLACK)

      ;; Compiler complains if x- and y-arguments to .drawString
      ;; are not integers. Why?
      (.drawString message
                   (int (/ (- width str-width) 2))
                   (int (+ (/ height 2) (/ str-height 4)))))
    
    bi))

(defn -main [filename]
  (-> (render-image "Hello world")
      (javax.imageio.ImageIO/write "PNG" (java.io.File. filename))))


Solution

  • The runtime (not the compiler) must decide which of the overloads of .drawString to use. The set of conversions it is willing to automatically perform can be found in Reflector.java. Note that for a method parameter declared as float (as you wish to call), it accepts only float, Float, and double. But the result of dividing two integers is a rational number. So, the float method does not apply. And neither does the int method, because again there is no automatic conversion from rational to int.

    You can see the same thing happen in a much simpler case: (Float/valueOf (/ 1 2)).

    Since there is no automatic conversion, you must convert explicitly. You successfully did this with int, but apparently you don't want to call the int method. The solution is simple: convert with float instead.