图像处理------泛洪填充算法(Flood Fill Algorithm) 油漆桶功能

泛洪填充算法(Flood Fill Algorithm)

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

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

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

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

非递归(基于栈)。

 

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

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

整的UI代码如下:

[java]  view plain copy
  1. package com.gloomyfish.paint.fill;  
  2.   
  3. import java.awt.Color;  
  4. import java.awt.Dimension;  
  5. import java.awt.Graphics;  
  6. import java.awt.Graphics2D;  
  7. import java.awt.MediaTracker;  
  8. import java.awt.event.MouseEvent;  
  9. import java.awt.event.MouseListener;  
  10. import java.awt.image.BufferedImage;  
  11. import java.io.File;  
  12. import java.io.IOException;  
  13.   
  14. import javax.imageio.ImageIO;  
  15. import javax.swing.JComponent;  
  16. import javax.swing.JFileChooser;  
  17. import javax.swing.JFrame;  
  18.   
  19. public class FloodFillUI extends JComponent implements MouseListener{  
  20.       
  21.     /** 
  22.      *  
  23.      */  
  24.     private static final long serialVersionUID = 1L;  
  25.     private BufferedImage rawImg;  
  26.     private MediaTracker tracker;  
  27.     private Dimension mySize;  
  28.     FloodFillAlgorithm ffa;  
  29.     public FloodFillUI(File f)  
  30.     {  
  31.         try {  
  32.             rawImg = ImageIO.read(f);  
  33.         } catch (IOException e1) {  
  34.             e1.printStackTrace();  
  35.         }  
  36.           
  37.         tracker = new MediaTracker(this);  
  38.         tracker.addImage(rawImg, 1);  
  39.           
  40.         // blocked 10 seconds to load the image data  
  41.         try {  
  42.             if (!tracker.waitForID(110000)) {  
  43.                 System.out.println("Load error.");  
  44.                 System.exit(1);  
  45.             }// end if  
  46.         } catch (InterruptedException e) {  
  47.             e.printStackTrace();  
  48.             System.exit(1);  
  49.         }// end catch  
  50.           
  51.         mySize = new Dimension(300300);  
  52.         this.addMouseListener(this);  
  53.         ffa = new FloodFillAlgorithm(rawImg);  
  54.         JFrame imageFrame = new JFrame("Flood File Algorithm Demo - Gloomyfish");  
  55.         imageFrame.getContentPane().add(this);  
  56.         imageFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  57.         imageFrame.pack();  
  58.         imageFrame.setVisible(true);  
  59.     }  
  60.   
  61.     public void paint(Graphics g) {  
  62.         Graphics2D g2 = (Graphics2D) g;  
  63.         g2.drawImage(rawImg, 1010, rawImg.getWidth(), rawImg.getHeight(), null);  
  64.     }  
  65.     public Dimension getPreferredSize() {  
  66.         return mySize;  
  67.     }  
  68.       
  69.     public Dimension getMinimumSize() {  
  70.         return mySize;  
  71.     }  
  72.       
  73.     public Dimension getMaximumSize() {  
  74.         return mySize;  
  75.     }  
  76.       
  77.     public static void main(String[] args) {  
  78.         JFileChooser chooser = new JFileChooser();  
  79.         chooser.showOpenDialog(null);  
  80.         File f = chooser.getSelectedFile();  
  81.         new FloodFillUI(f);  
  82.     }  
  83.   
  84.     @Override  
  85.     public void mouseClicked(MouseEvent e) {  
  86.         System.out.println("Mouse Clicked Event!!");  
  87.         int x = (int)e.getPoint().getX();  
  88.         int y = (int)e.getPoint().getY();  
  89.         System.out.println("mouse location x = " + x); // column  
  90.         System.out.println("mouse location y = " + y); // row  
  91.         System.out.println();  
  92.         long startTime = System.nanoTime();  
  93.         // ffa.floodFill4(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y));  
  94.         // ffa.floodFill8(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y));  
  95.         // ffa.floodFillScanLine(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y)); // 13439051  
  96.         ffa.floodFillScanLineWithStack(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y)); // - 16660142  
  97.         long endTime = System.nanoTime() - startTime;  
  98.         System.out.println("run time = " + endTime);  
  99.         ffa.updateResult();  
  100.         this.repaint();  
  101.     }  
  102.   
  103.     @Override  
  104.     public void mousePressed(MouseEvent e) {  
  105.         // TODO Auto-generated method stub  
  106.           
  107.     }  
  108.   
  109.     @Override  
  110.     public void mouseReleased(MouseEvent e) {  
  111.         // TODO Auto-generated method stub  
  112.           
  113.     }  
  114.   
  115.     @Override  
  116.     public void mouseEntered(MouseEvent e) {  
  117.         // TODO Auto-generated method stub  
  118.           
  119.     }  
  120.   
  121.     @Override  
  122.     public void mouseExited(MouseEvent e) {  
  123.         // TODO Auto-generated method stub  
  124.           
  125.     }  
  126.   
  127. }  

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

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

