Colouring a grayscale image

I’m now using grayscale alpha images for particles in my basic particle generator but I’m not sure how I’m meant to colour these right also need the fastest way ;).
Can anyone help?

Why do you use images for particles? Couldn’t you use fillRect, for example?

Anyway, to be honest I’m not so familiar with particle generation - it would
be useful if you showed some piece of code (or just a simple explanation of
how it is done and why images were used)…

Dmitri

I think he just want an image multiplied by a color, like you can do in OpenGL with textures.

You could create a Composite class for colors.

Here is something similar, that darkens an image:
http://www.koders.com/java/fid9AD328238C12CCB6A7500C98D20EC1622412BABB.aspx?s=rgbcomposite

I actually created a composite class not too long ago, that takes a color and transforms all the non-transparent parts. Not sure if that works in your case? I could probably post the code…

Well I can post code if you wish, but its not pretty yet…

This is generator code, which has a array of particles(which hold x,y,creation time). As you can see the old code is commented out that used fill but that wasn’t getting the effect I wanted and by reading other particle posts I it seems that images were faster.
I was just wondering if there was just a basic method that could just simply color the particles.


	public void render(final Graphics2D g){
		if(stop){
			return;
		}
		
		final long currentTime = System.currentTimeMillis();
		int i =0;
		final long leftOver = currentTime - lastUpdateTime;

		Particle currentParticle;
		final Random randomNumber = new Random();
		int partX, partY,partAngle, alpha, newSize;
		long age;
		float percentLifeLeft;
		while(i < particles.size()){
			currentParticle = particles.get(i);
			age = currentTime - currentParticle.getCreationTime();
			if(age > aliveTime){
				particles.remove(i);
				continue;
			}
			
			percentLifeLeft = (float)age/(float)aliveTime;
			if(startSize == finishSize){
				newSize = startSize;
			}
			newSize = (int)(startSize - startSize*percentLifeLeft);
			
			if(newSize == 0){
				particles.remove(i);
				continue;
			}
			
			
			partX = currentParticle.getX();
			partY = currentParticle.getY();
			
			
			partAngle = currentParticle.getAngle();
			alpha = (int)(255 -255 * percentLifeLeft);
			
			//if(alpha < 255 - ALPHACUTOFFRANGE){
			//	g.setColor(new Color(0,0,255,alpha));
			//}
			//else{
			//	g.setColor(new Color(0,0,255));
			//}
			
			//g.fillRect(partX, partY, newSize, newSize);
			
			
			g.drawImage(particleImage, partX, partY,newSize,newSize, null);
			//g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
			if(leftOver > 1000){
				currentParticle.setX((int)(partX + Math.sin(partAngle)*speed));
				currentParticle.setY((int)(partY + Math.cos(partAngle)*speed));
			}
			i++;

		}

		if(leftOver > 1000){
			if(particles.size() <= maxParticles){
				for(int j = 0; j< particlesPerSec; j++){
					particles.add(new Particle(x,y,randomNumber.nextInt(360),currentTime));
				}
			}
			lastUpdateTime = currentTime - (leftOver-1000);
		}

	}


I attached you the composite classes (copy them somewhere into your project). The bold code sample below colors all non-transparent areas with the color requested (also works for translucent images):

