Question

I am trying to create several similar visual styles for my programs, each with a different color theme. To do this, I have implemented the use of icons to represent the different states of JCheckBoxs and JRadioButtons. Instead of making one full set of icons for every possible color, is there any way I can just take one set and change the hue/saturation/luminosity/alpha of the image before displaying it?

Was it helpful?

Solution

There is a way, but you'll have to make use of some BufferedImage transformations. Once you create them, cache them or save them off to be easily reused later. Essentially, you want to start off with a black image (source color #000000) that only uses the alpha layer to turn off pixels (also providing smooth anti-aliasing). For example, in your source image, every pixel is black, but the alpha channel differs from pixel to pixel.

First, read this article for some background information: http://www.javalobby.org/articles/ultimate-image/

Once you get done with that primer, you need to load your image into a BufferedImage:

BufferedImage loadImg = ImageUtil.loadImage("C:/Images/myimg.png");

Next you need to create a new BufferedImage to make the transform into:

public BufferedImage colorImage(BufferedImage loadImg, int red, int green, int blue) {
    BufferedImage img = new BufferedImage(loadImg.getWidth(), loadImg.getHeight(),
        BufferedImage.TRANSLUCENT);
    Graphics2D graphics = img.createGraphics(); 
    Color newColor = new Color(red, green, blue, 0 /* alpha needs to be zero */);
    graphics.setXORMode(newColor);
    graphics.drawImage(loadImg, null, 0, 0);
    graphics.dispose();
    return img;
}

Essentially, the setXORMode will XOR the color you provide with the color in the source image. If the source image is black, then whatever color you provide will be written as you specify it. With the new color using "0" for the alpha channel, the original alpha channel values will be respected. The end result is the composite you are looking for.

Edit:

You can load the initial BufferedImage in one of two ways. The easiest is to use Java's newer ImageIO API: http://download.oracle.com/javase/6/docs/api/javax/imageio/ImageIO.html to load the file directly to a BufferedImage. The call would look something like this:

BufferedImage img = ImageIO.read(url); 

Alternatively, you can create a method to read the image using the ToolKit.

public BufferedImage loadImage(String url) {
    ImageIcon icon = new ImageIcon(url);
    Image image = icon.getImage();

    // Create empty BufferedImage, sized to Image
    BufferedImage buffImage = 
      new BufferedImage(
        image.getWidth(null), 
        image.getHeight(null), 
        BufferedImage.TYPE_INT_ARGB);

    // Draw Image into BufferedImage
    Graphics g = buffImage.getGraphics();
    g.drawImage(image, 0, 0, null);
    return buffImage;
}

Of course, if you pay attention, we have to do the exact same thing to read the image into a buffered image as we do to tint it. In short, if you changed the signature of the colorImage method to accept the Image object you only need to make a couple changes to the getWidth() and getHeight() methods to get it to work.

OTHER TIPS

public static void tint(BufferedImage img) {

    for (int x = 0; x < img.getWidth(); x++) {
        for (int y = 0; y < img.getHeight(); y++) {

            Color color = new Color(img.getRGB(x, y));

            // do something with the color :) (change the hue, saturation and/or brightness)
            // float[] hsb = new float[3];
            // Color.RGBtoHSB(color.getRed(), old.getGreen(), old.getBlue(), hsb);

            // or just call brighter to just tint it
            Color brighter = color.brighter();

            img.setRGB(x, y, brighter.getRGB());
        }
    }
}

The easiest way to do this would be by using the Image Filters by JH Labs. You can simply adjust HSB by calling,

public BufferedImage setHSB(BufferedImage source, float hValue, float sValue, float bValue) {        
    com.jhlabs.image.HSBAdjustFilter hsb hsb = new HSBAdjustFilter();
    BufferedImage destination = hsb.createCompatibleDestImage(source, null);
    hsb.setHFactor(hValue);
    hsb.setSFactor(sValue);
    hsb.setBFactor(bValue);
    BufferedImage result = hsb.filter(bi, destination);

    return result;
}

To calculate average for each color component and keep original alpha:

