[java] Showcase - Java2d image effects

Started by
8 comments, last by Mattox 17 years, 9 months ago
Hi. I would like a thread where we display, share, comment, and discuss various image effects. (Particle effects, image distortions, etc.) The purpose is to reduce everyone's efforts in trying to "invent the weel" each time they want to do an effect, plus establish a standard through discussion, as to what is considered best practices. I know I learn alot by looking at someone elses magic. I'll post my first sample later tonight after I generalize it abit. It will be an explosion-like effect. I hope everyone puts their egos aside, and share any cool stuff they have, so everyone can benefit and learn from each other. Who knows, someone might come with performance tips for you, to make your effect even better :) Lets keep this thread for Java2d only, (MemoryImageSource / BufferedImage I guess), - a seperate thread for 3D could be created. So, fire away everyone!
Development blog, The Omega Sector -- http://www.omegasector.org
Advertisement
As promised, an explosion like effect.

I removed all dependencies to my external classes and utilities,
but hopefully I did not remove anything that affects the effect in itself.
(If I did, so that nothing works or it doesnt look ok ,let me know, and I will fix it).

import java.awt.Graphics2D;import java.awt.image.BufferedImage;import java.awt.image.DataBufferInt;import java.awt.image.WritableRaster;import java.util.ArrayList;/** * @author Terje B.O * */public class ExplosionShowCase{	private BufferedImage explosionImage;	private WritableRaster explosionRaster;	private DataBufferInt buffer;	private int[] data;	//private int[] dataOriginal;	private Particle[] particles;	private int MAX_PARTICLES;	private boolean isExploding = false;	private int w;	private int h;	private int numParticles;	private int origW;	private int origH;			public ExplosionShowCase(BufferedImage originalImage)	{		if(originalImage!=null)		{			origW = originalImage.getWidth();			origH = originalImage.getHeight();// 			REPLACE THE FOLLOWING LINE WITH YOUR IMAGE-CREATION METHOD//			explosionImage = GameObject.createImage(//					origW*2,origH*2,//					originalImage.getTransparency());			w = explosionImage.getWidth();			h = explosionImage.getHeight();			Graphics2D g = explosionImage.createGraphics();			g.drawImage(originalImage,(explosionImage.getWidth()/2) - (originalImage.getWidth()/2),					(explosionImage.getHeight()/2) - (originalImage.getHeight()/2),null);			g.dispose();			explosionRaster = explosionImage.getRaster();			buffer =  (DataBufferInt)explosionRaster.getDataBuffer();			data = buffer.getData();		}		else throw new IllegalArgumentException("Cannot explode a null image ;-)");			}		private void initParticles()	{		ArrayList<Particle> tempList = new ArrayList<Particle>();		for(int i=0;i<data.length;i++)		{			if(data!=0 /* && ColorUtil.getAlpha(data)!=0*/)			{				tempList.add( createParticle(i) );			}		}		particles = new Particle[tempList.size()];		particles = tempList.toArray(particles);		MAX_PARTICLES = particles.length;		numParticles = MAX_PARTICLES;	}		private Particle createParticle(int index)	{	   Particle p = new Particle();			p.color = data[index];		p.x = getXpos(index);		p.y = getYpos(index);		p.dAngle = getAngle(p.x, p.y);		p.alphaLimit = 5;		p.vx =  (Math.random()*1) * Math.cos(Math.toRadians(p.dAngle));		p.vy =  (Math.random()*1) * Math.sin(Math.toRadians(p.dAngle));		return p;	}		private double getAngle(double x, double y)	{		double dx = x - (double)(w/2);		double dy = y - (double)(h/2);				double angle = Math.toDegrees( Math.atan2(dy,dx));		return angle;	}	private int getXpos(int index)	{		return index % w;	}		private int getYpos(int index)	{		return index / w;	}		private int getIndex(int x, int y)	{		return (w*y) + x;	}		public static int ARGB(int r, int g, int b, int a)	{	    return ( a << 24 ) | ( r << 16) | ( g << 8 ) | b;	}	public static int getAlpha(int rgb)	{		return (rgb&0xff000000)>>24;	}		public static int getRed(int rgb)	{		return (rgb&0x00ff0000)>>16;	}		public static int getGreen(int rgb)	{		return (rgb&0x0000ff00)>>8;	}		public static int getBlue(int rgb)	{		return rgb&0x000000ff;	}		private int getReducedAlpha(int color,int factor)	{		return ARGB(				getRed(color),				getGreen(color),				getBlue(color),				getAlpha(color)-factor);	}		public void render(Graphics2D g, int x, int y)	{		if(isExploding)		{			g.drawImage(explosionImage,					x - (w/2) + (origW/2) ,y - (h/2) + (origH/2) ,null);		}	}		public void updateExplosion()	{				Particle p;		int index;		int alphaDecrease = 5;		for(int i=0;i<MAX_PARTICLES;i++)		{			if(particles==null)				continue;						p = particles;						if(p.isAlive())			{								index = getIndex(p.x(),p.y());				if(index<data.length && index>0)					data[index] = 0;				p.x += p.vx;				p.y += p.vy;								if(p.x-1 < 0 || p.x()+1>=w || p.y-1 < 0 || p.y()+1 >= h)				{					p.kill();					numParticles--;					particles=null;					continue;				}								p.color = getReducedAlpha(p.color,alphaDecrease);								if(p.isAlive())				{					index = getIndex(p.x(),p.y());					data[index]=p.color;				}				else				{					numParticles--;					particles=null;				}			}			else				{					numParticles--;					particles=null;				}				}				if(numParticles<=0)			isExploding=false;	}	public boolean isFinished()	{		return !isExploding;	}	public void start()	{		initParticles();		isExploding = true;			}	public class Particle	{		public int color;		public double vx;		public double vy;		public double dAngle;		public double x;		public double y;		private boolean forcedKill;		public int alphaLimit;				public Particle()		{			forcedKill = false;			alphaLimit = 1;		}		public Particle(double x, double y, int _color, double _angle)		{			this.color = _color;			dAngle = _angle;			forcedKill = false;			alphaLimit = 1;		}				public boolean isAlive()		{			if(forcedKill)			{				forcedKill = false;				return false;			}			// alpha value above 0			return 	((color&0xff000000)>>>24)>alphaLimit;							}				public void kill()		{			forcedKill = true;		}		public int x()		{			return (int)Math.round(x);		}				public int y()		{			return (int)Math.round(y);		}	}}


How to use it:

Simply construct the object with the BufferedImage you'd like blown up.
call the start() method, and in your game loop call updateExplosion() at each
update interval, and render() at each render interval, until isFinished() returns true.

Feedback is always appreciated.

Edit:

I forgot to mention, the code above isn't very flexible. So to make it work, you need to use a 32bit image that supports transparency (a 32bit png works nicely). Increase the multiplier for the sin/cos calls to increase particle speed, decrease alphaDecrease for a longer explosion (note that both these requires your explosionImage to be larger than originalImage*2, - try and fail is the solution here). You can also remove the randomizatoin on the particle velocities for a different, yet equally cool, effect ;)
Development blog, The Omega Sector -- http://www.omegasector.org
No responses after 37 views? None bothered to test it and want to comment how sucky it is? :) None have anything to contribute? Come on! :)
Development blog, The Omega Sector -- http://www.omegasector.org
Okay, I've had a quick look at chunking this all together into a working albeit extremely ugly standalone class.

Couldn't get it to display though, I'm not actually sure how you intended it to be used or how the created image is accessed.

Here's what I threw together, maybe you can get it to work with a quick tinker. BTW, this is not how I write my real code - it's just easiest to supply a standalone class in forums. This looks lousy in previews too - how did you do that nice scrolling box for your code segment?

package JSierra.stubs;

import java.awt.Graphics2D;
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.Timer;
import javax.swing.JFrame;
import javax.swing.JPanel;
import JSierra.Globals;

/**
* @author Terje B.O
*/
public class ExplosionShowCase extends JPanel implements ActionListener
{
private BufferedImage explosionImage;
private WritableRaster explosionRaster;
private DataBufferInt buffer;
private int[] data;
//private int[] dataOriginal;
private Particle[] particles;
private int MAX_PARTICLES;
private boolean isExploding = false;
private int w;
private int h;
private int numParticles;
private int origW;
private int origH;

public static void main( String[] args )
{
JFrame frame = new JFrame();
try
{
ExplosionShowCase showcase = new ExplosionShowCase( ImageIO.read( new File( Globals._characterSourceDirectory + "\\default\\eatenCorpse.png" ) ) );
frame.add( showcase, BorderLayout.CENTER );
showcase.start();
Timer timer = new Timer( 25, showcase );
timer.start();
frame.setVisible( true );
}
catch( IOException e )
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}

public ExplosionShowCase( BufferedImage image )
{
System.out.println( image );
if( image != null )
{
w = image.getWidth();
h = image.getHeight();
BufferedImage transparentImage = new BufferedImage( image.getWidth(), image.getHeight(), 4 );
Graphics2D g = transparentImage.createGraphics();
g.drawImage( image, null, null );
g.dispose();

explosionRaster = transparentImage.getRaster();
buffer = ( DataBufferInt ) explosionRaster.getDataBuffer();
data = buffer.getData();
}
else
{
throw new IllegalArgumentException( "Cannot explode a null image ;-)" );
}

}

private void initParticles()
{
ArrayList<Particle> tempList = new ArrayList<Particle>();
for( int i = 0; i < data.length; i++ )
{
if( data != 0 /* && ColorUtil.getAlpha(data)!=0*/ )
{
tempList.add( createParticle( i ) );
}
}
particles = new Particle[tempList.size()];
particles = tempList.toArray( particles );
MAX_PARTICLES = particles.length;
numParticles = MAX_PARTICLES;
}

private Particle createParticle( int index )
{
Particle p = new Particle();

p.color = data[index];
p.x = getXpos( index );
p.y = getYpos( index );
p.dAngle = getAngle( p.x, p.y );
p.alphaLimit = 5;
p.vx = ( Math.random() * 1 ) * Math.cos( Math.toRadians( p.dAngle ) );
p.vy = ( Math.random() * 1 ) * Math.sin( Math.toRadians( p.dAngle ) );
return p;
}

private double getAngle( double x, double y )
{
double dx = x - ( double ) ( w / 2 );
double dy = y - ( double ) ( h / 2 );

double angle = Math.toDegrees( Math.atan2( dy, dx ) );

return angle;
}

private int getXpos( int index )
{
return index % w;
}

private int getYpos( int index )
{
return index / w;
}

private int getIndex( int x, int y )
{
return ( w * y ) + x;
}

public static int ARGB( int r, int g, int b, int a )
{
return ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b;
}

public static int getAlpha( int rgb )
{
return ( rgb & 0xff000000 ) >> 24;
}

public static int getRed( int rgb )
{
return ( rgb & 0x00ff0000 ) >> 16;
}

public static int getGreen( int rgb )
{
return ( rgb & 0x0000ff00 ) >> 8;
}

public static int getBlue( int rgb )
{
return rgb & 0x000000ff;
}

private int getReducedAlpha( int color, int factor )
{
return ARGB(
getRed( color ),
getGreen( color ),
getBlue( color ),
getAlpha( color ) - factor );
}

public void render( Graphics2D g, int x, int y )
{
if( isExploding )
{
g.drawImage( explosionImage,
x - ( w / 2 ) + ( origW / 2 ), y - ( h / 2 ) + ( origH / 2 ), null );
}
}

public void paintComponent( Graphics g )
{
super.paintComponent( g );
render( ( Graphics2D ) g, getWidth() / 2, getHeight() / 2 );
}

public void updateExplosion()
{

Particle p;
int index;
int alphaDecrease = 5;
for( int i = 0; i < MAX_PARTICLES; i++ )
{
if( particles == null )
{
continue;
}

p = particles;

if( p.isAlive() )
{

index = getIndex( p.x(), p.y() );
if( index < data.length && index > 0 )
{
data[index] = 0;
}

p.x += p.vx;
p.y += p.vy;

if( p.x - 1 < 0 || p.x() + 1 >= w || p.y - 1 < 0 || p.y() + 1 >= h )
{
p.kill();
numParticles--;
particles = null;
continue;
}

p.color = getReducedAlpha( p.color, alphaDecrease );

if( p.isAlive() )
{
index = getIndex( p.x(), p.y() );
data[index] = p.color;
}
else
{
numParticles--;
particles = null;
}
}
else
{
numParticles--;
particles = null;
}

}

if( numParticles <= 0 )
{
isExploding = false;
}
}

public boolean isFinished()
{
return !isExploding;
}


public void start()
{
initParticles();
isExploding = true;

}

public void actionPerformed( ActionEvent e )
{
if( !isFinished() )
{
updateExplosion();

}
}

public class Particle
{
public int color;
public double vx;
public double vy;
public double dAngle;
public double x;
public double y;
private boolean forcedKill;
public int alphaLimit;

public Particle()
{
forcedKill = false;
alphaLimit = 1;
}

public Particle( double x, double y, int _color, double _angle )
{
this.color = _color;
dAngle = _angle;
forcedKill = false;
alphaLimit = 1;
}

public boolean isAlive()
{
if( forcedKill )
{
forcedKill = false;
return false;
}
// alpha value above 0
return ( ( color & 0xff000000 ) >>> 24 ) > alphaLimit;

}

public void kill()
{
forcedKill = true;
}

public int x()
{
return ( int ) Math.round( x );
}

public int y()
{
return ( int ) Math.round( y );
}
}
}
jesus christ, dude, use a source tag!