Color color = …
Composite oldComposite = g2.getComposite();
g2.setComposite(new ColorComposite(1.0f, color);
g2.drawImage(image, 0, 0, null);
g2.setComposite(oldComposite);

Please note that my ColorComposite class ignores the alpha value! It works well, it just could have been implemented better. Let me know if you have questions…

/**
 * Color Composite.
 * 
 * @author Christoph Aschwanden
 * @since June 9, 2008
 */
public final class ColorComposite extends RGBComposite {

  /** The color. */
  private Color color;
  
  
  /**
   * Constructor.
   * 
   * @param alpha  The alpha level.
   * @param color  The color.
   */
  public ColorComposite(float alpha, Color color) {
    super(alpha);
    this.color = color;
  }

  /**
   * Creates the context.
   * 
   * @param srcColorModel  The source color model.
   * @param dstColorModel  The destination color model.
   * @param hints  The rendering hints.
   * @return The context.
   */
  public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints) {
    return new Context(extraAlpha, color, srcColorModel, dstColorModel);
  }

  /**
   * The context.
   */
  static class Context extends RGBCompositeContext {

    /** The color. */
    private int color;
    
    
    /**
     * Constructor for context.
     * 
     * @param alpha  The alpha level.
     * @param color  The color.
     * @param srcColorModel  The source color model.
     * @param dstColorModel  The destination color model.
     */
    public Context(float alpha, Color color, ColorModel srcColorModel, ColorModel dstColorModel) {
      super(alpha, srcColorModel, dstColorModel);
      
      // save color
      this.color = color.getRGB() & 0x00ffffff;
    }

    /**
     * The composing function. 
     * 
     * @param srcRGB  The source RGB.
     * @param dstRGB  The pre-destination RGB.
     * @param alpha  The alpha level in the range [0, 1].
     * @return  The compbined destination RGB.
     */
    @Override
    public int composeRGB(int srcRGB, int dstRGB, float alpha) {
      int opacity = (srcRGB >> 24) & 0xff;
      if (opacity > 0) {
        int r0 = (this.color >> 16) & 0xff;
        int g0 = (this.color >> 8) & 0xff;
        int b0 = (this.color) & 0xff;    
        r0 = r0 * opacity / 255;
        g0 = g0 * opacity / 255;
        b0 = b0 * opacity / 255;
        
        int r1 = (dstRGB >> 16) & 0xff;
        int g1 = (dstRGB >> 8) & 0xff;
        int b1 = (dstRGB) & 0xff;    
        int revertOpacity = 255 - opacity;
        r1 = r1 * revertOpacity / 255;
        g1 = g1 * revertOpacity / 255;
        b1 = b1 * revertOpacity / 255;

        return 0xff000000 | ((r0 + r1) << 16) | ((g0 + g1) << 8) | (b0 + b1);
      }
      else {
        // was fully transparent...
        return dstRGB;
      }
    }
  }
}
/**
 * Color Composite. Based on code by "Jerry Huxtable".
 * 
 * @author Christoph Aschwanden
 * @since June 9, 2008
 */
public abstract class RGBComposite implements Composite {

  /** The extra alpha value. */
  protected float extraAlpha;

  /**
   * Constructor.
   */
  public RGBComposite() {
    this(1.0f);
  }

  /**
   * Constructor.
   * 
   * @param alpha  The alpha level.
   */
  public RGBComposite(float alpha) {
    if (alpha < 0.0f || alpha > 1.0f) {
      throw new IllegalArgumentException("RGBComposite: alpha must be between 0 and 1");
    }
    this.extraAlpha = alpha;
  }

  /**
   * Returns alpha.
   * 
   * @return  Alpha level.
   */
  public float getAlpha() {
    return extraAlpha;
  }

  /**
   * Returns the hash code.
   * 
   * @return  The hash code.
   */
  public int hashCode() {
    return Float.floatToIntBits(extraAlpha);
  }

  /**
   * True if equal. 
   * 
   * @param object  The other object to compare this to.
   * @return  True for equal.
   */
  public boolean equals(Object object) {
    if (!(object instanceof RGBComposite)) {
      return false;
    }
    RGBComposite c = (RGBComposite)object;

    if (extraAlpha != c.extraAlpha) {
      return false;
    }
    return true;
  }

  /**
   * The context.
   */
  public abstract static class RGBCompositeContext implements CompositeContext {

    /** The alpha level. */
    private float alpha;
    /** The source model. */
    private ColorModel srcColorModel;
    /** The destination model. */
    private ColorModel dstColorModel;
    
    
    /**
     * Constructor for context.
     * 
     * @param alpha  The alpha level.
     * @param srcColorModel  The source color model.
     * @param dstColorModel  The destination color model.
     */
    public RGBCompositeContext(float alpha, ColorModel srcColorModel, ColorModel dstColorModel) {
      this.alpha = alpha;
      this.srcColorModel = srcColorModel;
      this.dstColorModel = dstColorModel;
    }

    /**
     * Dispose function. 
     */
    public void dispose() {
      // not used
    }

    /**
     * The composing function. 
     * 
     * @param srcRGB  The source RGB.
     * @param dstRGB  The pre-destination RGB.
     * @param alpha  The alpha level.
     * @return  The compbined destination RGB.
     */
    public abstract int composeRGB(int srcRGB, int dstRGB, float alpha);

    /**
     * Composer.
     * 
     * @param src  The source.
     * @param dstIn  The destination in.
     * @param dstOut  The destination out.
     */
    public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
      float alpha = this.alpha;

      int x0 = dstOut.getMinX();
      int x1 = x0 + dstOut.getWidth();
      int y0 = dstOut.getMinY();
      int y1 = y0 + dstOut.getHeight();

      for (int x = x0; x < x1; x++) {
        for (int y = y0; y < y1; y++) {
          int srcRGB = srcColorModel.getRGB(src.getDataElements(x, y, null));
          int dstInRGB = dstColorModel.getRGB(dstIn.getDataElements(x, y, null));
          int dstOutRGB = composeRGB(srcRGB, dstInRGB, alpha);
          dstOut.setDataElements(x, y, dstColorModel.getDataElements(dstOutRGB, null));
        }
      }  
    }
  }
}

Thanks so much I’ll give it a try and tell how it goes tonight.

Let me know if you run into problems. It works great for my by the way ;D

Double checking, I add this in my generator render method:


Composite oldComposite = g2.getComposite();
g2.setComposite(new ColorComposite(1.0f, color);
g2.drawImage(image, 0, 0, null);
g2.setComposite(oldComposite);

When I do this, my cpu shoots up to 99 (from 2-3)and only see 1-2 particles on screen.

Sorry, I forgot to mention: it’s NOT fast.

Let me make a suggestion: don’t use the color composite in each rendering loop. Just create a copy of the original image the first time you show a new color. I assume you do not need to change the image in each loop, just keep 1 or several copies of the original image with the different colors you are planning to render with. I assume your particles are rather small, so having 20-50 particle images cached doesn’t really use much memory?

EDIT: If you are using Java2D, there is no silver bullet for fast rendering to my knowledge. There are other techniques you could use such as a flood fill but it’s probably going to be even slower…

Ah I see. I guess I have a few options.
Depending how much I step though the alpha/colours would depend on how many images I need. Though I guess it wouldn’t be large in the ram…
Otherwise I keep with the circles, which are not that great on top of each other.
Or could have a option for both :slight_smile:

Though would thought there would be a easier way to colour a image. Since there’s easy and fast way to make a colour image to grayscale.

Heres some code I wrote a few months ago.


	/**
	 * Create a new BufferedImage that is a color version of the original grayscale image.
	 * 
	 * @param grayscaleImg The source image to be colored.
	 * @param newColor The color to use for the coloring of the image.
	 * @return The new color image.
	 */
	static public BufferedImage colorImage(BufferedImage grayscaleImg, Color newColor){
		int [] pixels = grabPixels(grayscaleImg);
		if (pixels==null || newColor == null)
			return grayscaleImg;
		int r, g, b, a, shade, red, green, blue, color;
		red = (0x00FF0000 & newColor.getRGB()) >> 16;
		green = (0x0000FF00 & newColor.getRGB()) >> 8;
		blue = (0x000000FF & newColor.getRGB());
		for (int i=0; i<pixels.length;i++){
			a = pixels[i] >> 24;
		if(a!=0){
			shade = (0x000000FF & pixels[i]);
			r = (red*shade/255);
			g = (green*shade/255);
			b = (blue*shade/255);
			a <<= 24;
			r <<= 16;
			g <<= 8;
			//b <<= 0;
			color = a|r|g|b;
			pixels[i] = color;
		}
		}
		return makeImage(pixels, grayscaleImg.getWidth(), grayscaleImg.getHeight());
	}

	/**
	 * This function creates an integer array from the pixel colors of the image passed into it.
	 * 
	 * @param img The source image.
	 * @return An integer array containing the color information. The 4 bytes of the integer are used for each color attribute, the first byte is the alpha information and the last 3 are the RGB(Red Green Blue) values.
	 */
	static public int[] grabPixels(BufferedImage img) {
		int w = img.getWidth();
		int h = img.getHeight();
		int[] pixels = new int[w * h];
		try {
			img.getRGB(0,0,w,h,pixels, 0, w);
		} catch (Exception e) {
			System.err.println("interrupted waiting for pixels!");
			return null;
		}
		return pixels;
	}
	/**
	 * Create a new BufferedImage from the information provided.
	 * 
	 * @param pixels The color information stored as an integer. The 4 bytes of the integer are used for each color attribute, the first byte is the alpha information and the last 3 are the RGB(Red Green Blue) values.
	 * @param sizeX The width in pixels of the image.
	 * @param sizeY The height in pixels of the image.
	 * @return A new image created from the information.
	 */
	static public BufferedImage makeImage(int[] pixels, int sizeX, int sizeY) {
		BufferedImage img = new BufferedImage(sizeX, sizeY, BufferedImage.TYPE_INT_ARGB);
		img.setRGB(0,0,sizeX, sizeY, pixels, 0, sizeX);
		return img;
	}