图像处理------泛洪填充算法(Flood Fill Algorithm) 油漆桶功能_第1张图片蓝色方格为四个邻域像素, p(x, y)为当前像素点。

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

[java]  view plain copy
  1. public void floodFill4(int x, int y, int newColor, int oldColor)  
  2. {  
  3.     if(x >= 0 && x < width && y >= 0 && y < height   
  4.             && getColor(x, y) == oldColor && getColor(x, y) != newColor)   
  5.     {   
  6.         setColor(x, y, newColor); //set color before starting recursion  
  7.         floodFill4(x + 1, y,     newColor, oldColor);  
  8.         floodFill4(x - 1, y,     newColor, oldColor);  
  9.         floodFill4(x,     y + 1, newColor, oldColor);  
  10.         floodFill4(x,     y - 1, newColor, oldColor);  
  11.     }     
  12. }  

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

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

图像处理------泛洪填充算法(Flood Fill Algorithm) 油漆桶功能_第2张图片

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

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

[java]  view plain copy
  1. public void floodFill8(int x, int y, int newColor, int oldColor)  
  2. {  
  3.     if(x >= 0 && x < width && y >= 0 && y < height &&   
  4.             getColor(x, y) == oldColor && getColor(x, y) != newColor)   
  5.     {   
  6.         setColor(x, y, newColor); //set color before starting recursion  
  7.         floodFill8(x + 1, y,     newColor, oldColor);  
  8.         floodFill8(x - 1, y,     newColor, oldColor);  
  9.         floodFill8(x,     y + 1, newColor, oldColor);  
  10.         floodFill8(x,     y - 1, newColor, oldColor);  
  11.         floodFill8(x + 1, y + 1, newColor, oldColor);  
  12.         floodFill8(x - 1, y - 1, newColor, oldColor);  
  13.         floodFill8(x - 1, y + 1, newColor, oldColor);  
  14.         floodFill8(x + 1, y - 1, newColor, oldColor);  
  15.     }     
  16. }  

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

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

代码如下:

[java]  view plain copy
  1. public void floodFillScanLine(int x, int y, int newColor, int oldColor)  
  2. {  
  3.     if(oldColor == newColor) return;  
  4.     if(getColor(x, y) != oldColor) return;  
  5.         
  6.     int y1;  
  7.       
  8.     //draw current scanline from start position to the top  
  9.     y1 = y;  
  10.     while(y1 < height && getColor(x, y1) == oldColor)  
  11.     {  
  12.         setColor(x, y1, newColor);  
  13.         y1++;  
  14.     }      
  15.       
  16.     //draw current scanline from start position to the bottom  
  17.     y1 = y - 1;  
  18.     while(y1 >= 0 && getColor(x, y1) == oldColor)  
  19.     {  
  20.         setColor(x, y1, newColor);  
  21.         y1--;  
  22.     }  
  23.       
  24.     //test for new scanlines to the left  
  25.     y1 = y;  
  26.     while(y1 < height && getColor(x, y1) == newColor)  
  27.     {  
  28.         if(x > 0 && getColor(x - 1, y1) == oldColor)   
  29.         {  
  30.             floodFillScanLine(x - 1, y1, newColor, oldColor);  
  31.         }   
  32.         y1++;  
  33.     }  
  34.     y1 = y - 1;  
  35.     while(y1 >= 0 && getColor(x, y1) == newColor)  
  36.     {  
  37.         if(x > 0 && getColor(x - 1, y1) == oldColor)   
  38.         {  
  39.             floodFillScanLine(x - 1, y1, newColor, oldColor);  
  40.         }  
  41.         y1--;  
  42.     }   
  43.       
  44.     //test for new scanlines to the right   
  45.     y1 = y;  
  46.     while(y1 < height && getColor(x, y1) == newColor)  
  47.     {  
  48.         if(x < width - 1 && getColor(x + 1, y1) == oldColor)   
  49.         {             
  50.             floodFillScanLine(x + 1, y1, newColor, oldColor);  
  51.         }   
  52.         y1++;  
  53.     }  
  54.     y1 = y - 1;  
  55.     while(y1 >= 0 && getColor(x, y1) == newColor)  
  56.     {  
  57.         if(x < width - 1 && getColor(x + 1, y1) == oldColor)   
  58.         {  
  59.             floodFillScanLine(x + 1, y1, newColor, oldColor);  
  60.         }  
  61.         y1--;  
  62.     }  
  63. }  

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

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

