Thursday, July 28, 2011

Scaling transparent images in Java.

Ever had the need to scale images using Java? If so you would have realized that its a fairly easy task given the amount of support provided by Java Imaging API's as well as the numerous resources available online. Yup I just said it, numerous resources online, so the obvious question why write another blog post? Well you are about to find out.

I've been working on scaling uploaded images using Java for a couple of days now. Everything works fine for an opaque image, but not too well for transparent images. As it turned out most of the articles on scaling images discussed how to scale opaque images but not transparent ones. So I thought I might as well blog about it.

The issue with scaling transparent images are that the resulting images is distorted, and the transparent areas are drawn in black! Check out the two images below.

                                        Original image                           Scaled Image

This obviously wasn't acceptable, as a quick fix we investigated on coloring the transparent images to White color but the that was an ugly fix too.

Most of the blogs and forums that discussed this issue of black background issue suggested setting the ColorModel to BufferedImage.TYPE_INT_ARGB but unfortunately that didn't quite work for me.

The issue is how we paint the image once transformed, make sure to write the image in PNG format so that transparency is preserved. JPEG doesn't support transparency and GIF formats behave differently depending on the background etc. On the other hand using PNG works fine for transparent and opaque images.
          
private static DataHandler scaleImage(DataHandler dataHandler, int height, int width) throws IOException {

        Image image = ImageIO.read(new BufferedInputStream(dataHandler.getInputStream()));
        // Check if the image has transparent pixels
        boolean hasAlpha = ((BufferedImage)image).getColorModel().hasAlpha();

        // Maintain Aspect ratio
        int thumbHeight = height;
        int thumbWidth = width;
        double thumbRatio = (double)width / (double)height;
        double imageRatio = (double)image.getWidth(null) / (double)image.getHeight(null);
        if (thumbRatio < imageRatio) {
            thumbHeight = (int)(thumbWidth / imageRatio);
        } else {
            thumbWidth = (int)(thumbHeight * imageRatio);
        }

        BufferedImage thumb;
        // Check if transparent pixels are available
        // and set the color mode accordingly 
        if (hasAlpha) {
            thumb = new BufferedImage(thumbWidth, thumbHeight,
                    BufferedImage.TYPE_INT_ARGB);
        } else {
            thumb = new BufferedImage(thumbWidth, thumbHeight, 
                    BufferedImage.TYPE_INT_RGB);
        }
        Graphics2D graphics2D = thumb.createGraphics();
        graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        graphics2D.drawImage(image, 0, 0, thumbWidth, thumbHeight, null);

        // Save the image as PNG so that transparent pixels are rendered
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        ImageIO.write(thumb, "PNG", output);

        DataSource dataSource= new ByteArrayDataSource(output.toByteArray(), 
                               "application/octet-stream");
        return new DataHandler(dataSource);
    }

As you can see the image format is set to PNG when and the final image is returned as a DataHandler.

If you want to iterate each and every pixel of an image and then identify transparent pixels and change the color of that pixel you can do it as follows;

int destWidth = 151;
  int destHeight = 179;
  BufferedImage dest = new BufferedImage(destWidth, destHeight,
    BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i < dest.getHeight(); i++) {
   for (int j = 0; j < dest.getWidth(); j++) {
    int pixel = dest.getRGB(j, i);
    byte alpha=(byte)pixel;
    alpha%=0xff;
    if (pixel == 0) {
     // Set the color of the pixel to White
     dest.setRGB(j, i, Color.WHITE.getRGB());  
    }
   }
  }

More useful links on scaling images are listed below.


[1] - http://developeriq.in/articles/2010/oct/07/playing-with-images-using-java/
[2] - http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html


1 comment: