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
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;
}