博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
图像处理之泛洪填充算法(Flood Fill Algorithm)
阅读量:5037 次
发布时间:2019-06-12

本文共 15952 字,大约阅读时间需要 53 分钟。

泛洪填充(Flood Fill Algorithm)

泛洪填充算法又称洪水填充算法是在很多图形绘制软件中常用的填充算法,最熟悉不过就是

windows paint的油漆桶功能。算法的原理很简单,就是从一个点开始附近像素点,填充成新

的颜色,直到封闭区域内的所有像素点都被填充新颜色为止。泛红填充实现最常见有四邻域

像素填充法,八邻域像素填充法,基于扫描线的像素填充方法。根据实现又可以分为递归与

非递归(基于栈)。

在介绍算法的三种实现方式之前,首先来看一下测试该算法的UI实现。基本思路是选择一

张要填充的图片,鼠标点击待填充的区域内部,算法会自动填充该区域,然后UI刷新。完

整的UI代码如下:

package com.gloomyfish.paint.fill;import java.awt.Color;import java.awt.Dimension;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.MediaTracker;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;import java.awt.image.BufferedImage;import java.io.File;import java.io.IOException;import javax.imageio.ImageIO;import javax.swing.JComponent;import javax.swing.JFileChooser;import javax.swing.JFrame;public class FloodFillUI extends JComponent implements MouseListener{        /**     *      */    private static final long serialVersionUID = 1L;    private BufferedImage rawImg;    private MediaTracker tracker;    private Dimension mySize;    FloodFillAlgorithm ffa;    public FloodFillUI(File f)    {        try {            rawImg = ImageIO.read(f);        } catch (IOException e1) {            e1.printStackTrace();        }                tracker = new MediaTracker(this);        tracker.addImage(rawImg, 1);                // blocked 10 seconds to load the image data        try {            if (!tracker.waitForID(1, 10000)) {                System.out.println("Load error.");                System.exit(1);            }// end if        } catch (InterruptedException e) {            e.printStackTrace();            System.exit(1);        }// end catch                mySize = new Dimension(300, 300);        this.addMouseListener(this);        ffa = new FloodFillAlgorithm(rawImg);        JFrame imageFrame = new JFrame("Flood File Algorithm Demo - Gloomyfish");        imageFrame.getContentPane().add(this);        imageFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        imageFrame.pack();        imageFrame.setVisible(true);    }    public void paint(Graphics g) {        Graphics2D g2 = (Graphics2D) g;        g2.drawImage(rawImg, 10, 10, rawImg.getWidth(), rawImg.getHeight(), null);    }    public Dimension getPreferredSize() {        return mySize;    }        public Dimension getMinimumSize() {        return mySize;    }        public Dimension getMaximumSize() {        return mySize;    }        public static void main(String[] args) {        JFileChooser chooser = new JFileChooser();        chooser.showOpenDialog(null);        File f = chooser.getSelectedFile();        new FloodFillUI(f);    }    @Override    public void mouseClicked(MouseEvent e) {        System.out.println("Mouse Clicked Event!!");        int x = (int)e.getPoint().getX();        int y = (int)e.getPoint().getY();        System.out.println("mouse location x = " + x); // column        System.out.println("mouse location y = " + y); // row        System.out.println();        long startTime = System.nanoTime();        // ffa.floodFill4(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y));        // ffa.floodFill8(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y));        // ffa.floodFillScanLine(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y)); // 13439051        ffa.floodFillScanLineWithStack(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y)); // - 16660142        long endTime = System.nanoTime() - startTime;        System.out.println("run time = " + endTime);        ffa.updateResult();        this.repaint();    }    @Override    public void mousePressed(MouseEvent e) {        // TODO Auto-generated method stub            }    @Override    public void mouseReleased(MouseEvent e) {        // TODO Auto-generated method stub            }    @Override    public void mouseEntered(MouseEvent e) {        // TODO Auto-generated method stub            }    @Override    public void mouseExited(MouseEvent e) {        // TODO Auto-generated method stub            }}

首先介绍四邻域的泛洪填充算法,寻找像素点p(x, y)的上下左右四个临近像素点,如果没有

被填充,则填充它们,并且继续寻找它们的四邻域像素,直到封闭区域完全被新颜色填充。

蓝色方格为四个邻域像素, p(x, y)为当前像素点。

 

基于递归实现代码很简单:

public void floodFill4(int x, int y, int newColor, int oldColor){    if(x >= 0 && x < width && y >= 0 && y < height             && getColor(x, y) == oldColor && getColor(x, y) != newColor)     {         setColor(x, y, newColor); //set color before starting recursion        floodFill4(x + 1, y,     newColor, oldColor);        floodFill4(x - 1, y,     newColor, oldColor);        floodFill4(x,     y + 1, newColor, oldColor);        floodFill4(x,     y - 1, newColor, oldColor);    }   }

八邻域的填充算法,则是在四邻域的基础上增加了左上,左下,右上,右下四个相邻像素。

并递归寻找它们的八邻域像素填充,直到区域完全被新颜色填充。

 

蓝色方格为四个邻域像素,黄色为左上,左下,右上,右下四个像素, p(x, y)为当前像素点。

基于递归实现的代码也很简单:

public void floodFill8(int x, int y, int newColor, int oldColor){    if(x >= 0 && x < width && y >= 0 && y < height &&             getColor(x, y) == oldColor && getColor(x, y) != newColor)     {         setColor(x, y, newColor); //set color before starting recursion        floodFill8(x + 1, y,     newColor, oldColor);        floodFill8(x - 1, y,     newColor, oldColor);        floodFill8(x,     y + 1, newColor, oldColor);        floodFill8(x,     y - 1, newColor, oldColor);        floodFill8(x + 1, y + 1, newColor, oldColor);        floodFill8(x - 1, y - 1, newColor, oldColor);        floodFill8(x - 1, y + 1, newColor, oldColor);        floodFill8(x + 1, y - 1, newColor, oldColor);    }   }

基于扫描线实现的泛洪填充算法的主要思想是根据当前输入的点p(x, y),沿y方向分别向上

与向下扫描填充,同时向左p(x-1, y)与向右p(x+1, y)递归寻找新的扫描线,直到递归结束。

代码如下:

public void floodFillScanLine(int x, int y, int newColor, int oldColor){    if(oldColor == newColor) return;    if(getColor(x, y) != oldColor) return;          int y1;        //draw current scanline from start position to the top    y1 = y;    while(y1 < height && getColor(x, y1) == oldColor)    {        setColor(x, y1, newColor);        y1++;    }            //draw current scanline from start position to the bottom    y1 = y - 1;    while(y1 >= 0 && getColor(x, y1) == oldColor)    {        setColor(x, y1, newColor);        y1--;    }        //test for new scanlines to the left    y1 = y;    while(y1 < height && getColor(x, y1) == newColor)    {        if(x > 0 && getColor(x - 1, y1) == oldColor)         {            floodFillScanLine(x - 1, y1, newColor, oldColor);        }         y1++;    }    y1 = y - 1;    while(y1 >= 0 && getColor(x, y1) == newColor)    {        if(x > 0 && getColor(x - 1, y1) == oldColor)         {            floodFillScanLine(x - 1, y1, newColor, oldColor);        }        y1--;    }         //test for new scanlines to the right     y1 = y;    while(y1 < height && getColor(x, y1) == newColor)    {        if(x < width - 1 && getColor(x + 1, y1) == oldColor)         {                       floodFillScanLine(x + 1, y1, newColor, oldColor);        }         y1++;    }    y1 = y - 1;    while(y1 >= 0 && getColor(x, y1) == newColor)    {        if(x < width - 1 && getColor(x + 1, y1) == oldColor)         {            floodFillScanLine(x + 1, y1, newColor, oldColor);        }        y1--;    }}

基于递归实现的泛洪填充算法有个致命的缺点,就是对于大的区域填充时可能导致栈溢出

错误,对最后一种基于扫描线的算法,实现了一种非递归的泛洪填充算法。

public void floodFillScanLineWithStack(int x, int y, int newColor, int oldColor){    if(oldColor == newColor) {        System.out.println("do nothing !!!, filled area!!");        return;    }    emptyStack();        int y1;     boolean spanLeft, spanRight;    push(x, y);        while(true)    {            x = popx();        if(x == -1) return;        y = popy();        y1 = y;        while(y1 >= 0 && getColor(x, y1) == oldColor) y1--; // go to line top/bottom        y1++; // start from line starting point pixel        spanLeft = spanRight = false;        while(y1 < height && getColor(x, y1) == oldColor)        {            setColor(x, y1, newColor);            if(!spanLeft && x > 0 && getColor(x - 1, y1) == oldColor)// just keep left line once in the stack            {                push(x - 1, y1);                spanLeft = true;            }            else if(spanLeft && x > 0 && getColor(x - 1, y1) != oldColor)            {                spanLeft = false;            }            if(!spanRight && x < width - 1 && getColor(x + 1, y1) == oldColor) // just keep right line once in the stack            {                push(x + 1, y1);                spanRight = true;            }            else if(spanRight && x < width - 1 && getColor(x + 1, y1) != oldColor)            {                spanRight = false;            }             y1++;        }    }    }

运行效果:

算法类源代码如下:

package com.gloomyfish.paint.fill;import java.awt.image.BufferedImage;import com.gloomyfish.filter.study.AbstractBufferedImageOp;public class FloodFillAlgorithm extends AbstractBufferedImageOp {    private BufferedImage inputImage;    private int[] inPixels;    private int width;    private int height;        //     stack data structure    private int maxStackSize = 500; // will be increased as needed    private int[] xstack = new int[maxStackSize];    private int[] ystack = new int[maxStackSize];    private int stackSize;    public FloodFillAlgorithm(BufferedImage rawImage) {        this.inputImage = rawImage;        width = rawImage.getWidth();        height = rawImage.getHeight();        inPixels = new int[width*height];        getRGB(rawImage, 0, 0, width, height, inPixels );    }    public BufferedImage getInputImage() {        return inputImage;    }    public void setInputImage(BufferedImage inputImage) {        this.inputImage = inputImage;    }        public int getColor(int x, int y)    {        int index = y * width + x;        return inPixels[index];    }        public void setColor(int x, int y, int newColor)    {        int index = y * width + x;        inPixels[index] = newColor;    }        public void updateResult()    {        setRGB( inputImage, 0, 0, width, height, inPixels );    }        /**     * it is very low calculation speed and cause the stack overflow issue when fill      * some big area and irregular shape. performance is very bad.     *      * @param x     * @param y     * @param newColor     * @param oldColor     */    public void floodFill4(int x, int y, int newColor, int oldColor)    {        if(x >= 0 && x < width && y >= 0 && y < height                 && getColor(x, y) == oldColor && getColor(x, y) != newColor)         {             setColor(x, y, newColor); //set color before starting recursion            floodFill4(x + 1, y,     newColor, oldColor);            floodFill4(x - 1, y,     newColor, oldColor);            floodFill4(x,     y + 1, newColor, oldColor);            floodFill4(x,     y - 1, newColor, oldColor);        }       }    /**     *      * @param x     * @param y     * @param newColor     * @param oldColor     */    public void floodFill8(int x, int y, int newColor, int oldColor)    {        if(x >= 0 && x < width && y >= 0 && y < height &&                 getColor(x, y) == oldColor && getColor(x, y) != newColor)         {             setColor(x, y, newColor); //set color before starting recursion            floodFill8(x + 1, y,     newColor, oldColor);            floodFill8(x - 1, y,     newColor, oldColor);            floodFill8(x,     y + 1, newColor, oldColor);            floodFill8(x,     y - 1, newColor, oldColor);            floodFill8(x + 1, y + 1, newColor, oldColor);            floodFill8(x - 1, y - 1, newColor, oldColor);            floodFill8(x - 1, y + 1, newColor, oldColor);            floodFill8(x + 1, y - 1, newColor, oldColor);        }       }        /**     *      * @param x     * @param y     * @param newColor     * @param oldColor     */    public void floodFillScanLine(int x, int y, int newColor, int oldColor)    {        if(oldColor == newColor) return;        if(getColor(x, y) != oldColor) return;                  int y1;                //draw current scanline from start position to the top        y1 = y;        while(y1 < height && getColor(x, y1) == oldColor)        {            setColor(x, y1, newColor);            y1++;        }                    //draw current scanline from start position to the bottom        y1 = y - 1;        while(y1 >= 0 && getColor(x, y1) == oldColor)        {            setColor(x, y1, newColor);            y1--;        }                //test for new scanlines to the left        y1 = y;        while(y1 < height && getColor(x, y1) == newColor)        {            if(x > 0 && getColor(x - 1, y1) == oldColor)             {                floodFillScanLine(x - 1, y1, newColor, oldColor);            }             y1++;        }        y1 = y - 1;        while(y1 >= 0 && getColor(x, y1) == newColor)        {            if(x > 0 && getColor(x - 1, y1) == oldColor)             {                floodFillScanLine(x - 1, y1, newColor, oldColor);            }            y1--;        }                 //test for new scanlines to the right         y1 = y;        while(y1 < height && getColor(x, y1) == newColor)        {            if(x < width - 1 && getColor(x + 1, y1) == oldColor)             {                           floodFillScanLine(x + 1, y1, newColor, oldColor);            }             y1++;        }        y1 = y - 1;        while(y1 >= 0 && getColor(x, y1) == newColor)        {            if(x < width - 1 && getColor(x + 1, y1) == oldColor)             {                floodFillScanLine(x + 1, y1, newColor, oldColor);            }            y1--;        }    }        public void floodFillScanLineWithStack(int x, int y, int newColor, int oldColor)    {        if(oldColor == newColor) {            System.out.println("do nothing !!!, filled area!!");            return;        }        emptyStack();                int y1;         boolean spanLeft, spanRight;        push(x, y);                while(true)        {                x = popx();            if(x == -1) return;            y = popy();            y1 = y;            while(y1 >= 0 && getColor(x, y1) == oldColor) y1--; // go to line top/bottom            y1++; // start from line starting point pixel            spanLeft = spanRight = false;            while(y1 < height && getColor(x, y1) == oldColor)            {                setColor(x, y1, newColor);                if(!spanLeft && x > 0 && getColor(x - 1, y1) == oldColor)// just keep left line once in the stack                {                    push(x - 1, y1);                    spanLeft = true;                }                else if(spanLeft && x > 0 && getColor(x - 1, y1) != oldColor)                {                    spanLeft = false;                }                if(!spanRight && x < width - 1 && getColor(x + 1, y1) == oldColor) // just keep right line once in the stack                {                    push(x + 1, y1);                    spanRight = true;                }                else if(spanRight && x < width - 1 && getColor(x + 1, y1) != oldColor)                {                    spanRight = false;                }                 y1++;            }        }            }        private void emptyStack() {        while(popx() != - 1) {            popy();        }        stackSize = 0;    }    final void push(int x, int y) {        stackSize++;        if (stackSize==maxStackSize) {            int[] newXStack = new int[maxStackSize*2];            int[] newYStack = new int[maxStackSize*2];            System.arraycopy(xstack, 0, newXStack, 0, maxStackSize);            System.arraycopy(ystack, 0, newYStack, 0, maxStackSize);            xstack = newXStack;            ystack = newYStack;            maxStackSize *= 2;        }        xstack[stackSize-1] = x;        ystack[stackSize-1] = y;    }        final int popx() {        if (stackSize==0)            return -1;        else            return xstack[stackSize-1];    }    final int popy() {        int value = ystack[stackSize-1];        stackSize--;        return value;    }    @Override    public BufferedImage filter(BufferedImage src, BufferedImage dest) {        // TODO Auto-generated method stub        return null;    }}

 

转载于:https://www.cnblogs.com/jiangxiaobo/p/6050741.html

你可能感兴趣的文章
80X86寄存器详解<转载>
查看>>
c# aop讲解
查看>>
iterable与iterator
查看>>
返回顶部(动画)
查看>>
webpack+react+antd 单页面应用实例
查看>>
Confluence 6 SQL Server 数据库驱动修改
查看>>
Confluence 6 通过 SSL 或 HTTPS 运行 - 备注和问题解决
查看>>
【47.76%】【Round #380B】Spotlights
查看>>
Git(使用码云)
查看>>
分享Java web 开发必游之路
查看>>
IIS初始化(预加载),解决第一次访问慢,程序池被回收问题(转载)
查看>>
Bean的Scope
查看>>
【BZOJ】3142: [Hnoi2013]数列
查看>>
http初探
查看>>
elasticsearch的安装
查看>>
__next__()
查看>>
爬取:中国大学排名
查看>>
聊天室(C++客户端+Pyhton服务器)_1.框架搭设
查看>>
UpdatePanel 内控件 更新“外的”控件【转】
查看>>
mybatis中&gt;=和&lt;=的实现方式
查看>>