艺术效果:熔岩
与火焰效果一样,借鉴粒子系统的思想,我们也可以在图像中实现类似熔岩的艺术效果。
粒子系统基本上都要通过循环迭代实现,下面先看看本文方法实现的熔岩效果。
第一张为原始图像,第二张为循环迭代30次后的水纹效果,第三张为通过该算法生成的动态GIF图的显示效果。
算法实现
我们通过一个单独的类来保存粒子的状态,这里直接给出代码,感兴趣的朋友可直接通过阅读代码了解实现细节。
import java.awt.image.BufferedImage;
public class PlasmaRoutine {
int m_iWidth;
int m_iHeight;
int m_iAlpha;
int[] m_pPlasmaBits;
int[] m_pPalletteBuffer;
int[] m_icostbl;
int[] m_PlasmaColors;// Yep 16 colors needed to generate our pallete...
int m_a1, m_a2, m_a3, m_a4, m_b1, m_b2, m_b3, m_b4;
int m_iModifier1;
int m_iModifier2;
int m_iModifier3;
int m_iModifier4;
int m_iXModifier1;
int m_iXModifier2;
int m_iYModifier1;
int m_iYModifier2;
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 PlasmaRoutine() {
m_pPalletteBuffer = new int[256];
m_PlasmaColors = new int[16];
m_icostbl = new int[256];
m_iHeight = 0;
m_iWidth = 0;
m_pPlasmaBits = null;
m_iAlpha = 255;
m_a1 = 0;
m_a2 = 0;
m_a3 = 0;
m_a4 = 0;
m_b1 = 0;
m_b2 = 0;
m_b3 = 0;
m_b4 = 0;
m_iModifier1 = 1;
m_iModifier2 = 2;
m_iModifier3 = 1;
m_iModifier4 = 2;
m_iXModifier1 = -1;
m_iXModifier2 = 3;
m_iYModifier1 = 1;
m_iYModifier2 = -2;
// Set up our defalt plasma colors
m_PlasmaColors[0] = getRGB(0, 0, 0);// From black
m_PlasmaColors[1] = getRGB(0, 0, 255);// To Blue
m_PlasmaColors[2] = getRGB(0, 0, 255);// From Blue
m_PlasmaColors[3] = getRGB(0, 255, 0);// To Green
m_PlasmaColors[4] = getRGB(0, 255, 0);// From Green
m_PlasmaColors[5] = getRGB(0, 255, 255);// To Cyan
m_PlasmaColors[6] = getRGB(0, 255, 255);// Cyan
m_PlasmaColors[7] = getRGB(0, 255, 255);
m_PlasmaColors[8] = getRGB(0, 255, 255);// Cyan
m_PlasmaColors[9] = getRGB(0, 255, 255);
m_PlasmaColors[10] = getRGB(0, 255, 255);// From White
m_PlasmaColors[11] = getRGB(0, 255, 0); // To dark green
m_PlasmaColors[12] = getRGB(0, 255, 0);// From Dark Blue
m_PlasmaColors[13] = getRGB(0, 0, 255);// To dark green
m_PlasmaColors[14] = getRGB(0, 0, 255);// From Dark Blue
m_PlasmaColors[15] = getRGB(0, 0, 0);// To Black
}
public void initCostBLTable() {
for (int t = 0; t < 256; t++)
m_icostbl[t] = (int) (30 * Math.cos(t * (3.14159 / 64)));
}
public void setRGB(int iIndex, int R, int G, int B) {
int color = getRGB(R + 50, G + 50, B + 50);
m_pPalletteBuffer[iIndex] = color;
}
public void initPallette() {
// Create a gradient between all the colors we have for our plasma
CreateGradient(m_PlasmaColors[0], m_PlasmaColors[1], 32, 0); // From black to Blue
CreateGradient(m_PlasmaColors[2], m_PlasmaColors[3], 32, 32); // From Green to Blue
CreateGradient(m_PlasmaColors[4], m_PlasmaColors[5], 32, 64); // From Green to White
CreateGradient(m_PlasmaColors[6], m_PlasmaColors[6], 32, 96);// From Cyan to Cyan
CreateGradient(m_PlasmaColors[8], m_PlasmaColors[9], 32, 128);// Cyan To Cyan
CreateGradient(m_PlasmaColors[10], m_PlasmaColors[11], 32, 160);// While to dark Green
CreateGradient(m_PlasmaColors[12], m_PlasmaColors[13], 32, 192);// Dark Blue to dark green
CreateGradient(m_PlasmaColors[14], m_PlasmaColors[15], 32, 224);// Dark Blue to black
}
public void CreateGradient(int clrStart, int clrEnd, int lSteps, int iStart) {
// I created this routine to make real smooth gradients...
// It may not be real optimized, but it works....and that is what matters to me
// right now...
int r, g, b; // First distance, then starting value
int rTotal, gTotal, bTotal;
int roffset, goffset, boffset;
int scalerR;
int scalerG;
int scalerB;
roffset = goffset = boffset = 0;
scalerR = scalerG = scalerB = 0;
// Get the color differences and scalers
rTotal = getRValue(clrEnd) - getRValue(clrStart);
if (rTotal < 0)
scalerR = -1;
else if (rTotal > 0)
scalerR = 1;
gTotal = getGValue(clrEnd) - getGValue(clrStart);
if (gTotal < 0)
scalerG = -1;
else if (gTotal > 0)
scalerG = 1;
bTotal = getBValue(clrEnd) - getBValue(clrStart);
if (bTotal < 0)
scalerB = -1;
else if (bTotal > 0)
scalerB = 1;
// reset to positives
rTotal = Math.abs(rTotal);
gTotal = Math.abs(gTotal);
bTotal = Math.abs(bTotal);
// Get the starting color values...
r = getRValue(clrStart);
g = getGValue(clrStart);
b = getBValue(clrStart);
for (int i = 0; i < lSteps; i++) {
roffset = i * rTotal / lSteps;
goffset = i * gTotal / lSteps;
boffset = i * bTotal / lSteps;
roffset *= scalerR;
goffset *= scalerG;
boffset *= scalerB;
int color = getRGB((b + boffset), (g + goffset), (r + roffset));
m_pPalletteBuffer[iStart + i] = color;
}
}
public void calcPlasma() {
// Initialize with outer variables
m_a1 = m_b1;
m_a2 = m_b2;
int iIndex = 0;
for (long y = 0; y < m_iHeight; y++) {
// Initialize with outer variables
m_a3 = m_b3;
m_a4 = m_b4;
for (long x = 0; x < m_iWidth; x++) {
if (m_a1 >= 256 || m_a1 < 0)
m_a1 = (m_a1 + 256) % 256;
if (m_a2 >= 256 || m_a2 < 0)
m_a2 = (m_a2 + 256) % 256;
if (m_a3 >= 256 || m_a3 < 0)
m_a3 = (m_a3 + 256) % 256;
if (m_a4 >= 256 || m_a4 < 0)
m_a4 = (m_a4 + 256) % 256;
m_pPlasmaBits[iIndex] = m_icostbl[m_a1] + m_icostbl[m_a2] + m_icostbl[m_a3] + m_icostbl[m_a4];
m_pPlasmaBits[iIndex++] &= 0x00ff;
// Higher values result in many slower plasmas
m_a3 += m_iModifier1;// 4;
m_a4 += m_iModifier2;// 1;
}
// Same as the previous comment
m_a1 += m_iModifier3;// 1;
m_a2 += m_iModifier4;// 4;
}
m_b1 += m_iYModifier1;// y modifier 1
m_b2 += m_iYModifier2;// y modifier 2
m_b3 += m_iXModifier1;// x modifier 1
m_b4 += m_iXModifier2;// x modifier 2
}
public void setAlpha(int iAlpha) {
m_iAlpha = iAlpha;
}
public void create(int iWidth, int iHeight) {
m_pPlasmaBits = new int[(iWidth * iHeight)];
m_iHeight = iHeight;
m_iWidth = iWidth;
// zero out our plasma
for (int i = 0; i < iWidth * iHeight; i++) {
m_pPlasmaBits[i] = 0;
}
// memset(m_pPlasmaBits,0,(iWidth*iHeight));
// Init the Cost Table
initCostBLTable();
// Init our pallette
initPallette();
// Go ahead and calculate a plasma
calcPlasma();
}
public void nullPos() {
calcPlasma();
}
public void render(BufferedImage img, int iwidth, int iheight, int iLineLength, BufferedImage resultImage) {
/*************/
// Right now Im just going to blit it right onto the video memory
int pSrcBitlin;// = m_pFireBits+(m_iWidth*3);// get rid of our fire source
int dst;// =(BYTE*)Dib->pVideoMemory;
int myColor;
int r;
int g;
int b;
for (int i = 0; i < m_iHeight; i++) {
if (i <= iheight) {
for (int x = 0; x < m_iWidth; x++) {
if (x <= iLineLength) {
myColor = m_pPalletteBuffer[m_pPlasmaBits[m_iWidth * i + x]];
r = getRValue(myColor);
g = getGValue(myColor);
b = getBValue(myColor);
int color = img.getRGB(x, i);
r = ((r - getRValue(color)) * m_iAlpha + (getRValue(color) << 8)) >> 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, i, color);
}
}
}
}
calcPlasma();
/*****************/
}
public void setDefaultValues(int[] pExtParms) {
m_iAlpha = 255;
pExtParms[2] = m_iAlpha;
m_iModifier1 = 1;
m_iModifier2 = 2;
m_iModifier3 = 1;
m_iModifier4 = 2;
pExtParms[3] = m_iModifier1;
pExtParms[4] = m_iModifier2;
pExtParms[5] = m_iModifier3;
pExtParms[6] = m_iModifier4;
m_iXModifier1 = -1;
m_iXModifier2 = 3;
m_iYModifier1 = 1;
m_iYModifier2 = -2;
pExtParms[7] = m_iXModifier1;
pExtParms[8] = m_iXModifier2;
pExtParms[9] = m_iYModifier1;
pExtParms[10] = m_iYModifier2;
// Set up our defalt plasma colors
m_PlasmaColors[0] = getRGB(0, 0, 0);// From black
m_PlasmaColors[1] = getRGB(0, 0, 255);// To Blue
m_PlasmaColors[2] = getRGB(0, 0, 255);// From Blue
m_PlasmaColors[3] = getRGB(0, 255, 0);// To Green
m_PlasmaColors[4] = getRGB(0, 255, 0);// From Green
m_PlasmaColors[5] = getRGB(0, 255, 255);// To Cyan
m_PlasmaColors[6] = getRGB(0, 255, 255);// Cyan
m_PlasmaColors[7] = getRGB(0, 255, 255);
m_PlasmaColors[8] = getRGB(0, 255, 255);// Cyan
m_PlasmaColors[9] = getRGB(0, 255, 255);
m_PlasmaColors[10] = getRGB(0, 255, 255);// From White
m_PlasmaColors[11] = getRGB(0, 255, 0); // To dark green
m_PlasmaColors[12] = getRGB(0, 255, 0);// From Dark Blue
m_PlasmaColors[13] = getRGB(0, 0, 255);// To dark green
m_PlasmaColors[14] = getRGB(0, 0, 255);// From Dark Blue
m_PlasmaColors[15] = getRGB(0, 0, 0);// To Black
pExtParms[11] = m_PlasmaColors[0];
pExtParms[12] = m_PlasmaColors[1];
pExtParms[13] = m_PlasmaColors[2];
pExtParms[14] = m_PlasmaColors[3];
pExtParms[15] = m_PlasmaColors[4];
pExtParms[16] = m_PlasmaColors[5];
pExtParms[17] = m_PlasmaColors[6];
pExtParms[18] = m_PlasmaColors[7];
pExtParms[19] = m_PlasmaColors[8];
pExtParms[20] = m_PlasmaColors[9];
pExtParms[21] = m_PlasmaColors[10];
pExtParms[22] = m_PlasmaColors[11];
pExtParms[23] = m_PlasmaColors[12];
pExtParms[24] = m_PlasmaColors[13];
pExtParms[25] = m_PlasmaColors[14];
pExtParms[26] = m_PlasmaColors[15];
initPallette();
}
public void initializePlasma(int[] pExtParms) {
m_iAlpha = pExtParms[2];
m_iModifier1 = pExtParms[3];
m_iModifier2 = pExtParms[4];
m_iModifier3 = pExtParms[5];
m_iModifier4 = pExtParms[6];
m_iXModifier1 = pExtParms[7];
m_iXModifier2 = pExtParms[8];
m_iYModifier1 = pExtParms[9];
m_iYModifier2 = pExtParms[10];
m_PlasmaColors[0] = pExtParms[11];
m_PlasmaColors[1] = pExtParms[12];
m_PlasmaColors[2] = pExtParms[13];
m_PlasmaColors[3] = pExtParms[14];
m_PlasmaColors[4] = pExtParms[15];
m_PlasmaColors[5] = pExtParms[16];
m_PlasmaColors[6] = pExtParms[17];
m_PlasmaColors[7] = pExtParms[18];
m_PlasmaColors[8] = pExtParms[19];
m_PlasmaColors[9] = pExtParms[20];
m_PlasmaColors[10] = pExtParms[21];
m_PlasmaColors[11] = pExtParms[22];
m_PlasmaColors[12] = pExtParms[23];
m_PlasmaColors[13] = pExtParms[24];
m_PlasmaColors[14] = pExtParms[25];
m_PlasmaColors[15] = pExtParms[26];
if (pExtParms[0] != m_iWidth || pExtParms[1] != m_iHeight)
create(pExtParms[0], pExtParms[1]);
else
initPallette();
}
}
测试代码
下面是测试代码:
public class GifEffectLavaTest {
public static void lavaImage() throws IOException {
BufferedImage img = ImageIO.read(new File("cat.jpg"));
PlasmaRoutine pr = new PlasmaRoutine();
pr.create(img.getWidth(), img.getHeight());
pr.setAlpha(30);
for (int i = 0; i < 30; i++) {
pr.nullPos();
}
BufferedImage resultImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
pr.render(img, img.getWidth(), img.getHeight(), img.getWidth(), resultImage);
ImageIO.write(resultImage, "jpeg", new File("cat-lava.jpg"));
}
public static Vector lavaEffect(BufferedImage img) {
Vector images = new Vector<>();
PlasmaRoutine pr = new PlasmaRoutine();
pr.create(img.getWidth(), img.getHeight());
pr.setAlpha(30);
for (int i = 0; i < 50; i++) {
if (i % 5 == 0) {
BufferedImage tmpImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
pr.render(img, img.getWidth(), img.getHeight(), img.getWidth(), tmpImg);
images.add(tmpImg);
} else {
pr.nullPos();
}
}
return images;
}
public static void lavaImageGif() throws IOException {
BufferedImage img = ImageIO.read(new File("cat.jpg"));
OutputStream out = new FileOutputStream("cat-lava.gif");
GifEncoder gif = new GifEncoder();
if (!gif.isInit()) {
gif.init(img.getWidth(), img.getHeight(), out, true);
gif.setDelay(10);
}
Vector images = lavaEffect(img);
for (BufferedImage item : images) {
gif.addImage(item);
}
gif.setCount(0);
gif.encodeMultiple();
out.close();
}
public static void main(String[] argv) throws IOException {
lavaImage();
lavaImageGif();
}
}
其中lavaEffect实现迭代30次后的熔岩效果,lavaImageGif实现每迭代5次生成一张图片,叠加或生成动态GIF图片。GIF图片生成类GifEncoder请参考文章《JAVA实现GIF动画》