This discussion is archived
1 2 Previous Next 15 Replies Latest reply: Mar 7, 2009 11:03 AM by 843802 RSS

JAI scales down an image with poor performance

843802 Newbie
Currently Being Moderated
Hi,

It seems to be the easiest thing to do in JAI (or any image API for that matter) but gives me a headache.

Task is: scale down an image with best quality.

Outcome: JAI has poor performance compared to standard applications like GIMP.

Here are these two examples and the original image:
http://nikhomepage.com/jai-test/use-gimp.jpg
http://nikhomepage.com/jai-test/use-jai.jpg
http://nikhomepage.com/jai-test/original.jpg

Notice how one of the lines is not smooth after scale by JAI.

The scale operation itself is performed by following code:

ParameterBlock pb = new ParameterBlock();
pb.addSource(src); // The source image is RenderedOp
pb.add(scaleFactor); // scale factor is ~ 0.3
pb.add(scaleFactor); // ~0.3
pb.add(0.0F); // The x translation
pb.add(0.0F); // The y translation
pb.add(new InterpolationNearest());
//pb.add(new InterpolationBicubic(5)); // another attempt
//pb.add(new InterpolationBilinear(5)); // another attempt
RenderedOp scaled = JAI.create("scale", pb);

You have seen the results...
Any idea how to improve this?

Thanks,
Nik
  • 1. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    The commercial library ImageResize4J produces the next output:
    [http://www.imageresize4j.com/use-imageresize4j-ideal2.jpg]

    with the next code:
    //...
    import com.imageresize4j.jai.ImprovedScaleDescriptor;
    import com.imageresize4j.jai.ImprovedScaleInterpolation;
    
    //...
    PlanarImage image = JAI.create("fileload", args[0]);
    RenderedOp renderedOp = ImprovedScaleDescriptor.create(image, new Float(0.335f), null,ImprovedScaleInterpolation.IDEAL_2, null);
  • 2. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    Allright, thanks.
    Since I am only doing a home project I am not into buying a commercial library.
    But this helped to know JAI is not enough, need to use workaround.

    Nik
  • 3. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    I wish I could remember what article this method was from. It was an article about how the new ways to resize images through graphics.drawImage() was preferential to the old way of Image.getScaledInstance() in terms of speed vs. quality. Except when you tried to scale down an image to less than half its size the results were undesirable.

    So the article suggested an incrimental scale down technique and provided the method below.
    /**
         * Convenience method that returns a scaled instance of the
         * provided {@code BufferedImage}.
         *
         * @param img the original image to be scaled
         * @param targetWidth the desired width of the scaled instance,
         *    in pixels
         * @param targetHeight the desired height of the scaled instance,
         *    in pixels
         * @param hint one of the rendering hints that corresponds to
         *    {@code RenderingHints.KEY_INTERPOLATION} (e.G.
         *    {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
         *    {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
         *    {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
         * @param higherQuality if true, this method will use a multi-step
         *    scaling technique that provides higher quality than the usual
         *    one-step technique (only useful in downscaling cases, where
         *    {@code targetWidth} or {@code targetHeight} is
         *    smaller than the original dimensions, and generally only when
         *    the {@code BILINEAR} hint is specified)
         * @return a scaled version of the original {@code BufferedImage}
         */
        public static BufferedImage getScaledInstance(BufferedImage img,
                                               int targetWidth,
                                               int targetHeight,
                                               Object hint,
                                               boolean higherQuality)
        {
            int type = (img.getTransparency() == java.awt.Transparency.OPAQUE) ?
                BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
            BufferedImage ret = (BufferedImage)img;
            int w, h;
            if (higherQuality) {
                // Use multi-step technique: start with original size, then
                // scale down in multiple passes with drawImage()
                // until the target size is reached
                w = img.getWidth();
                h = img.getHeight();
            } else {
                // Use one-step technique: scale directly from original
                // size to target size with a single drawImage() call
                w = targetWidth;
                h = targetHeight;
            }
            
            do {
                if (higherQuality && w > targetWidth) {
                    w /= 2;
                    if (w < targetWidth) {
                        w = targetWidth;
                    }
                }
    
                if (higherQuality && h > targetHeight) {
                    h /= 2;
                    if (h < targetHeight) {
                        h = targetHeight;
                    }
                }
    
                BufferedImage tmp = new BufferedImage(w, h, type);
                Graphics2D g2 = tmp.createGraphics();
                g2.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION, hint);
                g2.drawImage(ret, 0, 0, w, h, null);
                g2.dispose();
    
                ret = tmp;
            } while (w != targetWidth || h != targetHeight);
    
            return ret;
        }
    I tried resizing your test image using this method and the results were certainly better then the JAI version, although not quite as good as the GIMP one. In terms of quality, the best resize technique that core java has to offer is the old technique of
    Image.getScaledInstance(int width, int height, Image.SCALE_AREA_AVERAGING);
    Whatever workaround you do use, try comparing it to the methods offered by the core library.

    Edited by: Maxideon on Oct 17, 2008 3:21 PM
    I probably should of included a picture. This is using the static method above
    http://img233.imageshack.us/img233/9357/usestaticmethodlh7.png
  • 4. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    I have developed a library called ThumbMaster which is specifically targeted at Java programmers who need to resize images with JAI -- and get high quality results. It is implemented as a new JAI interpolation, and so the standard built-in operators (and their native acceleration) can all be used. Image quality and file size are both nearly identical to the results of ImageMagick, as they are both based heavily on the same image processing algorithms.

    Resizing images using JAI instead of getScaledInstance will give much better performance, and allows all of the power of the JAI APIs to be leveraged (native acceleration, tiling, border extenders, etc.). For more information, and a download of the software, visit [http://www.devella.net/thumbmaster].
  • 5. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    Resizing images using JAI instead of getScaledInstance will give much better performance, and allows all of the power of the JAI APIs to be leveraged (native acceleration, tiling, border extenders, etc.). For more information, and a download of the software, visit http://www.devella.net/thumbmaster.
    I highly doubt this. The native acceleration you speak of mostly refers to saving and loading images. The scaling on the fly approach in my post above most definetly uses the graphics card for fast scaling.

    As it is, a user of JAI can get the same scaling quality as getScaledInstance very simply by the following code.
    RenderingHints qualityHints = new RenderingHints(
        RenderingHints.KEY_RENDERING,
        RenderingHints.VALUE_RENDER_QUALITY);
    
    RenderedOp resizedImage = 
                JAI.create("SubsampleAverage",paramBlock, qualityHints);
    I surmise that your ThumbMaster is no better, except you're asking 50 dollars for it.
  • 6. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    RenderingHints qualityHints = new RenderingHints(
        RenderingHints.KEY_RENDERING,
        RenderingHints.VALUE_RENDER_QUALITY);
     
    RenderedOp resizedImage = 
                JAI.create("SubsampleAverage",paramBlock, qualityHints);
    Unfortunately, this still isn't as good as:

    image.getScaledInstance(newWidth, (newWidth * origHeight) / origWidth, Image.SCALE_SMOOTH);

    I have yet to see any algorithm beat getScaledInstance() for quality. I wish JAI could do it, but the above JAI code does not. It's weird that this old way of doing things is still champ in quality.

    Try taking a 4000px wide image and shrinking it down to 200px wide and you'll see.
  • 7. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    Try taking a 4000px wide image and shrinking it down to 200px wide and you'll see.
    I have, and I don't see what you are seing. I've scaled hundreds of images from a few hundred pixels wide to 6000 pixels wide. In all cases+ "SubsampleAverage" and getScaledInstance produce almost identical results visually. I would expect this since the algorithms are practically the same.

    I don't know what you are doing to get different results.
  • 8. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    I have, and I don't see what you are seing. I've scaled hundreds of images from a few hundred pixels wide to 6000 pixels wide. In all cases "SubsampleAverage" and getScaledInstance produce almost identical results visually. I would expect this since the algorithms are practically the same.
    
    I don't know what you are doing to get different results. 
    I don't know either. Are you scaling JPEGs? This article: [http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html] suggests that getScaledInstance() is the best quality solution, although he doesn't mention JAI for some reason.

    I've also read on other sites that JAI is not as high a quality as getScaledInstance(), but the most important thing is that my images show artifacts when I use JAI (with the code you showed above) and show none when I use getScaledInstance().

    Edited by: cochrane68 on Feb 20, 2009 3:05 PM
  • 9. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    This is the code I use that gives me the highest quality:
        public static void resizeVeryHigh(File originalFile, File resizedFile, int newWidth) throws IOException {
            ImageIcon imageIcon = new ImageIcon(originalFile.getCanonicalPath());
            Image image = imageIcon.getImage();
            Image resizedImage = null;
     
            int iWidth = image.getWidth(null);
            int iHeight = image.getHeight(null);
     
            if (iWidth > iHeight) {
                resizedImage = image.getScaledInstance(newWidth, (newWidth * iHeight) / iWidth, Image.SCALE_SMOOTH);
            } else {
                resizedImage = image.getScaledInstance((newWidth * iWidth) / iHeight, newWidth, Image.SCALE_SMOOTH);
            }
     
            // This code ensures that all the pixels in the image are loaded.
            Image temp = new ImageIcon(resizedImage).getImage();
    
            // Create the buffered image.
            BufferedImage bufferedImage = new BufferedImage(temp.getWidth(null), temp.getHeight(null), BufferedImage.TYPE_INT_RGB);
     
            // Copy image to buffered image.
            Graphics g = bufferedImage.createGraphics();
     
            // Clear background and paint the image.
            g.setColor(Color.white);
            g.fillRect(0, 0, temp.getWidth(null), temp.getHeight(null));
            g.drawImage(temp, 0, 0, null);
            g.dispose();
     
            // Encodes image as a JPEG data stream
            FileOutputStream out = new FileOutputStream(resizedFile);
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
            JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bufferedImage);
            param.setQuality(1.0f, true);
            encoder.setJPEGEncodeParam(param);
            encoder.encode(bufferedImage);
        }
    The call to param.setQuality(1.0f, true) is critical. If I set this to 0.75 instead of 1.0, the quality is similar to JAI.

    This is the JAI code I use:
         public static void resizeMedium(File originalFile, File resizedFile, int newWidth) throws IOException {
              InputStream inputStream = new FileInputStream(originalFile);
              OutputStream outputStream = new FileOutputStream(resizedFile);
    
              SeekableStream s = SeekableStream.wrapInputStream(inputStream, true);
              RenderedOp image = JAI.create("stream", s);
              ((OpImage) image.getRendering()).setTileCache(null);
    
              double scale = (double)newWidth / image.getWidth();
    
              ParameterBlock pb = new ParameterBlock();
              pb.addSource(image); // The source image
              pb.add(scale); // The xScale
              pb.add(scale); // The yScale
              pb.add(0.0); // The x translation
              pb.add(0.0); // The y translation
    
              RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_RENDERING,
                       RenderingHints.VALUE_RENDER_QUALITY);
    //          RenderedOp resizedImage = JAI.create("SubsampleAverage", 
    //              image, scale, scale, qualityHints);
              RenderedOp resizedImage = JAI.create("SubsampleAverage", pb, qualityHints);
              
              // lastly, write the newly-resized image to an output stream, in a specific encoding
              JAI.create("encode", resizedImage, outputStream, "JPEG", null);
         }
  • 10. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    The call to param.setQuality(1.0f, true) is critical. If I set this to 0.75 instead of 1.0, the quality is similar to JAI.
    It's a simple matter of fact that the lower the compression quality then the more jpeg artifacts may be introduced. The jpeg format is a lossy compression format. In general a quality setting of .8 to .9 offers a good compromise between file size and image quality. If you are going to use 1.0 instead, then you might as well save the file as a PNG.

    In your JAI code you have this
    JAI.create("encode", resizedImage, outputStream, "JPEG", null);
    JAI will use the default compression to encode the image. I don't know what the default level is (you mentioned 75?) but it's not 1.0. If you display the image returned by
    resizedImage.getAsBufferedImage();
    in a JFrame/JLabel you will see that the resized image looks perfectly fine and is of the same quality as getScaledInstance . It's the JPEG compression (when saving the file) that introduced your artifacts.
  • 11. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    You're right. Saving it using:
            FileOutputStream out = new FileOutputStream(resizedFile);
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
            JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(resizedImage.getAsBufferedImage());
            param.setQuality(0.92f, true);
            encoder.setJPEGEncodeParam(param);
            encoder.encode(resizedImage.getAsBufferedImage());
    instead of:
         JAI.create("encode", resizedImage, outputStream, "JPEG", null);
    greatly increased the quality. In fact, it's so close now I can't really tell the difference, even when I blow it up to 200%.

    One thing I did notice, however, is that the speed of JAI appears to be no better than getScaledImage(), which I thought was surprising. So for this task, there may be no reason to use JAI.

    Thanks for the help.
  • 12. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    I tried use JAI to scale down very big tiff plan images (7224 x 5733) to little png (200 x 150) images with :
    JFileChooser jfc = new JFileChooser();
            if(jfc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
                try {
                    RenderedOp op1 = JAI.create("stream", new MemoryCacheSeekableStream(new FileInputStream(jfc.getSelectedFile())));
                    RenderingHints qualityHints = new RenderingHints(
                    RenderingHints.KEY_RENDERING,
                    RenderingHints.VALUE_RENDER_QUALITY);
                    RenderedOp op2 = JAI.create("SubsampleAverage", new ParameterBlock().addSource(op1).add(200d / op1.getWidth()).add(150d / op1.getHeight()), qualityHints);
                    JAI.create("encode", new ParameterBlock().addSource(op2).add(new FileOutputStream("c:\\success.png")).add("png"));
                } catch (Exception ex) {}
            }
    And the result is PERFECT, it's the same I get with getScaleInstance(smooth...). But, I don't know why but unlike getScaleInstance, this method take a lot of memory, and I have to set -Xmx to more than 128m. With getScaleInstance I could set it to 32m and everything is fine. Any ideas why?

    Edited by: DeadlyPredator on Mar 6, 2009 10:49 AM
  • 13. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    What did your getScaledInstance(...) code look like?

    An advantage of working with the Toolkit is something like this
    Image unloadedImage = Toolkit.getDefaultToolkit().createImage(...);
    
    Image resizedImg = unloadedImage.getScaledInstance(..,..,...);
    loadImage(resizedImg);
    the unloadedImage remains unloaded even when the resized image is loaded. If you look at the hard drive activity in your task manager it seems that when the resized image is loaded the unloadedImage is loaded in "chunks" (which are discarded when done). At the end of it all the unloadedImage is still unloaded. That's the image producer/consumer model at work.

    JAI, on the other hand, speaks in the language of rendered images so it probably has to fully load an image before resizing it (where as the Toolkit can do it in chunks).
  • 14. Re: JAI scales down an image with poor performance
    843802 Newbie
    Currently Being Moderated
    JAI uses a MemoryCacheSeekableStream to read tiles in a image. I open a very big tif plan image with stream op, send it to a scale op, and then a rotate op and I have a jscrollpane which draw only the current visible rect of the image and this is very fast, no full image loading. After, I can store this new image in a tif with the tile options and it's saved with chunks too, so the image is NEVER fully present in memory, so dont need much memory. But when I try to scale down the big image and store it... I think it fully loads the image so I got a OutOfMemory... it's a major problem
1 2 Previous Next