Clojureでイメージファイルのリサイズ

思ったより処理速度早かった。リアルタイムな処理中でも使えそう。
出力はJpegだけですが、ちょっといじれば拡張できます。ImageIOが対応している形式なら出力できると思う。

(ns resize-image
  (:import [java.awt.image BufferedImage]
	   [java.awt Image]
	   [javax.imageio IIOImage ImageIO ImageWriteParam]
	   [javax.imageio.plugins.jpeg JPEGImageWriteParam]))

(defn- read-image [file-path]
  (ImageIO/read (java.io.File. file-path)))

(defn- get-jpeg-param []
  (doto (JPEGImageWriteParam. (java.util.Locale/getDefault))
    (.setCompressionMode ImageWriteParam/MODE_EXPLICIT)
    (.setCompressionQuality 1.0)))

(defn- get-jpeg-writer []
  (.. (ImageIO/getImageWritersByFormatName "jpg")
      next))

(defn- write-jpeg-image [file-path image]
  (let [param (get-jpeg-param)
	file (java.io.File. file-path)
	out (ImageIO/createImageOutputStream file)]
    (doto (get-jpeg-writer)
      (.setOutput out)
      (.write nil (IIOImage. image nil nil) param)
      (.dispose))
    (doto out (.flush) (.close))
    file))

(defn #^{:doc "イメージデータのリサイズ.
  アスペクト比を保ったままリサイズを行う.
  src-image: リサイズ対象のImageオブジェクト.
  rate: 拡大縮小率."}
  resize-image [src-image rate]
  (let [width (* rate (.getWidth src-image))
	height (* rate (.getHeight src-image))
	dest-image (BufferedImage.
		    width height (.getType src-image))
	scaled-image (.getScaledInstance src-image
					 width height
					 Image/SCALE_AREA_AVERAGING)]
    (doto (.getGraphics dest-image)
      (.drawImage scaled-image 0 0 width height nil))
    dest-image))


(defn #^{:doc "イメージデータのリサイズ及び、ファイルへの書き出し.
  イメージの長辺を基準にリサイズを行う.幅方向の方が長ければ out-width,
 高さ方向の方が長ければ out-height にあわせてイメージのリサイズを行い,
 out-fileへ書き出す。評価値はイメージを書き出したFileオブジェクト."}
  resize-image-file [src-file out-file out-width out-height]
  (let [[out-width out-height] (map float [out-width out-height])
	image (read-image src-file)
	[width height] ((juxt #(.getWidth %) #(.getHeight %)) image)
 	rate (if (< width height)
	       (/ out-height height) (/ out-width width))
	resized-image (resize-image image rate)]
    (write-jpeg-image out-file resized-image)))

余談だけど、emacs + clojure-mode で関数のメタデータ書くとその後に続く「関数名」と「関数内の最初のS式」のインデントが同じ高さになってしまうな。ちょっと気持ち悪い。