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
  1. package dustin.examples;  
  2.   
  3. import java.awt.Color;  
  4. import java.awt.Graphics2D;  
  5. import java.awt.Image;  
  6. import java.awt.Toolkit;  
  7. import java.awt.image.BufferedImage;  
  8. import java.awt.image.FilteredImageSource;  
  9. import java.awt.image.ImageFilter;  
  10. import java.awt.image.ImageProducer;  
  11. import java.awt.image.RGBImageFilter;  
  12. import java.io.File;  
  13. import javax.imageio.ImageIO;  
  14.   
  15. import static java.lang.System.out;  
  16.   
  17. /** 
  18.  * Class containing code for converting an image's white background to be 
  19.  * transparent, adapted with minor changes from StackOverflow thread "How to 
  20.  * make a color transparent in a BufferedImage and save as PNG" 
  21.  * (http://stackoverflow.com/questions/665406/how-to-make-a-color-transparent-in-a-bufferedimage-and-save-as-png). 
  22.  */  
  23. public class ImageTransparency  
  24. {  
  25.    /** 
  26.     * Main function for converting image at provided path/file name to have 
  27.     * transparent background. 
  28.     * 
  29.     * @param arguments Command-line arguments: only one argument is required 
  30.     *    with the first (required) argument being the path/name of the source 
  31.     *    image and the second (optional) argument being the path/name of the 
  32.     *    destination file. 
  33.     */  
  34.    public static void main(final String[] arguments) throws Exception  
  35.    {  
  36.       if (arguments.length < 1)  
  37.       {  
  38.          out.println("A source image file must be provided.");  
  39.          System.exit(-1);  
  40.       }  
  41.   
  42.       final String inputFileName = arguments[0];  
  43.       final int decimalPosition = inputFileName.lastIndexOf(".");  
  44.       final String outputFileName =  
  45.            arguments.length > 1  
  46.          ? arguments[1]  
  47.          : inputFileName.substring(0,decimalPosition)+".copy.png";  
  48.   
  49.       out.println("Copying file " + inputFileName + " to " + outputFileName);  
  50.   
  51.       final File in = new File(inputFileName);  
  52.       final BufferedImage source = ImageIO.read(in);  
  53.   
  54.       final int color = source.getRGB(00);  
  55.   
  56.       final Image imageWithTransparency = makeColorTransparent(source, new Color(color));  
  57.   
  58.       final BufferedImage transparentImage = imageToBufferedImage(imageWithTransparency);  
  59.   
  60.       final File out = new File(outputFileName);  
  61.       ImageIO.write(transparentImage, "PNG", out);  
  62.    }  
  63.   
  64.    /** 
  65.     * Convert Image to BufferedImage. 
  66.     * 
  67.     * @param image Image to be converted to BufferedImage. 
  68.     * @return BufferedImage corresponding to provided Image. 
  69.     */  
  70.    private static BufferedImage imageToBufferedImage(final Image image)  
  71.    {  
  72.       final BufferedImage bufferedImage =  
  73.          new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);  
  74.       final Graphics2D g2 = bufferedImage.createGraphics();  
  75.       g2.drawImage(image, 00null);  
  76.       g2.dispose();  
  77.       return bufferedImage;  
  78.     }  
  79.   
  80.    /** 
  81.     * Make provided image transparent wherever color matches the provided color. 
  82.     * 
  83.     * @param im BufferedImage whose color will be made transparent. 
  84.     * @param color Color in provided image which will be made transparent. 
  85.     * @return Image with transparency applied. 
  86.     */  
  87.    public static Image makeColorTransparent(final BufferedImage im, final Color color)  
  88.    {  
  89.       final ImageFilter filter = new RGBImageFilter()  
  90.       {  
  91.          // the color we are looking for (white)... Alpha bits are set to opaque  
  92.          public int markerRGB = color.getRGB() | 0xFFFFFFFF;  
  93.   
  94.          public final int filterRGB(final int x, final int y, final int rgb)  
  95.          {  
  96.             if ((rgb | 0xFF000000) == markerRGB)  
  97.             {  
  98.                // Mark the alpha bits as zero - transparent  
  99.                return 0x00FFFFFF & rgb;  
  100.             }  
  101.             else  
  102.             {  
  103.                // nothing to do  
  104.                return rgb;  
  105.             }  
  106.          }  
  107.       };  
  108.   
  109.       final ImageProducer ip = new FilteredImageSource(im.getSource(), filter);  
  110.       return Toolkit.getDefaultToolkit().createImage(ip);  
  111.    }  
  112. }  

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

