• 图像处理------泛洪填充算法(Flood Fill Algorithm) 油漆桶功能 分类: 视频图像处理 2015-07-24 15:03 68人阅读 评论(0) 收藏


    泛洪填充算法(Flood Fill Algorithm)

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

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

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

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

    非递归(基于栈)。

     

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

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

    整的UI代码如下:

    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)的上下左右四个临近像素点,如果没有

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

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

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

    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. }  

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

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


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

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

    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)递归寻找新的扫描线,直到递归结束。

    代码如下:

    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栈溢出

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

    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. }  
    运行效果:


    算法类源代码如下:

    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. }  

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    PO BO VO DTO POJO DAO DO这些Java中的概念分别指一些什么?
    前端面试题汇总(待续)
    vue lottie vue-lottie : 使用教程
    webstorm 换行时 代码不对齐
    webstorm 导出编辑器配置.editorconfig
    vue 查看dist文件里的结构
    vue-cli 生产打包
    element form 校验数组每一项
    typescript无法识别vue中的$refs
    mac 10.14.5 [vue create的时候 mkdir没有权限]
  • 原文地址:https://www.cnblogs.com/mao0504/p/4705503.html
Copyright © 2020-2023  润新知