Tuesday, April 19, 2011

Making White Image Backgrounds Transparent with Java 2D/Groovy

I occasionally need to convert the background color of a particular image to be transparent so that the image looks better on medium with backgrounds of colors other than the image's original background color. More specifically, I typically need to convert an image with a white background to have a transparent background so that it blends more naturally with a non-white background. The next screen snapshot shows the difference in appearance between an image with a white background on a non-white background and an image with a transparent background.


There are numerous tools that support conversion of a background in an image to be transparent (assuming the image format supports transparency). However, I have wanted a simple command-line script to do this so that I don't need to open a tool, load a file, select the color to covert, and then save the new image with the transparent background. I figured that someone had already done this in Java and my idea was to "port" the Java to Groovy. In this post, I show a Groovy script that does perform this conversion based on an easy-to-follow example of how to do this in Java that is available in the StackOverflow thread How to make a color transparent in a BufferedImage and save as PNG.

PhiLho and corgrath provide Java code examples illustrating conversion of a background color to a transparent background for an image in the StackOverflow thread How to make a color transparent in a BufferedImage and save as PNG. With these illustrative code examples in place, the most difficult part of the task is already completed. The next Java code listing contains my very slight adaptation of the code provided by them and is provided mainly for easy comparison to the ported Groovy code to be shown later.

ImageTransparency.java
package dustin.examples;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.File;
import javax.imageio.ImageIO;

import static java.lang.System.out;

/**
 * Class containing code for converting an image's white background to be
 * transparent, adapted with minor changes from StackOverflow thread "How to
 * make a color transparent in a BufferedImage and save as PNG"
 * (http://stackoverflow.com/questions/665406/how-to-make-a-color-transparent-in-a-bufferedimage-and-save-as-png).
 */
public class ImageTransparency
{
   /**
    * Main function for converting image at provided path/file name to have
    * transparent background.
    *
    * @param arguments Command-line arguments: only one argument is required
    *    with the first (required) argument being the path/name of the source
    *    image and the second (optional) argument being the path/name of the
    *    destination file.
    */
   public static void main(final String[] arguments) throws Exception
   {
      if (arguments.length < 1)
      {
         out.println("A source image file must be provided.");
         System.exit(-1);
      }

      final String inputFileName = arguments[0];
      final int decimalPosition = inputFileName.lastIndexOf(".");
      final String outputFileName =
           arguments.length > 1
         ? arguments[1]
         : inputFileName.substring(0,decimalPosition)+".copy.png";

      out.println("Copying file " + inputFileName + " to " + outputFileName);

      final File in = new File(inputFileName);
      final BufferedImage source = ImageIO.read(in);

      final int color = source.getRGB(0, 0);

      final Image imageWithTransparency = makeColorTransparent(source, new Color(color));

      final BufferedImage transparentImage = imageToBufferedImage(imageWithTransparency);

      final File out = new File(outputFileName);
      ImageIO.write(transparentImage, "PNG", out);
   }

   /**
    * Convert Image to BufferedImage.
    *
    * @param image Image to be converted to BufferedImage.
    * @return BufferedImage corresponding to provided Image.
    */
   private static BufferedImage imageToBufferedImage(final Image image)
   {
      final BufferedImage bufferedImage =
         new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
      final Graphics2D g2 = bufferedImage.createGraphics();
      g2.drawImage(image, 0, 0, null);
      g2.dispose();
      return bufferedImage;
    }

   /**
    * Make provided image transparent wherever color matches the provided color.
    *
    * @param im BufferedImage whose color will be made transparent.
    * @param color Color in provided image which will be made transparent.
    * @return Image with transparency applied.
    */
   public static Image makeColorTransparent(final BufferedImage im, final Color color)
   {
      final ImageFilter filter = new RGBImageFilter()
      {
         // the color we are looking for (white)... Alpha bits are set to opaque
         public int markerRGB = color.getRGB() | 0xFFFFFFFF;

         public final int filterRGB(final int x, final int y, final int rgb)
         {
            if ((rgb | 0xFF000000) == markerRGB)
            {
               // Mark the alpha bits as zero - transparent
               return 0x00FFFFFF & rgb;
            }
            else
            {
               // nothing to do
               return rgb;
            }
         }
      };

      final ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
      return Toolkit.getDefaultToolkit().createImage(ip);
   }
}

