JAI vs. ImageMagick image resizing

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:

JAI vs. ImageMagick Resizing in Pachyderm

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);
	}

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:

JAI vs. ImageMagick Resizing in Pachyderm

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);
}

38 thoughts on “JAI vs. ImageMagick image resizing”

  1. I have done this kind of hack already. I use it nearly for any image transformation I have coded in EverLearn (http://www.everlearn.info) a LCMS I have done the last 2 and a half years.

    You can do amazing things with imagemagick. And it does not use up nearly the same amount of memory as the Java-Solution does. Memory (better: missing Memory) using java-solution JAI is clearly an issue here.

    Go with imagemagick its very powerful and stable! Drop me an eMail if I should give you some insight on whats possible using e.g. WebObjects in conjunction with ImageMagick.

    regards,
    helge

  2. Nice article. This is exactly what I need.

    I would love to see the runtime.exec, Webobjects ImageMagick code snip to help me out.

    Please email off list if you could share.

    Thanks,
    Chris

  3. Curtis, I just updated the post with some sample code. It won’t work out of the box – it’s intended to be called by other methods that set it up and deal with the output – but should get you up and running quickly.

  4. Hi D’Arcy

    I am running into a wall using Java imaging on very large images (~98MB) so I have to look at other options.
    (scaling master down to use copies)

    We are trying to build an image processor diretly into our digital library collection system so that image submission is a one stop shop

    I would be interested to see how you connected ImageMagick to a a Java program using Runtime.exec()

    thanks for any info

    Curtis

  5. Thomas, I actually gave up on JMagick – the direct call via Runtime.exec() does the trick well enough. It’d be nice to have a proper API to set parameters, but it’s not too onerous to do that manually…

  6. Well done D’Arcy,

    i went through the same idea : JMagick works fine on my windows computer but i have lost my last sunday trying to do the same on my mac computer .

    that’s a shame that we must do this kind of trick but it does the job and who care ?

    by the way if u achieved with JMagick on mac os X, can u wrote an article ? 🙂

    Thomas M.

  7. Finally i keep using the code ripped from http://www.xs4all.nl/~kzeil/en/project/snipsnap/index.html :

    My macro work with java.awt.image.BufferedImage and it is very expensive to use ImageIO.write to have a File for using with your snippet : it cost about 25% more time : because using ImageMagick convert let you win about 50% time on a 500p*500p image but it cost about 100% more time to translate your buffer into a File .

    For your information with this code there is no kind of deformation on the images :

    // takes a -BufferedImage image- in parameter
    // return a BufferedImage scaled with parameter maxSize

    double scale = 1;
    if (image.getWidth() > image.getHeight()) {
    scale = (double) maxSize / (double) image.getWidth();
    } else {
    scale = (double) maxSize / (double) image.getHeight();
    }

    int scaledW = (int)(scale*image.getWidth(null));
    int scaledH = (int)(scale*image.getHeight(null));

    Image img = image.getScaledInstance(scaledW, scaledH, Image.SCALE_SMOOTH);

    ColorModel cm = image.getColorModel();
    WritableRaster wr = cm.createCompatibleWritableRaster(scaledW, scaledH);

    BufferedImage outImage = new BufferedImage(cm, wr, false, null);

    Graphics2D g2 = outImage.createGraphics();
    g2.drawImage(img, 0, 0, null);
    g2.dispose();

    return outImage;

    i have added a parameter -Djava.awt.headless=true on the jboss launch to avoid shell window to popup at the call of this snippet .

    Thomas .

  8. Thomas, thanks for the code. I’ll give it a shot as well. I was having some serious memory issues when doing all of the image manipulation in java, which is one of the reasons (the more serious one) why I went to ImageMagick. It might be a little slower, but it’s been 100% reliable on Very Large Images.

  9. # Hi, I had to do this to get jmagick installed on OS X.
    # see http://www.yeo.id.au/jmagick/fom-serve/cache/27.html

    To install jmagick, follow instructions at http://www.yeo.id.au/jmagick/fom-serve/cache/16.html, basically just download, untar/gzip and …

    ./configure
    make

    Except for some changes …

    instead of “make install”

    cd to src/magick

    [steve:JMagick-6.2.4-1/src/magick] tammy% foreach f ( `ls *.c` )
    foreach? cc -I/usr/local/src/JMagick-6.2.4-1/generated/magick -I/usr/local/include -I/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/ -I/usr/local/include -D_REENTRANT -INONE -c /usr/local/src/JMagick-6.2.4-1/src/magick/$f -fno-common -DPIC
    foreach? end

    Then
    (my ImageMagick is installed in /usr/local directly. Add a -I/usr/local/imagemagick_home if you installed it elsewhere.)
    cc -bundle -I/usr/local/src/JMagick-6.2.4-1/generated/magick -I/usr/local/include -I/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/ -I/usr/local/include -D_REENTRANT -INONE -framework JavaVM -fno-common -DPIC -L/usr/local/lib -lMagick -ljpeg -lpng -o libJMagick.jnilib *.o

    cd to JMagick source directory

    [steve:local/src/JMagick-6.2.4-1] tammy% sudo cp src/magick/*.jn* /usr/lib/java
    [steve:local/src/JMagick-6.2.4-1] tammy% sudo cp lib/jmagick.jar /Library/Java/Extensions/

    Set
    setenv DYLD_LIBRARY_PATH “/usr/local/lib:/usr/lib/java”
    in your login script

  10. Hi

    I am new to ImageMagick and JMagick. Can any one help me how to deploy both ImageMagick & JMagick in elobrate in windows 2k operating system

    Thanks in Advance.

  11. you wrote nicely about ImageMagic, but not about JAI that you are comparing. While you are giving information how JAI scaled your example images (bicubic), you don’t tell us how ImageMagic does it.
    If you e.g. change JAI scaling fom Bicubic to NearestNeighbour it will speed scaling up by factor 4 to 6 (especially when dealing with huge color images).
    If you want to scale bitonal images (even large bitonal images), JAI is fast – and provides a very good quality, if you choose “subsampletogray”.

    My experience is, that JAI is much better in quality and faster when it comes to scale bitonal and greyscale images.

  12. Markus, thanks for the tip! I’ll definitely try that. I didn’t go much further with JAI because I kept hitting the memory issue – unless I can allocate a couple gigs to the java app, it falls over on large image.

  13. Yes the problem in java is not the speed but the memory consumption.

    I try to code a thumbnail generator (servlet) for large images and i have always the problem to running out of memory, after x calls (depending on the size of the images). I think BufferedImage has a memory leak, cause you cant free the complete resources.

     

    So, now i will try your solution with
    imagemagick.

     

  14. I’ve been playing with JAI lately and I find its performance utterly horrible (Running on Java 1.5.0_06 with 1.1.3 and native accelerators). It takes 15-45 seconds to rotate a 5megapixel image and scale down to thumbnail size (140xYYY). That isn’t even counting encoding back to JPEG. Not to mention, like you point out above, both BICUBIC and BICUBIC_2 have very poor scaling performance compared to the default settings of ImageMagick (I often use PerlMagick and may soon become a user of JMagick as well) when scaling photographs.

  15. Well, it isn’t as horrible as I first made JAI out to be, downscaling photographs using BILINEAR interpolation is much better than either BICUBIC variant. It’s not as nice as the default Lanczos or Mitchell filters in ImageMagick, but it’s a step in the right direction.

    I can’t use ImageMagick or JMagick because either approach requires round-tripping from a java Image object to a file and back, and that’s just a horrible approach. (If there’s another method to convert to MagickImage and back in Java, I don’t know it).

  16. Hi,

    I had to create a thumbnail generator this year using JAI. If you want to get thumbnails with a very good better quality, you can apply a bilinear or a bicubic interpolation algorithm several times (ex: 4-5 times) on a given image.

    For instance, if you have to scale an image down by 50%, scale it by SQRT(50%) the first time and scale the resulting image by SQRT(50%).

    If you want to scale an image using n pass, simply scale the image down by (percentage)^(1/nPass) (use math.pow!) at each pass to get a final image scaled down by percentage%

    Using a nth pass approach ensures there are more sampled pixels used to produce the final image and this is why the thumbnail has a better quality.

    Indeed, the downside of this approach is that the process is slower. It takes about 2-3 seconds to create a thumbnail of a 3 Megapixels picture using 4 pass on a P4 2.66 Ghz FSB 533 with 1 Go memory. JAI native acceleration was not used at all, because I deleted the dll!

    For the JAI memory issue, some objects require to call the dispose method (like with ImageReader). Otherwise, some memory resource are not freed.

  17. Am executing ImageMagick commands from java using Runtime.exec().
    I noticed that the exec command works only if I provide absolute path to the ImageMagick installation directory even after adding installation directory to PATH variable. But the same command works from command prompt. Am I missing something here?

  18. I use JAI everyday for rotating, scaling and cropping images. I find we do it in less than a second with JAI.

    You need to give it as much ram as possible and with JPEG’s you should use: ImageIO.read

    and ImageIO.write

    JAI takes a long time loading and unloading jpegs. If you use JAI to load a JPEG without the IO plugins it slows down a lot because it loads it into one tile.

  19. D’Arcy,

    I’m having trouble executing an ImageMagick command using the Runtime.exec(). I print out the entire command string right before I run it and if I copy and paste it into my terminal it executes fine. I was wondering if you would mind trying it yourself using the Runtime.exec() method to see if you get the same error. Here is the command:

    convert original.jpg -resize 1212x -resize ‘x1212

  20. I just spent a day trying to get JMagick to work. Apparently, its latest release is incompatible with versions of ImageMagick released less than a year ago, and its documentation is a joke.

    Excerpt from the javadoc:

    “int getMonochrome() – gets the Monochrome attribute”

    Eventually, I followed your advice and simply used a Runtime.exec() command. The code didn’t get any prettier, but at least it works and is reasonably fast now (platform-independent, even). JNI kind of loses its point when a sytem command works better and is more stable than the interface.

  21. Hallo,
    this is a little OT, but not more than my previous spekers ;o)

    I dont know JAI, but I´ve a question about using ImageMagick from my java application:

    When i need to do frequently a lot of (parallel) rendering jobs with ImageMagick, would it be better (faster, more stable) to use Process Execution or JNI (JMaigic)?

    Regards,
    Pascal

  22. Argh!

    I’ve spent atleast 40 hours on some JAI image-resizing/watermarking code, and now I realize it is completely unusable for animations!

    I should have gone with imagemagick to start with. Now, using imagemagick instead isn’t a big deal, but it is annoying to switch between JAI and im, and I dont like depending no external executables. I did read this page earlier though, and I solved the scaling problems kinda nicely with JAI, by “cheating” via multiple small downscaling steps, instead of one big. This is sometimes done in Photoshop på upsize images for example, but it also works to remove aliasing when downsizing.

  23. Hi
    I am using Imagemagick 6.3.7 on solaris.I tried the above code and I get the following
    output from error stream.
    no decode delegate for this image format `myimage.jpg’.
    missing an image filename `resizeimage.jpg’.
    I am trying to resize myimage.jpg to resizeimage.jpg.

    I have no clue why its happning.
    Can somebody help?

  24. Hey,
    I got it working…Some how it wasn’t working for me using runtime exec.
    I tried with Process builder class and it worked.
    Here is the code for your reference.

    try {

    String strArr[]={ “/usr/local/ImageMagick-6.3.7/bin/convert”,
    “testimage.jpg”,
    “-resize”,
    “96x”,”imagetoresize.jpg”};
    ProcessBuilder pb = new ProcessBuilder(strArr);

    //I need this environment variable in my path…This step is optional.
    Map env = pb.environment();

    env.put( “LD_LIBRARY_PATH”, “:/usr/local/ImageMagick-6.3.7/lib” );

    pb.redirectErrorStream( true );

    Process p = pb.start();

    } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }

  25. Hi @all,
    since years I’m successfully using ImageMagick via Runtime#exec on Windows, OS X and Linux. It’s very stable and quick. Fine!
    But there is one problem I couldn’t solve yet: If the path of an image contains one or more spaces, the command fails. I’ve tried various possibilities of escaping, but no success! I’ve tested it on OS X for now.

    Is there anybody who has an hint for me?

    Best regards
    Thilo

  26. Hi Guys,
    i think this is a very nice thread going on here,
    i am running into a problem here
    i used exec and created jpg’s from other formats like eps and ai,
    jpg’s created were all fine , opens properly from all viewers, but when i try to read them through java ImageIO.read(..)
    it fails , it give this exception
    sun.awt.image.ImageFormatException: Unsupported color conversion request
    at sun.awt.image.JPEGImageDecoder.readImage(Native Method)
    at sun.awt.image.JPEGImageDecoder.produceImage(Unknown Source)
    at sun.awt.image.InputStreamImageSource.doFetch(Unknown Source)
    at sun.awt.image.ImageFetcher.fetchloop(Unknown Source)
    at sun.awt.image.ImageFetcher.run(Unknown Source)
    sun.awt.image.ImageFormatException: Unsupported color conversion request
    at sun.awt.image.JPEGImageDecoder.readImage(Native Method)
    at sun.awt.image.JPEGImageDecoder.produceImage(Unknown Source)
    at sun.awt.image.InputStreamImageSource.doFetch(Unknown Source)
    at sun.awt.image.ImageFetcher.fetchloop(Unknown Source)
    at sun.awt.image.ImageFetcher.run(Unknown Source)

    PLEASE HELP, i need this ASAP done

  27. Hi.

    we’ve had much probs with ImageIO.read() – our application / javaVM halted COMPLETELY when using this on some pictures – we run imageMagick “correctimage” before reading with ImageIO.read().
    Perhaps this has nothing to do with your error (color conversion), but perhaps it helps on some problems.
    (If we got errormessages from ImageIO.read – in most cases they are random but had nothing to do with the real problem on current image)….

  28. It is possible to get ImageMagick’s level of quality when using JAI to resize images. I have written a tool called ThumbMaster which is a new Interpolation implementation that uses the same algorithm as ImageMagick. It allows you resize images using standard JAI APIs without the complexity and overhead of integrating with a command line tool. More information is available at the tool’s website, devella.net/thumbmaster.

    Full disclosure: I am the developer of ThumbMaster, and am posting this here because I do believe it is a relevant solution to the problems expressed by many of the above posters.

  29. ImageMagick is an excellent package. However, you don’t need to use it to get top-quality scaled images.

    Image.getScaledInstance(…, Image.SCALE_AREA_AVERAGING) gives you the maximum-possible scaling quality, and you don’t need to mess around with ImageMagick. It’s slow, but I haven’t done performance comparisons, so I don’t know how much slower than IM. If you want top-quality resizing, without needing to use external binaries, Image.getScaledInstance(…, Image.SCALE_AREA_AVERAGING) is what you need.

Comments are closed.