[java]  view plain copy
  1. public void floodFillScanLineWithStack(int x, int y, int newColor, int oldColor)  
  2. {  
  3.     if(oldColor == newColor) {  
  4.         System.out.println("do nothing !!!, filled area!!");  
  5.         return;  
  6.     }  
  7.     emptyStack();  
  8.       
  9.     int y1;   
  10.     boolean spanLeft, spanRight;  
  11.     push(x, y);  
  12.       
  13.     while(true)  
  14.     {      
  15.         x = popx();  
  16.         if(x == -1return;  
  17.         y = popy();  
  18.         y1 = y;  
  19.         while(y1 >= 0 && getColor(x, y1) == oldColor) y1--; // go to line top/bottom  
  20.         y1++; // start from line starting point pixel  
  21.         spanLeft = spanRight = false;  
  22.         while(y1 < height && getColor(x, y1) == oldColor)  
  23.         {  
  24.             setColor(x, y1, newColor);  
  25.             if(!spanLeft && x > 0 && getColor(x - 1, y1) == oldColor)// just keep left line once in the stack  
  26.             {  
  27.                 push(x - 1, y1);  
  28.                 spanLeft = true;  
  29.             }  
  30.             else if(spanLeft && x > 0 && getColor(x - 1, y1) != oldColor)  
  31.             {  
  32.                 spanLeft = false;  
  33.             }  
  34.             if(!spanRight && x < width - 1 && getColor(x + 1, y1) == oldColor) // just keep right line once in the stack  
  35.             {  
  36.                 push(x + 1, y1);  
  37.                 spanRight = true;  
  38.             }  
  39.             else if(spanRight && x < width - 1 && getColor(x + 1, y1) != oldColor)  
  40.             {  
  41.                 spanRight = false;  
  42.             }   
  43.             y1++;  
  44.         }  
  45.     }  
  46.       
  47. }  
运行效果:

图像处理------泛洪填充算法(Flood Fill Algorithm) 油漆桶功能_第3张图片
算法类源代码如下:

[java]  view plain copy
  1. package com.gloomyfish.paint.fill;  
  2.   
  3. import java.awt.image.BufferedImage;  
  4.   
  5. import com.gloomyfish.filter.study.AbstractBufferedImageOp;  
  6.   
  7. public class FloodFillAlgorithm extends AbstractBufferedImageOp {  
  8.   
  9.     private BufferedImage inputImage;  
  10.     private int[] inPixels;  
  11.     private int width;  
  12.     private int height;  
  13.       
  14.     //  stack data structure  
  15.     private int maxStackSize = 500// will be increased as needed  
  16.     private int[] xstack = new int[maxStackSize];  
  17.     private int[] ystack = new int[maxStackSize];  
  18.     private int stackSize;  
  19.   
  20.     public FloodFillAlgorithm(BufferedImage rawImage) {  
  21.         this.inputImage = rawImage;  
  22.         width = rawImage.getWidth();  
  23.         height = rawImage.getHeight();  
  24.         inPixels = new int[width*height];  
  25.         getRGB(rawImage, 00, width, height, inPixels );  
  26.     }  
  27.   
  28.     public BufferedImage getInputImage() {  
  29.         return inputImage;  
  30.     }  
  31.   
  32.     public void setInputImage(BufferedImage inputImage) {  
  33.         this.inputImage = inputImage;  
  34.     }  
  35.       
  36.     public int getColor(int x, int y)  
  37.     {  
  38.         int index = y * width + x;  
  39.         return inPixels[index];  
  40.     }  
  41.       
  42.     public void setColor(int x, int y, int newColor)  
  43.     {  
  44.         int index = y * width + x;  
  45.         inPixels[index] = newColor;  
  46.     }  
  47.       
  48.     public void updateResult()  
  49.     {  
  50.         setRGB( inputImage, 00, width, height, inPixels );  
  51.     }  
  52.       
  53.     /** 
  54.      * it is very low calculation speed and cause the stack overflow issue when fill  
  55.      * some big area and irregular shape. performance is very bad. 
  56.      *  
  57.      * @param x 
  58.      * @param y 
  59.      * @param newColor 
  60.      * @param oldColor 
  61.      */  
  62.     public void floodFill4(int x, int y, int newColor, int oldColor)  
  63.     {  
  64.         if(x >= 0 && x < width && y >= 0 && y < height   
  65.                 && getColor(x, y) == oldColor && getColor(x, y) != newColor)   
  66.         {   
  67.             setColor(x, y, newColor); //set color before starting recursion  
  68.             floodFill4(x + 1, y,     newColor, oldColor);  
  69.             floodFill4(x - 1, y,     newColor, oldColor);  
  70.             floodFill4(x,     y + 1, newColor, oldColor);  
  71.             floodFill4(x,     y - 1, newColor, oldColor);  
  72.         }     
  73.     }  
  74.     /** 
  75.      *  
  76.      * @param x 
  77.      * @param y 
  78.      * @param newColor 
  79.      * @param oldColor 
  80.      */  
  81.     public void floodFill8(int x, int y, int newColor, int oldColor)  
  82.     {  
  83.         if(x >= 0 && x < width && y >= 0 && y < height &&   
  84.                 getColor(x, y) == oldColor && getColor(x, y) != newColor)   
  85.         {   
  86.             setColor(x, y, newColor); //set color before starting recursion  
  87.             floodFill8(x + 1, y,     newColor, oldColor);  
  88.             floodFill8(x - 1, y,     newColor, oldColor);  
  89.             floodFill8(x,     y + 1, newColor, oldColor);  
  90.             floodFill8(x,     y - 1, newColor, oldColor);  
  91.             floodFill8(x + 1, y + 1, newColor, oldColor);  
  92.             floodFill8(x - 1, y - 1, newColor, oldColor);  
  93.             floodFill8(x - 1, y + 1, newColor, oldColor);  
  94.             floodFill8(x + 1, y - 1, newColor, oldColor);  
  95.         }     
  96.     }  
  97.       
  98.     /** 
  99.      *  
  100.      * @param x 
  101.      * @param y 
  102.      * @param newColor 
  103.      * @param oldColor 
  104.      */  
  105.     public void floodFillScanLine(int x, int y, int newColor, int oldColor)  
  106.     {  
  107.         if(oldColor == newColor) return;  
  108.         if(getColor(x, y) != oldColor) return;  
  109.             
  110.         int y1;  
  111.           
  112.         //draw current scanline from start position to the top  
  113.         y1 = y;  
  114.         while(y1 < height && getColor(x, y1) == oldColor)  
  115.         {  
  116.             setColor(x, y1, newColor);  
  117.             y1++;  
  118.         }      
  119.           
  120.         //draw current scanline from start position to the bottom  
  121.         y1 = y - 1;  
  122.         while(y1 >= 0 && getColor(x, y1) == oldColor)  
  123.         {  
  124.             setColor(x, y1, newColor);  
  125.             y1--;  
  126.         }  
  127.           
  128.         //test for new scanlines to the left  
  129.         y1 = y;  
  130.         while(y1 < height && getColor(x, y1) == newColor)  
  131.         {  
  132.             if(x > 0 && getColor(x - 1, y1) == oldColor)   
  133.             {  
  134.                 floodFillScanLine(x - 1, y1, newColor, oldColor);  
  135.             }   
  136.             y1++;  
  137.         }  
  138.         y1 = y - 1;  
  139.         while(y1 >= 0 && getColor(x, y1) == newColor)  
  140.         {  
  141.             if(x > 0 && getColor(x - 1, y1) == oldColor)   
  142.             {  
  143.                 floodFillScanLine(x - 1, y1, newColor, oldColor);  
  144.             }  
  145.             y1--;  
  146.         }   
  147.           
  148.         //test for new scanlines to the right   
  149.         y1 = y;  
  150.         while(y1 < height && getColor(x, y1) == newColor)  
  151.         {  
  152.             if(x < width - 1 && getColor(x + 1, y1) == oldColor)   
  153.             {             
  154.                 floodFillScanLine(x + 1, y1, newColor, oldColor);  
  155.             }   
  156.             y1++;  
  157.         }  
  158.         y1 = y - 1;  
  159.         while(y1 >= 0 && getColor(x, y1) == newColor)  
  160.         {  
  161.             if(x < width - 1 && getColor(x + 1, y1) == oldColor)   
  162.             {  
  163.                 floodFillScanLine(x + 1, y1, newColor, oldColor);  
  164.             }  
  165.             y1--;  
  166.         }  
  167.     }  
  168.       
  169.     public void floodFillScanLineWithStack(int x, int y, int newColor, int oldColor)  
  170.     {  
  171.         if(oldColor == newColor) {  
  172.             System.out.println("do nothing !!!, filled area!!");  
  173.             return;  
  174.         }  
  175.         emptyStack();  
  176.           
  177.         int y1;   
  178.         boolean spanLeft, spanRight;  
  179.         push(x, y);  
  180.           
  181.         while(true)  
  182.         {      
  183.             x = popx();  
  184.             if(x == -1return;  
  185.             y = popy();  
  186.             y1 = y;  
  187.             while(y1 >= 0 && getColor(x, y1) == oldColor) y1--; // go to line top/bottom  
  188.             y1++; // start from line starting point pixel  
  189.             spanLeft = spanRight = false;  
  190.             while(y1 < height && getColor(x, y1) == oldColor)  
  191.             {  
  192.                 setColor(x, y1, newColor);  
  193.                 if(!spanLeft && x > 0 && getColor(x - 1, y1) == oldColor)// just keep left line once in the stack  
  194.                 {  
  195.                     push(x - 1, y1);  
  196.                     spanLeft = true;  
  197.                 }  
  198.                 else if(spanLeft && x > 0 && getColor(x - 1, y1) != oldColor)  
  199.                 {  
  200.                     spanLeft = false;  
  201.                 }  
  202.                 if(!spanRight && x < width - 1 && getColor(x + 1, y1) == oldColor) // just keep right line once in the stack  
  203.                 {  
  204.                     push(x + 1, y1);  
  205.                     spanRight = true;  
  206.                 }  
  207.                 else if(spanRight && x < width - 1 && getColor(x + 1, y1) != oldColor)  
  208.                 {  
  209.                     spanRight = false;  
  210.                 }   
  211.                 y1++;  
  212.             }  
  213.         }  
  214.           
  215.     }  
  216.       
  217.     private void emptyStack() {  
  218.         while(popx() != - 1) {  
  219.             popy();  
  220.         }  
  221.         stackSize = 0;  
  222.     }  
  223.   
  224.     final void push(int x, int y) {  
  225.         stackSize++;  
  226.         if (stackSize==maxStackSize) {  
  227.             int[] newXStack = new int[maxStackSize*2];  
  228.             int[] newYStack = new int[maxStackSize*2];  
  229.             System.arraycopy(xstack, 0, newXStack, 0, maxStackSize);  
  230.             System.arraycopy(ystack, 0, newYStack, 0, maxStackSize);  
  231.             xstack = newXStack;  
  232.             ystack = newYStack;  
  233.             maxStackSize *= 2;  
  234.         }  
  235.         xstack[stackSize-1] = x;  
  236.         ystack[stackSize-1] = y;  
  237.     }  
  238.       
  239.     final int popx() {  
  240.         if (stackSize==0)  
  241.             return -1;  
  242.         else  
  243.             return xstack[stackSize-1];  
  244.     }  
  245.   
  246.     final int popy() {  
  247.         int value = ystack[stackSize-1];  
  248.         stackSize--;  
  249.         return value;  
  250.     }  
  251.   
  252.     @Override  
  253.     public BufferedImage filter(BufferedImage src, BufferedImage dest) {  
  254.         // TODO Auto-generated method stub  
  255.         return null;  
  256.     }  
  257.   
  258. }  

你可能感兴趣的:(图像处理------泛洪填充算法(Flood Fill Algorithm) 油漆桶功能)