public static void tint(BufferedImage image, Color color) {
    for (int x = 0; x < image.getWidth(); x++) {
        for (int y = 0; y < image.getHeight(); y++) {
            Color pixelColor = new Color(image.getRGB(x, y), true);
            int r = (pixelColor.getRed() + color.getRed()) / 2;
            int g = (pixelColor.getGreen() + color.getGreen()) / 2;
            int b = (pixelColor.getBlue() + color.getBlue()) / 2;
            int a = pixelColor.getAlpha();
            int rgba = (a << 24) | (r << 16) | (g << 8) | b;
            image.setRGB(x, y, rgba);
        }
    }
}

This works best for my case.

I tried every solution on this page, had no luck. The Xor one (accepted answer) didn't work for me - tinted a weird yellow color instead of the color I was giving as an argument no matter what the argument. I finally found an approach that works for me, though it is a bit messy. Figured I'd add it in case anyone else is having the same issues I am with the other solutions. Cheers!

/** Tints the given image with the given color.
 * @param loadImg - the image to paint and tint
 * @param color - the color to tint. Alpha value of input color isn't used.
 * @return A tinted version of loadImg */
public static BufferedImage tint(BufferedImage loadImg, Color color) {
    BufferedImage img = new BufferedImage(loadImg.getWidth(), loadImg.getHeight(),
            BufferedImage.TRANSLUCENT);
    final float tintOpacity = 0.45f;
    Graphics2D g2d = img.createGraphics(); 

    //Draw the base image
    g2d.drawImage(loadImg, null, 0, 0);
    //Set the color to a transparent version of the input color
    g2d.setColor(new Color(color.getRed() / 255f, color.getGreen() / 255f, 
        color.getBlue() / 255f, tintOpacity));

    //Iterate over every pixel, if it isn't transparent paint over it
    Raster data = loadImg.getData();
    for(int x = data.getMinX(); x < data.getWidth(); x++){
        for(int y = data.getMinY(); y < data.getHeight(); y++){
            int[] pixel = data.getPixel(x, y, new int[4]);
            if(pixel[3] > 0){ //If pixel isn't full alpha. Could also be pixel[3]==255
                g2d.fillRect(x, y, 1, 1);
            }
        }
    }
    g2d.dispose();
    return img;
}

This isn't exactly tinting, its more like applying another layer on it but it works for me:

public static BufferedImage colorImage(BufferedImage loadImg, int red, int green, int blue, int alpha /*Also the intesity*/) {
    Graphics g = loadImg.getGraphics();
    g.setColor(new Color(red, green, blue, alpha));
    g.fillRect(0, 0, loadImg.getWidth(), loadImg.getHeight());
    g.dispose();
    return loadImg;
}

Because all methods I found didn't work for me for whatever reason, here is an easy way to approach this (no extra libs needed):

/**
 * Colors an image with specified color.
 * @param r Red value. Between 0 and 1
 * @param g Green value. Between 0 and 1
 * @param b Blue value. Between 0 and 1
 * @param src The image to color
 * @return The colored image
 */
protected BufferedImage color(float r, float g, float b, BufferedImage src) {

    // Copy image ( who made that so complicated :< )
    BufferedImage newImage = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TRANSLUCENT);
    Graphics2D graphics = newImage.createGraphics();
    graphics.drawImage(src, 0, 0, null);
    graphics.dispose();

    // Color image
    for (int i = 0; i < newImage.getWidth(); i++) {
        for (int j = 0; j < newImage.getHeight(); j++) {
            int ax = newImage.getColorModel().getAlpha(newImage.getRaster().getDataElements(i, j, null));
            int rx = newImage.getColorModel().getRed(newImage.getRaster().getDataElements(i, j, null));
            int gx = newImage.getColorModel().getGreen(newImage.getRaster().getDataElements(i, j, null));
            int bx = newImage.getColorModel().getBlue(newImage.getRaster().getDataElements(i, j, null));
            rx *= r;
            gx *= g;
            bx *= b;
            newImage.setRGB(i, j, (ax << 24) | (rx << 16) | (gx << 8) | (bx << 0));
        }
    }
    return newImage;
}

A black image will always stay black, but a white image will be the color you specify. This method goes through every pixel and multiply the red green and blue values of the image with parameters. This is the exact behavior of the OpenGL glColor3f() method. R, G and B params must be 0.0F to 1.0F.

This method has no problem with alpha values.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top