JAVA图像处理系列(十一)——艺术效果:熔岩

艺术效果:熔岩

与火焰效果一样,借鉴粒子系统的思想,我们也可以在图像中实现类似熔岩的艺术效果。
粒子系统基本上都要通过循环迭代实现,下面先看看本文方法实现的熔岩效果。
第一张为原始图像,第二张为循环迭代30次后的水纹效果,第三张为通过该算法生成的动态GIF图的显示效果。


cat.jpg
cat-lava.jpg
cat-lava.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动画》

你可能感兴趣的:(JAVA图像处理系列(十一)——艺术效果:熔岩)