3

I am writing an application where the user can change the color of the image on screen by choosing a new color. I have a simple image - only 40x40 - like this: Character Head

I have tried many things: pixel replacement, ColorModel, RGBFilter, etc. I can't figure this image stuff out. In the process of trying, I have learned about BufferedImage and I can get the .png's into that format and displayed on screen. I can turn the shape into a solid blob of color using pixel replacement, but the results are horrible.

From what I've gleaned, I want to use a ColorModel or Filter, but I am stumped. Here is a simple app that demonstrates where I am.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;


public class ChangeImageColor {
  public static void main(String[] args) {
    final ChangeColor cc = new ChangeColor();
    java.awt.EventQueue.invokeLater( new Runnable() { @Override
      public void run() { cc.setVisible( true ); }}
    ); // invokeLater
  } // main
} // ChangeImageColor

  class ChangeColor extends JFrame {
    String  CHAR_HEAD    = "res/images/char/Head.png";
    JLabel  imageHead    = new JLabel( new ImageIcon( CHAR_HEAD ) );
    JButton buttonChoose = new JButton( "Choose Color" );

    public ChangeColor() {
      this.setSize( 200, 200 );
      this.setLayout( new BorderLayout() );

      buttonChoose.addActionListener( listenerButton );

      this.add( imageHead, BorderLayout.CENTER );
      this.add( buttonChoose, BorderLayout.SOUTH );
    } // constructor

    ActionListener listenerButton = new ActionListener() {
      @Override public void actionPerformed( ActionEvent event ) {
        Color newColor = JColorChooser.showDialog( null, "Choose Color", Color.RED );
        ImageIcon icon = new ImageIcon( CHAR_HEAD );

        System.out.println( "~" + newColor );

        // *****************
        // insert code to change color of Head.png to shades of chosen color
        // *****************

        imageHead.setIcon( icon );
      } // actionPerformed
    };

  } // class

(I've never worked with dynamic colors and images, so I'm way over my head. Thanks in advance for your help.)

Edit: Here is a "before" and "after" image of what I would like after a darker color is chosen:

enter image description here enter image description here

Jon
  • 198
  • 1
  • 2
  • 12
  • Are you restricting the colors the user can choose? What parts of the image do you want to be the new color? – Matt Mills Nov 06 '11 at 20:02
  • 1
    I think he wants the grayscale image colors as some kind of brightness for the chosen color. – foowtf Nov 06 '11 at 20:24
  • Yes, the image is grayscale and has between 2 and 6 shades of gray in it. I want to adjust all of those shades to reflect the chosen color. – Jon Nov 06 '11 at 22:38
  • Well, doesn't my answer help you, any questions? – foowtf Nov 07 '11 at 00:00

2 Answers2

5

Perhaps there is a more elegant and efficient version but if you have a BufferedImage you could try something like:

BufferedImage image;
for(int y = 0; y < image.getHeight(); y++)
    for(int x = 0; x < image.getWidth(); x++)
    {
        Color imageColor = new Color(image.getRGB(x, y));
        //mix imageColor and desired color 
        image.setRGB(x, y, imageColor.getRGB());
    }

Maybe this is the real problem: Mixing the two colors can be done by multiplying them...

Edit:

private Color multiply(Color color1, Color color2)
{
    float[] color1Components = color1.getRGBComponents(null);   
    float[] color2Components = color2.getRGBColorComponents(null);
    float[] newComponents = new float[3]; 

    for(int i = 0; i < 3; i++)
        newComponents[i] = color1Components[i] * color2Components[i];

    return new Color(newComponents[0], newComponents[1], newComponents[2],
        color1Components[3]);
}
foowtf
  • 399
  • 1
  • 9
  • +1 for `setRGB()`; I've amplified [here](http://stackoverflow.com/questions/8029903/how-to-change-the-color-of-an-icon-based-on-user-action/8032538#8032538). – trashgod Nov 07 '11 at 03:50
  • I tried setRGB and it changed the color, but it all came out as a single color. I couldn't figure out how to multiply them, as you suggest. I need to take into consideration the additional shades in the original image. – Jon Nov 08 '11 at 01:21
  • A RGB color has three components: red, green, blue which range between 0 and 255 (one byte). If you convert the bytes to floats from 0.0 to 1.0 you can multiply each component. For your grayscale image each pixel has an equal intensity for each color component, that way it acts as a brightness factor (0 is black and 1 is white). I added code to my answer to explain this (untested), hope that helps... – foowtf Nov 08 '11 at 19:38
  • Yes, that helps. I will try it out. I had no idea there was a brightness there. I've seen references to float, bytes, 'pre-multiplied', and shifts - it just went over my head. Reading the doc on getRGBColorComponents has made things click. I'll let you know how it comes out. – Jon Nov 09 '11 at 02:31
  • This is working, but I am having trouble with the transparency. I am getting a large black box around the image. The .png is 40x40 and I figure it is ignoring Alpha, but I haven't figured out how to preserve that. Any pointers there? – Jon Nov 09 '11 at 15:47
  • Oh yes, I forgot this. There's a Color constructor taking 4 arguments with the alpha value as the fourth one. I updated the code to return a color that has the alpha value of the color1 parameter. You'd have to pass the image color as color1... – foowtf Nov 09 '11 at 16:11
  • Yep, this does the trick! The Alpha part doesn't have the desired effect, so I added a check to see if the pixel is empty. I only change the color if it is not empty. My previous attempt at this had me multiplying the int values (0-255). I didn't understand the component values, but I do now. Thanks a bunch for your help. – Jon Nov 10 '11 at 01:09
3

If you know the colors you want in advance, you can use a LookupOp with a LookupTable. Examples may be found in Using the Java 2D LookupOp Filter Class to Process Images and Image processing with Java 2D.

If not, darker() or getHSBColor() may helpful, as shown here in the context of BufferedImage.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I don't know what the customer will choose for a color - hence the JColorChooser Dialog. i don't know if that changes your suggestion. – Jon Nov 08 '11 at 01:19
  • 1
    Ah, you're reating an _editor_. You can implement an _eye dropper_ tool, as suggested in this [example](http://stackoverflow.com/questions/2900801/wanting-a-type-of-grid-for-a-pixel-editor/2901472#2901472) that displays RGB values as the mouse moves. The three source RGB components are indexes into the `LookupTable`; the chosen RGB components are the new entries at those indexes. – trashgod Nov 08 '11 at 04:20