泛洪填充算法(Flood Fill Algorithm)java
泛洪填充算法又稱洪水填充算法是在不少圖形繪製軟件中經常使用的填充算法,最熟悉不過就是算法
windows paint的油漆桶功能。算法的原理很簡單,就是從一個點開始附近像素點,填充成新windows
的顏色,直到封閉區域內的全部像素點都被填充新顏色爲止。泛紅填充實現最多見有四鄰域ide
像素填充法,八鄰域像素填充法,基於掃描線的像素填充方法。根據實現又能夠分爲遞歸與測試
非遞歸(基於棧)。this
在介紹算法的三種實現方式以前,首先來看一下測試該算法的UI實現。基本思路是選擇一url
張要填充的圖片,鼠標點擊待填充的區域內部,算法會自動填充該區域,而後UI刷新。完spa
整的UI代碼以下:.net
- 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);
-
-
- try {
- if (!tracker.waitForID(1, 10000)) {
- System.out.println("Load error.");
- System.exit(1);
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- System.exit(1);
- }
-
- 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);
- System.out.println("mouse location y = " + y);
- System.out.println();
- long startTime = System.nanoTime();
-
-
-
- ffa.floodFillScanLineWithStack(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y));
- long endTime = System.nanoTime() - startTime;
- System.out.println("run time = " + endTime);
- ffa.updateResult();
- this.repaint();
- }
-
- @Override
- public void mousePressed(MouseEvent e) {
-
-
- }
-
- @Override
- public void mouseReleased(MouseEvent e) {
-
-
- }
-
- @Override
- public void mouseEntered(MouseEvent e) {
-
-
- }
-
- @Override
- public void mouseExited(MouseEvent e) {
-
-
- }
-
- }
首先介紹四鄰域的泛洪填充算法,尋找像素點p(x, y)的上下左右四個臨近像素點,若是沒有orm
被填充,則填充它們,而且繼續尋找它們的四鄰域像素,直到封閉區域徹底被新顏色填充。
藍色方格爲四個鄰域像素, 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);
- floodFill4(x + 1, y, newColor, oldColor);
- floodFill4(x - 1, y, newColor, oldColor);
- floodFill4(x, y + 1, newColor, oldColor);
- floodFill4(x, y - 1, newColor, oldColor);
- }
- }
八鄰域的填充算法,則是在四鄰域的基礎上增長了左上,左下,右上,右下四個相鄰像素。
並遞歸尋找它們的八鄰域像素填充,直到區域徹底被新顏色填充。
![](http://static.javashuo.com/static/loading.gif)
藍色方格爲四個鄰域像素,黃色爲左上,左下,右上,右下四個像素, 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);
- 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;
-
-
- y1 = y;
- while(y1 < height && getColor(x, y1) == oldColor)
- {
- setColor(x, y1, newColor);
- y1++;
- }
-
-
- y1 = y - 1;
- while(y1 >= 0 && getColor(x, y1) == oldColor)
- {
- setColor(x, y1, newColor);
- y1--;
- }
-
-
- 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--;
- }
-
-
- 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--;
- }
- }
基於遞歸實現的泛洪填充算法有個致命的缺點,就是對於大的區域填充時可能致使JAVA棧溢出
錯誤,對最後一種基於掃描線的算法,實現了一種非遞歸的泛洪填充算法。
- 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--;
- y1++;
- spanLeft = spanRight = false;
- while(y1 < height && getColor(x, y1) == oldColor)
- {
- setColor(x, y1, newColor);
- if(!spanLeft && x > 0 && getColor(x - 1, y1) == oldColor)
- {
- 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)
- {
- push(x + 1, y1);
- spanRight = true;
- }
- else if(spanRight && x < width - 1 && getColor(x + 1, y1) != oldColor)
- {
- spanRight = false;
- }
- y1++;
- }
- }
-
- }
運行效果:
![](http://static.javashuo.com/static/loading.gif)
算法類源代碼以下:
- 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;
-
-
- private int maxStackSize = 500;
- 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 );
- }
-
-
-
-
-
-
-
-
-
-
- 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);
- floodFill4(x + 1, y, newColor, oldColor);
- floodFill4(x - 1, y, newColor, oldColor);
- floodFill4(x, y + 1, newColor, oldColor);
- floodFill4(x, y - 1, newColor, 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);
- 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);
- }
- }
-
-
-
-
-
-
-
-
- public void floodFillScanLine(int x, int y, int newColor, int oldColor)
- {
- if(oldColor == newColor) return;
- if(getColor(x, y) != oldColor) return;
-
- int y1;
-
-
- y1 = y;
- while(y1 < height && getColor(x, y1) == oldColor)
- {
- setColor(x, y1, newColor);
- y1++;
- }
-
-
- y1 = y - 1;
- while(y1 >= 0 && getColor(x, y1) == oldColor)
- {
- setColor(x, y1, newColor);
- y1--;
- }
-
-
- 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--;
- }
-
-
- 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--;
- y1++;
- spanLeft = spanRight = false;
- while(y1 < height && getColor(x, y1) == oldColor)
- {
- setColor(x, y1, newColor);
- if(!spanLeft && x > 0 && getColor(x - 1, y1) == oldColor)
- {
- 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)
- {
- 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) {
-
- return null;
- }
-
- }