Part of the functionality of the Pachyderm authoring application is the dynamic and on-the-fly resizing of images to whatever dimensions are required by the flash templates that are used to display a screen in a presentation.
I wrote the first version of the image resizing code using Java Advanced Imaging (JAI), and it worked quite well. But, during the authoring of the Mavericks prototype, it became apparent that the quality of the resized images wasn’t quite up to snuff. I tried setting JAI to use bicubic interpolation (InterpolationBicubic and InterpolationBicubic2) instead of the default nearest-neighbour (InterpolationNearest) method. Still produced inconsistent results.
I had looked at using ImageMagick, using the java JMagick bridge, but that was just plain funky. It relies on a JNI bridge that apparently doesn’t compile well on MacOSX (I never got it compiled, and Google only turned up one person in the history of the internets that had success - on an older version of the OS).
Fast forward to last night. I decided to try an ImageMagick solution using java’s Runtime.exec() - and it works perfectly. Image quality is MUCH better. The memory issues we were seeing with JAI disappeared (JAI was barfing on Very Large Images, where ImageMagick chews through them with ease). The downside is that it takes considerably longer to process the resized images, and since ImageMagick can only work with local files (not URLs), I have to download the image from the web each time I want to process it (this can be done more intelligently - I just haven’t done that yet).
Compare the output of the two resize methods:
Update: I get asked by email every now and then for some sample code to show how we used Runtime.exec() in this case. For the Googlers out there, here ’s the goods…
/**
* Uses a Runtime.exec()to use imagemagick to perform the given conversion
* operation. Returns true on success, false on failure. Does not check if
* either file exists.
*
* @param in Description of the Parameter
* @param out Description of the Parameter
* @param newSize Description of the Parameter
* @param quality Description of the Parameter
* @return Description of the Return Value
*/
private static boolean convert(File in, File out, int width, int height, int quality) {
System.out.println("convert(" + in.getPath()+ ", " + out.getPath()+ ", " + newSize + ", " + quality);
if (quality < 0 || quality > 100) {
quality = 75;
}
ArrayList command = new ArrayList(10);
// note: CONVERT_PROG is a class variable that stores the location of ImageMagick's convert command
// it might be something like "/usr/local/magick/bin/convert" or something else, depending on where you installed it.
command.add(CONVERT_PROG);
command.add("-geometry");
command.add(width + "x" + height);
command.add("-quality");
command.add("" + quality);
command.add(in.getAbsolutePath());
command.add(out.getAbsolutePath());
System.out.println(command);
return exec((String[])command.toArray(new String[1]));
}
/**
* Tries to exec the command, waits for it to finsih, logs errors if exit
* status is nonzero, and returns true if exit status is 0 (success).
*
* @param command Description of the Parameter
* @return Description of the Return Value
*/
private static boolean exec(String[] command) {
Process proc;
try {
//System.out.println("Trying to execute command " + Arrays.asList(command));
proc = Runtime.getRuntime().exec(command);
} catch (IOException e) {
System.out.println("IOException while trying to execute " + command);
return false;
}
//System.out.println("Got process object, waiting to return.");
int exitStatus;
while (true) {
try {
exitStatus = proc.waitFor();
break;
} catch (java.lang.InterruptedException e) {
System.out.println("Interrupted: Ignoring and waiting");
}
}
if (exitStatus != 0) {
System.out.println("Error executing command: " + exitStatus);
}
return (exitStatus == 0);
}```