[Formerly "capn_midnight". See some of my projects. Find me on twitter tumblr G+ Github.]

The example was made to be used in a normal renderloop. Pasting all the components in here is an overkill. The basics is that you got a JFrame where you create a bufferstrategy. (or in your Canvas, if you don't care about lightweight swing components). Then, from your render loop, you do something like this:

public void run(){  Graphics2D g;  explosion.start();  while(!explosion.isFinished())  {   explosion.updateExplosion();   g = getMyBufferStrategyGraphics();   explosion.render(g,50,50);   g.dispose();   try  {   Thread.sleep(15);  }  catch(InterruptedException ie)  {}}


That's the simple way of doing it. Again, written on the fly so might contain typos.
Development blog, The Omega Sector -- http://www.omegasector.org
My server is down from where I just reinstalled windows, but if this thread is still floating around when I get my site back up I have some fancy fire particle effects that I like to show off. I have standard fire, explosion, and a cool fireball you can throw around. Eh, I can't wait so I'll post the source for the basic fire since it is the shortest. When my site comes back up I can link to the other ones. I have some pretty cool general image stuff as well let's see um, rotation, translation(inside an image), image seperation and overlay, blur, motion blur, invisible backgrounds, and disintegrate. The disintegrate is cool and actually really easy to implement. You just treat the pixel array like an array of sand and run it a couple times per frame. Anyways, I'll link when I get the chance and I can save my rambling for another day.

http://www.alrecenk.cjb.net:81/Java/Show/fire/index.php

edit:
removed code in thread and replaced with link to applets
the source is still available in the same folder linked to above with the following filenames:
fire.java
test.java (explosion)
test2.java (normal fire)
test3.java (fireball)


[Edited by - Alrecenk on July 20, 2006 5:23:17 AM]
My site is back up, yay. Here is my general image stuff:

http://www.alrecenk.cjb.net:81/Java/Show/graphics/

Has some fairly fancy techqniques, but you can just look for yourself. I can post code later if anyone is interested. It uses a texture class that has all the methods built into it so it should be fairly portable. All you really need to know is how to get a pixel array that uses ARGB ints onto the screen. On second thought that may be built in too...
Quote:Original post by Addictman
No responses after 37 views? None bothered to test it and want to comment how sucky it is? :) None have anything to contribute? Come on! :)


Soorry dude, I usually stay on the network side of projects.. And when they're single player games, I like to do AI and game logic. Rendering and graphics is not my thing, when I'm forced to work on them I follow someone's guidelines, hardly come up with something new.

But still, I love code showcase threads :D Keep up the good work!

a.k.a javabeats at yahoo.ca
I think I played with the fireball applet for 15 minutes :S

This topic is closed to new replies.

Advertisement