Short explanation of what the code is doing is interwoven with the code here. The code without the comments (for easy copy/paste'ing) is at bottom.
First, we import a few Java classes:
(require 'clojure.java.io)
(import [javax.imageio IIOImage ImageIO]
[javax.imageio.plugins.jpeg JPEGImageWriteParam])
(try
Then we get the default JPEG image reader and writer:
(let [image-reader (.next (ImageIO/getImageReadersByFormatName "jpg"))
image-writer (.next (ImageIO/getImageWritersByFormatName "jpg"))]
Now we open up streams to the input and output JPEG Files. Note that
with-open
will close the streams for you, so you don't have to later.
(with-open [image-input-stream (ImageIO/createImageInputStream
(clojure.java.io/file "path/to/inputFile.jpg"))
image-output-stream (ImageIO/createImageOutputStream
(clojure.java.io/file "path/to/outputFile.jpg"))]
Next, mate the reader/writer to the respective streams:
(.setInput image-reader image-input-stream)
(.setOutput image-writer image-output-stream)
Then we'll get the JPEG input file into a container that will also contain the metadata:
(let [iio-image (IIOImage. (.read image-reader 0) nil
(.getImageMetadata image-reader 0))
Now set up the JPEG quality (ie, compression) level desired. Here it's set to 0.7 (where 1 is highest quality, and 0 is highest compression):
jpeg-params (doto (.getDefaultWriteParam image-writer)
(.setCompressionMode JPEGImageWriteParam/MODE_EXPLICIT)
(.setCompressionQuality 0.7))]
Finally we get to write out the JPEG file with the new compression level:
(.write image-writer nil iio-image jpeg-params)))
Lastly, make sure to clean up the reader/writer's so they don't continue to hog system resources (yes, you have to do this even though Java has garbage collection):
(finally
(do (.dispose image-writer)
(.dispose image-reader)))))
To recap, here it is again without the comments, just the bare code:
(require 'clojure.java.io)
(import [javax.imageio IIOImage ImageIO]
[javax.imageio.plugins.jpeg JPEGImageWriteParam])
(try
(let [image-reader (.next (ImageIO/getImageReadersByFormatName "jpg"))
image-writer (.next (ImageIO/getImageWritersByFormatName "jpg"))]
(with-open [image-input-stream (ImageIO/createImageInputStream
(clojure.java.io/file "path/to/inputFile.jpg"))
image-output-stream (ImageIO/createImageOutputStream
(clojure.java.io/file "path/to/outputFile.jpg"))]
(.setInput image-reader image-input-stream)
(.setOutput image-writer image-output-stream)
(let [iio-image (IIOImage. (.read image-reader 0) nil
(.getImageMetadata image-reader 0))
jpeg-params (doto (.getDefaultWriteParam image-writer)
(.setCompressionMode JPEGImageWriteParam/MODE_EXPLICIT)
(.setCompressionQuality 0.7))]
(.write image-writer nil iio-image jpeg-params)))
(finally
(do (.dispose image-writer)
(.dispose image-reader)))))
I should warn you that I haven't tested the above code exactly as it is. I factored it into pieces so that I could use it in what I wrote differently, but the above is the smallest coherent example, I suppose. So you'll have to adapt the above. Eg, the
require
and import
should be in the (ns ...)
.Hope that helps!
(Edit: Fixed a few minor errors, including one pointed out by klang in comments below.)
3 comments:
I have tested the code and wrapped it up in a gist .. am I missing something about the use of 'finally' in the clojure program? The image-reader/writer variables are out of scope when finally is called.
This gist represent a testet version of the code above.
You are absolutely right about image-reader/writer going out of scope. I just realized there's a few other minor mistakes.
Sorry!! Thanks for making the tested version.
I'm going to have to fix this post and/or put up a link to the gist later.
Post a Comment