艺术效果:火焰
火焰在3D作图中一般是通过粒子系统实现的,借鉴粒子系统的思想,我们也可以在图像中实现类似火焰的艺术效果。
粒子系统基本上都要通过循环迭代实现,下面先看看本文方法实现的火焰效果。
第一张为原始图像,第二张为循环迭代100次后的火焰效果,第三张为通过该算法生成的动态GIF图的显示效果。
cat.jpg
cat-fire.jpg
cat-fire.gif
算法实现
我们通过一个单独的类来保存粒子的状态,这里直接给出代码,感兴趣的朋友可直接通过阅读代码了解实现细节。
import java.awt.image.BufferedImage;
public class FireRoutine {
java.util.Random r = new java.util.Random(System.currentTimeMillis());
int m_iFlameHeight;
int m_iWidth;
int m_iHeight;
int m_iFireSource;//The y position for the lit spots
int m_iFireChance;
int m_iAvgFlameWidth;
int m_iAlpha;
int[] m_FireColors;
int[] m_pFireBits;
int[] m_pPalletteBuffer;
int[] m_pYIndexes;
public int getRGB(int r, int g, int b) {
return (0x00ff&r)<<16 | (0x00ff&g) << 8 | (0x00ff & b);
}
public int getBValue(int color) {
return color & 0x00ff;
}
public int getGValue(int color) {
return (color >> 8) & 0x00ff;
}
public int getRValue(int color) {
return (color >> 16) & 0x00ff;
}
public FireRoutine() {
m_FireColors = new int[4];
m_pPalletteBuffer = new int[256];
m_iWidth = 0;
m_iHeight = 0;
m_pFireBits = null;
m_pYIndexes = null;
m_iFireSource = 2;
m_iFireChance = 10;
m_iFlameHeight = 50;
m_iAlpha = 255;
m_FireColors[0] = getRGB(0,0,0);// Black
m_FireColors[1] = getRGB(255,0,0);// Red
m_FireColors[2] = getRGB(255,255,0);// Yellow
m_FireColors[3] = getRGB(255,255,255);// White
m_iAvgFlameWidth = 35;
}
public void setParam(int iWidth, int iHeight, int iAlpha) {
this.m_iWidth = iWidth;
this.m_iHeight = iHeight;
this.m_iAlpha = iAlpha;
}
public void initFire() {
// Add three to the height
m_iHeight+=3;
m_pYIndexes = new int[m_iHeight];
m_pFireBits = new int[m_iWidth*m_iHeight];
// Clear the Fire bits
for (int i=0; i < m_iWidth*m_iHeight; i++) {
m_pFireBits[i] = 0;
}
// do all the y index pre-calc..
for (int y = m_iHeight; y >0; y--)
m_pYIndexes[y-1] = (y-1) * m_iWidth;
// Create our pallete
initPallette();
clrHotSpots();
}
public void clrHotSpots() {
for (int i = 0; i < m_iWidth; i++) {
m_pFireBits[m_pYIndexes[m_iFireSource] + i] = 0;
}
// memset(&m_pFireBits[m_pYIndexes[m_iFireSource]],0,m_iWidth);
}
public void initPallette() {
long iCount = 0;
int clrStart;
int clrEnd;
for(int iColor = 1;iColor<4;iColor++)
{
clrStart = m_FireColors[iColor-1];
clrEnd = m_FireColors[iColor];
int r, g, b; // First distance, then starting value
float rStep, gStep, bStep; // Step size for each color
// Get the color differences
r = (getRValue(clrEnd) - getRValue(clrStart));
g = (getGValue(clrEnd) - getGValue(clrStart));
b = (getBValue(clrEnd) - getBValue(clrStart));
int nSteps = Math.max(Math.abs(r), Math.max(Math.abs(g), Math.abs(b)));
float fStep = (float)(255/3)/ (float)nSteps;
// Calculate the step size for each color
rStep = r/(float)nSteps;
gStep = g/(float)nSteps;
bStep = b/(float)nSteps;
// Reset the colors to the starting position
r = getRValue(clrStart);
g = getGValue(clrStart);
b = getBValue(clrStart);
for (int iOnBand = 0; iOnBand < nSteps; iOnBand++)
{
//COLORREF color = RGB(r+rStep*iOnBand, g + gStep*iOnBand, b + bStep *iOnBand);
int color = getRGB((int)(r + rStep *iOnBand), (int)(g + gStep*iOnBand),(int)(b+bStep*iOnBand));
int lIndex = (int)(iOnBand * fStep);
if(lIndex+((iColor-1)*85) < 255)
m_pPalletteBuffer[lIndex+((iColor-1)*85)] = color;
}
}
// Step on the second color a little bit...
clrStart = m_FireColors[0];
clrEnd = m_FireColors[1];
for(int kj=0;kj>8;
g = ((g-getGValue(color))*m_iAlpha+(getGValue(color)<<8))>>8;
b = ((b-getBValue(color))*m_iAlpha+(getBValue(color)<<8))>>8;
color = getRGB(r, g, b);
resultImage.setRGB(x, iHeight-i-1, color);
}
}
}
public int average(int x, int y) {
int ave_color;
int ave1, ave2, ave3, ave4, ave5, ave6, ave7;
// Make sure we are not at the last line...
if(y == m_iHeight)
ave1 = m_pFireBits[m_pYIndexes[y-1] + x];
else
ave1 = m_pFireBits[m_pYIndexes[y + 1] + x];
ave2 = m_pFireBits[m_pYIndexes[y - 1] + x];
ave3 = m_pFireBits[m_pYIndexes[y] + x + 1];
ave4 = m_pFireBits[m_pYIndexes[y] + x - 1];
ave5 = m_pFireBits[m_pYIndexes[y] + x + 2];
ave6 = m_pFireBits[m_pYIndexes[y] + x - 2];
ave7 = m_pFireBits[m_pYIndexes[y] + x];
ave_color = (int)((ave1 + ave2 + ave3 + ave4 + ave5 + ave6 + ave7) / 7);
return(ave_color);
}
}
测试代码
下面是测试代码:
public class GifEffectTest {
public static void fireImage() throws IOException {
BufferedImage img = ImageIO.read(new File("cat.jpg"));
FireRoutine fr = new FireRoutine();
fr.setParam(img.getWidth(), img.getHeight(), 50);
fr.initFire();
for (int i = 0; i < 100; i++) {
fr.nullPos();
}
BufferedImage resultImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
fr.render(img, img.getWidth(), img.getHeight(), resultImage);
ImageIO.write(resultImage, "jpeg", new File("cat-fire.jpg"));
}
public static Vector fireEffect(BufferedImage img) {
Vector images = new Vector<>();
FireRoutine fr = new FireRoutine();
fr.setParam(img.getWidth(), img.getHeight(), 50);
fr.initFire();
for (int i = 0; i < 100; i++) {
if (i % 5 == 0) {
BufferedImage tmpImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
fr.render(img, img.getWidth(), img.getHeight(), tmpImg);
images.add(tmpImg);
} else {
fr.nullPos();
}
}
return images;
}
public static void fireImageGif() throws IOException {
BufferedImage img = ImageIO.read(new File("cat.jpg"));
OutputStream out = new FileOutputStream("cat-fire.gif");
GifEncoder gif = new GifEncoder();
if (!gif.isInit()) {
gif.init(img.getWidth(), img.getHeight(), out, true);
gif.setDelay(10);
}
Vector images = fireEffect(img);
for (BufferedImage item : images) {
gif.addImage(item);
}
gif.setCount(0);
gif.encodeMultiple();
out.close();
}
public static void main(String[] argv) throws IOException {
fireImage();
fireImageGif();
}
}
其中fireEffect实现迭代100次后的火焰效果,fireImageGif实现每迭代5次生成一张图片,叠加或生成动态GIF图片。GIF图片生成类GifEncoder请参考文章《JAVA实现GIF动画》