Search code examples
clojurebufferedimagering

Serve a BufferedImage by a Ring handler


I'm looking for a way to let a ring server - by request - grab an image from an URL, process it and serve the modified version.

This is how I got so far:

(require '[clj-http.client :as client]
         '[ring.adapter.jetty :refer [run-jetty])
(import javax.imageio.ImageIO)

(def handler (-> (client/get "http://.../some-img.jpg" {:as :stream})
                 :body
                 ImageIO/read
                 .. ;; do some processing with the BufferedImage here
                 .. ;; and serve the modified version))

(run-jetty handler {:port 55555})

Especially I'm having troubles performing the last step inside the threading macro.


Solution

  • To return bytes as the response in Ring you need to provide either java.io.File or java.io.InputStream as the body content:

    (defn jpeg-response [image-data]
      (-> image-data
        (ring.util.response/response)
        (ring.util.response/content-type "image/jpeg")))
    

    I haven't found a way to obtain an InputStream from BufferedImage directly without creating an intermediate byte arrays. Maybe this is a limitation of the Java Image API due to complexity required to implement a "pull" approach to get a stream of image bytes in the desired format.

    Instead there is a "push" API where ImageIO.write method requires some kind of output for image bytes. It might be a java.io.File or java.io.OutputStream. Thus you need to first store the bytes somewhere (java.io.File or java.io.ByteArrayOutputStream) and then use them as your response body:

    With file (so storing the image on the disk first - you need to remember to clean it up):

    (let [image (ImageIO/read "...")
          image-file (java.io.File. "...")]
      (ImageIO/write image "jpg" image-file)
      (jpeg-response image-file))
    

    In-memory byte array:

    (let [image (ImageIO/read "...")
          image-output-stream (ByteArrayOutputStream.)]
      (ImageIO/write image "jpg" image-output-stream)
      (jpeg-response (ByteArrayInputStream. (.toByteArray image-output-stream))))