makeImageTransparent.groovy
  1. #!/usr/bin/env groovy  
  2. // makeImageTransparent.groovy  
  3.   
  4. import java.awt.Color  
  5. import java.awt.Graphics2D  
  6. import java.awt.Image  
  7. import java.awt.Toolkit  
  8. import java.awt.image.BufferedImage  
  9. import java.awt.image.FilteredImageSource  
  10. import java.awt.image.RGBImageFilter  
  11. import javax.imageio.ImageIO  
  12.   
  13. /* 
  14.  * Script containing code for converting an image's white background to be 
  15.  * transparent, adapted with minor changes from StackOverflow thread "How to 
  16.  * make a color transparent in a BufferedImage and save as PNG" 
  17.  * (http://stackoverflow.com/questions/665406/how-to-make-a-color-transparent-in-a-bufferedimage-and-save-as-png). 
  18.  * 
  19.  * Command-line arguments: only one argument is required 
  20.  * with the first (required) argument being the path/name of the source 
  21.  * image and the second (optional) argument being the path/name of the 
  22.  * destination file. 
  23.  */  
  24.   
  25. if (args.length < 1)  
  26. {  
  27.    println "A source image file must be provided."  
  28.    System.exit(-1);  
  29. }  
  30.   
  31. def inputFileName = args[0];  
  32. def decimalPosition = inputFileName.lastIndexOf(".")  
  33. def outputFileName = args.length > 1 ? args[1] : inputFileName.substring(0,decimalPosition)+".copy.png"  
  34.   
  35. println "Copying file ${inputFileName} to ${outputFileName}"  
  36.   
  37. def input = new File(inputFileName)  
  38. def source = ImageIO.read(input)  
  39.   
  40. def color = source.getRGB(00)  
  41.   
  42. def imageWithTransparency = makeColorTransparent(source, new Color(color))  
  43.   
  44. def final BufferedImage transparentImage = imageToBufferedImage(imageWithTransparency)  
  45.   
  46. def out = new File(outputFileName)  
  47. ImageIO.write(transparentImage, "PNG", out)  
  48.   
  49.   
  50.   
  51. /** 
  52.  * Convert Image to BufferedImage. 
  53.  * 
  54.  * @param image Image to be converted to BufferedImage. 
  55.  * @return BufferedImage corresponding to provided Image. 
  56.  */  
  57. def BufferedImage imageToBufferedImage(final Image image)  
  58. {  
  59.    def bufferedImage =  
  60.       new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);  
  61.    def g2 = bufferedImage.createGraphics();  
  62.    g2.drawImage(image, 00null);  
  63.    g2.dispose();  
  64.    return bufferedImage;  
  65. }  
  66.   
  67. /** 
  68.  * Make provided image transparent wherever color matches the provided color. 
  69.  * 
  70.  * @param im BufferedImage whose color will be made transparent. 
  71.  * @param color Color in provided image which will be made transparent. 
  72.  * @return Image with transparency applied. 
  73.  */  
  74. public static Image makeColorTransparent(final BufferedImage im, final Color color)  
  75. {  
  76.    def filter = new CustomImageFilter(color)  
  77.    def ip = new FilteredImageSource(im.getSource(), filter)  
  78.    return Toolkit.getDefaultToolkit().createImage(ip)  
  79. }  
  80.   
  81. /** 
  82.  * Was 'anonymous inner class' in Java version. 
  83.  */  
  84. public class CustomImageFilter extends RGBImageFilter  
  85. {  
  86.    private int markerRGB  
  87.   
  88.    public CustomImageFilter(final Color color)  
  89.    {  
  90.       // the color we are looking for (white)... Alpha bits are set to opaque  
  91.       markerRGB = color.getRGB() | 0xFFFFFFFF  
  92.    }  
  93.   
  94.    public int filterRGB(final int x, final int y, final int rgb)  
  95.    {  
  96.       if ((rgb | 0xFF000000) == markerRGB)  
  97.       {  
  98.          // Mark the alpha bits as zero - transparent  
  99.          return 0x00FFFFFF & rgb  
  100.       }  
  101.       else  
  102.       {  
  103.          // nothing to do  
  104.          return rgb  
  105.       }  
  106.    }     
  107. }  

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.

2 comments:

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.

@DustinMarx said...

The source code listings shown above are available on GitHub for Java (ImageTransparency.java) and Groovy (makeImageTransparent.groovy).