A first cut at a Groovy port of the above Java class is shown in the next Groovy code listing.

makeImageTransparent.groovy
#!/usr/bin/env groovy
// makeImageTransparent.groovy

import java.awt.Color
import java.awt.Graphics2D
import java.awt.Image
import java.awt.Toolkit
import java.awt.image.BufferedImage
import java.awt.image.FilteredImageSource
import java.awt.image.RGBImageFilter
import javax.imageio.ImageIO

/*
 * Script containing code for converting an image's white background to be
 * transparent, adapted with minor changes from StackOverflow thread "How to
 * make a color transparent in a BufferedImage and save as PNG"
 * (http://stackoverflow.com/questions/665406/how-to-make-a-color-transparent-in-a-bufferedimage-and-save-as-png).
 *
 * Command-line arguments: only one argument is required
 * with the first (required) argument being the path/name of the source
 * image and the second (optional) argument being the path/name of the
 * destination file.
 */

if (args.length < 1)
{
   println "A source image file must be provided."
   System.exit(-1);
}

def inputFileName = args[0];
def decimalPosition = inputFileName.lastIndexOf(".")
def outputFileName = args.length > 1 ? args[1] : inputFileName.substring(0,decimalPosition)+".copy.png"

println "Copying file ${inputFileName} to ${outputFileName}"

def input = new File(inputFileName)
def source = ImageIO.read(input)

def color = source.getRGB(0, 0)

def imageWithTransparency = makeColorTransparent(source, new Color(color))

def final BufferedImage transparentImage = imageToBufferedImage(imageWithTransparency)

def out = new File(outputFileName)
ImageIO.write(transparentImage, "PNG", out)



/**
 * Convert Image to BufferedImage.
 *
 * @param image Image to be converted to BufferedImage.
 * @return BufferedImage corresponding to provided Image.
 */
def BufferedImage imageToBufferedImage(final Image image)
{
   def bufferedImage =
      new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
   def g2 = bufferedImage.createGraphics();
   g2.drawImage(image, 0, 0, null);
   g2.dispose();
   return bufferedImage;
}

/**
 * Make provided image transparent wherever color matches the provided color.
 *
 * @param im BufferedImage whose color will be made transparent.
 * @param color Color in provided image which will be made transparent.
 * @return Image with transparency applied.
 */
public static Image makeColorTransparent(final BufferedImage im, final Color color)
{
   def filter = new CustomImageFilter(color)
   def ip = new FilteredImageSource(im.getSource(), filter)
   return Toolkit.getDefaultToolkit().createImage(ip)
}

/**
 * Was 'anonymous inner class' in Java version.
 */
public class CustomImageFilter extends RGBImageFilter
{
   private int markerRGB

   public CustomImageFilter(final Color color)
   {
      // the color we are looking for (white)... Alpha bits are set to opaque
      markerRGB = color.getRGB() | 0xFFFFFFFF
   }

   public int filterRGB(final int x, final int y, final int rgb)
   {
      if ((rgb | 0xFF000000) == markerRGB)
      {
         // Mark the alpha bits as zero - transparent
         return 0x00FFFFFF & rgb
      }
      else
      {
         // nothing to do
         return rgb
      }
   }   
}

The (slightly) adapted Groovy script above will convert the white portions of the provided image to be transparent and will save the new image with transparent portions (assumed to be background) to a new PNG file (of either provided name or of default based on source file's name).

Although the above Groovy script is sufficient for my most common need, there are numerous improvements that can be made to this script to make it more usable. Groovy's CLI support could be leveraged to allow more options or more granular options to be specified. Colors other than white could be used as the source color to the changed to be transparent. Formats that support transparency could be specified rather than using PNG by default.


Conclusion

One of the advantages of using Java or an alternative JVM language is access to the rich Java SDK and access to the insight and previous experience of a large Java/JVM community. In this post, I've attempted to demonstrate how these advantages were leveraged to come up with a useful script in a very small amount of time.

1 comment:

KLD said...

Thanks man. I copied the method "makeColorTransparent" and I added the URL after return. I don't really understand it, but I know how to use it. I'm making a Sudoku game and thanks to you, it's now easier to draw images.