I’ll admit it, I used ImageMagick for my image manipulation instead of Java and I’ll show you how I did it.
I tried using the image tools plugin. It’s terrific. It is easy to install and simple to use. I couldn’t ask for anything more. The only problem is that the default interpolation algorithm (nearest neighbor) doesn’t create the greatest images when scaling. This is a JAI problem. So I did some trial and error and found that bicubic interpolation is much better; not great, but better. It was slow. This is a JAI problem. It got even slower when I tried a multi pass approach to scaling the image. It was just too slow for the image quality that it was producing. It seemed I had a problem with JAI.
On a past RoR project I used a plugin that facilitated file uploads on models. It had a built-in thumbnailing feature that used ImageMagick for image processing. I had a decent experience with it, so I decided to give it a shot.
Pros: It is noticeably faster , the image quality is superb, and it supports modifying and saving gifs straight up. You can even scale a jpeg and save it as a gif if you so desired. Note that I wasn’t using mediaLib which speeds up processing, though quality is actually the biggest sticking point.
Con: I’m interfacing ImageMagick using Groovy’s String.execute() method (a wrapper for ProcessBuilder). Not that this is an ImageMagick problem, as there is a JMagick library that uses JNI to interop. However, judging from some reports on the intartubes, there haven’t been too many success stories.
If your still interested here’s how it went down:
1. Installed ImageMagick
Downloaded and installed ImageMagick
2. Installed Hibernate Events Plugin
-
grails install-plugin http://thegioraproject.com/files/grails/plugins/grails-hibernate-events-0.2.zip
3. Created a generic Attachment Domain class
-
import grails.util.GrailsUtil
-
import org.springframework.web.multipart.MultipartFile
-
import org.codehaus.groovy.grails.commons.ConfigurationHolder
-
-
class Attachment {
-
//Allows us to have spring bind the file automatically. <input name="attachment.file" type="file" />
-
MultipartFile file
-
//Save file metadata to the database
-
-
-
-
-
//Don’t try to save the MultipartFile. Hibernate will barf all over you.
-
static transients = ["file"]
-
-
//Location for saving files i.e. /home/me/app_files/attachments/
-
def baseDir = { ConfigurationHolder.config.paperclip.files.dir[GrailsUtil.environment] }
-
-
//Use Hibernate Events Plugin to save the file after it’s metadata has been saved.
-
def afterInsert = {
-
writeFile()
-
}
-
-
//Create a unique directory to store the file based on the saved id. Produces a two deep
-
//directory to stave off the 32000 hard link limit on ext3 filesystem. So the path might look
-
//like 0000/0001/filename.extension
-
//see: http://thegioraproject.com/2008/03/02/workaround-for-subdirectory-limit-on-ext3-filesystem/
-
def directory = {
-
def stringId = id.toString()
-
def fullId = stringId.padLeft(8, "0")
-
[fullId[0..3], fullId[4..7]].join("/")
-
}
-
-
//Joins the directory and filename to create a path. Useful for creating links on views: can call
-
//attachment.path() to produce href.
-
def path = {
-
[directory(), fileName].join("/")
-
}
-
-
//Location on disk to write the file to.
-
def absolutePath = {
-
[baseDir(), path()].join("/")
-
}
-
-
//Writes the file to the disk making sure all parent directories are present
-
protected void writeFile() {
-
def destination =
new File(absolutePath
())
-
destination.mkdirs()
-
file.transferTo(destination)
-
}
-
-
//When Spring binds the file to the object we set the metadata for the file on the object.
-
//Eliminates some setter code.
-
public void setFile(MultipartFile upload) {
-
file = upload
-
contentType = upload.contentType
-
fileName = upload.originalFilename
-
size = upload.size
-
}
-
}
4. Created an Image Domain class
-
import grails.util.GrailsUtil
-
import org.codehaus.groovy.grails.commons.ConfigurationHolder
-
-
class Image extends Attachment
{
-
//Root directory of the ImageMagick install determined by the environment. So if we’re using
-
//development it will get the config variable paperclip.magick.dir.development from Config.groovy
-
def magickDir = { ConfigurationHolder.config.paperclip.magick.dir[GrailsUtil.environment] }
-
-
//After saving make sure we save the original by calling writeFile() on Attachment,
-
//then create the thumbnails
-
def afterInsert = {
-
writeFile()
-
createThumbnails()
-
}
-
-
//Location on disk to write the thumbnail to
-
def absoluteThumbnail = {type ->
-
[baseDir(), thumbnail(type)].join("/")
-
}
-
-
//Kind of like path on Attachment. Allows us to call attachment.thumbnail("300") on the view to
-
//produce an img src.
-
def thumbnail = {type ->
-
def tokens = fileName.tokenize(".")
-
def name = [tokens[0..-2].join("."), type, tokens[-1]].join(".")
-
[directory(), name].join("/")
-
}
-
-
//The meat of it all. It gets a little lost because there isn’t a lot of code. So we loop over
-
//an array to produce two thumbnails. One will be 100×100, the other will be 300×300. However
-
//ImageMagick will keep the images aspect ratio using the geometry we supplied. This method will
-
//create a string like "/ImageMagick/convert /path/image.jpg -thumbnail 100×100 /path/image.100.jpg"
-
//and then execute it as if you were on the command line. The ImageMagick command will resize and
-
//save the image to the supplied dir. The execute() call returns a process object which we call
-
//waitFor on to make sure the processing finishes before we move on. We can also get the exit code
-
//from the process to handle exceptions.
-
//More info ongeometry flags and resizing here: http://www.imagemagick.org/Usage/resize/#resize.
-
//It is very flexible.
-
protected void createThumbnails() {
-
[300, 100].each {
-
def process = "${magickDir()}/convert ${absolutePath()} -thumbnail ${it}x${it} ${absoluteThumbnail(it)}".execute()
-
process.waitFor()
-
}
-
}
-
}
Hopefully the comments explain how everything works together. If you have questions, comments or criticism, please let me know. And yes, I feel a little dirty calling another program in a process, but when it works this well, getting a little dirty feels pretty good.
Image comparison (my youngest, Ariana):
ImageMagick

ImageTool (JAI)

ImageMagick

ImageTool (JAI)
