Saturday, January 5, 2013

How do you make a specific Color transparent in an image in Java?

Suppose you have the following image...

...and you want to make all the "green" on it transparent.  Suppose also that the only graphical editing program on your computer, MS Paint, does not support the setting of transparent colors.  What do you do?  This was the challenge I recently I had to deal with.

Before I started working on the transparency issue I created a few simple tests for myself.  To test that the transparency aspect was working, I painted a red dot on the background of the JPanel.  To ensure that the image was actually being drawn, I added stars to the image. (The image was originally completely "green").

  @Override
  protected void paintComponent(Graphics graphics) {
    super.paintComponent(graphics);

    // if transparency on image worked, then this red dot should show through
    graphics.setColor(Color.RED);
    graphics.fillOval(10, 10, 50, 50);

    graphics.drawImage(image, 0, 0, null);
  }

Here's what that looks like:


Next I did some research on how transparency is handled by Java.  I found David Flanagan's Java Foundation Classes in a Nutshell to be a good source of information, specifically pages 80 and 81.

With this knowledge, I noticed that BufferedImage has a method for getting the color of a specific pixel and another method for setting that pixel's color.  They are getRGB and setRGB respectively.  Unfortunately the image already has its color values set.  So calling setRGB on it does nothing.  Therefore I had to make a pixel by pixel copy of the image.

Here's the solution I came up with.    To conform to best practice, I extracted the code into a utility class:
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageUtility {

  public static BufferedImage makeColorTransparent(String pathToSource, int red, int green, int blue) throws IOException {
    // read original image with no transparency
    BufferedImage original = ImageIO.read(new File(pathToSource));

    // get dimensions of image
    int width = original.getWidth();
    int height = original.getHeight();

    // create a new image that will have transparent colors
    BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);

    // make color transparent
    int oldRGB = new Color(red, green, blue).getRGB();
    int newRGB = new Color(red, green, blue, 0).getRGB();
    int currRGB;

    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        currRGB = original.getRGB(x, y);

        if (oldRGB == currRGB) {
          result.setRGB(x, y, newRGB);
        } else {
          result.setRGB(x, y, currRGB);
        }
      }
    }

    return result;
  }
}

Here's what the ImageUtility looks like when used:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JFrame;
import javax.swing.JPanel;

import src.ImageUtility;

// Class for testing the ability of making a certain color on an image transparent.
public class TransparentBackgroundPanel extends JPanel {

  public static void main(String[] args) throws IOException {
    JFrame frame = new JFrame("~ ShapesPanel ~");
    frame.setSize(200, 320);
    frame.setContentPane(new TransparentBackgroundPanel());
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
  }

  private BufferedImage image;

  public TransparentBackgroundPanel() throws IOException {
    image = ImageUtility.makeColorTransparent(
        "./test.png",  // pathToSource,
        34,                                 // red,
        177,                                // green,
        76                                  // blue
    );
  }

  @Override
  protected void paintComponent(Graphics graphics) {
    super.paintComponent(graphics);

    // if transparency on image worked, then this red dot should show through
    graphics.setColor(Color.RED);
    graphics.fillOval(10, 10, 50, 50);

    graphics.drawImage(image, 0, 0, null);
  }
}

You'll notice that I explicitly state the red, green, and blue values.  That's because Color doesn't have any way to set its alpha value expect through one of its constructors.  So I could have passed a Color object to makeColorTransparent, but I would have still had to access its red, green, and blue values.

Here's the result:

No comments:

Post a Comment