traversing all pixels in an area

lol, i kinda went and made changes where i thought it would be appropriate to implement the check… I should have waited abit too see if you responded before it did it :slight_smile:

Well, here are my changes, however it seems that there is no measureable difference… i may have done something wrong…



      class LineSegStack { 
            private static final int elementSize = 7; 
            private static final int xOff      = 0-elementSize; 
            private static final int yOff      = 1-elementSize; 
            private static final int leftOff  = 2-elementSize; 
            private static final int rightOff   = 3-elementSize;
            private static final int prevLeftOff   = 4-elementSize;
            private static final int prevRightOff   = 5-elementSize;
            private static final int fromTopOff   = 6-elementSize;  
       int[] data;  
       int size = 0; 
       int offset;  
   
       public LineSegStack() { 
        data= new int[1024*elementSize];  
        offset=0;  
       }  
       
 
       public void push(int left, int right, int y,int fromTop) { 
        if (offset == data.length) { 
         int[] temp = new int[2*data.length];  
         System.arraycopy(data, 0, temp, 0, data.length);  
         data = temp;  
        } 
        
        if (offset>0)
        {
        
              offset+=elementSize;  
              data[offset+leftOff] = left; 
              data[offset+prevLeftOff] = data[offset+prevLeftOff-elementSize];
              data[offset+rightOff] = right; 
              data[offset+prevRightOff] =data[offset+prevRightOff-elementSize];
              data[offset+xOff] = left; 
              data[offset+yOff] = y; 
              data[offset+fromTopOff]=fromTop;

        }
        else
        {
            offset+=elementSize;  
            data[offset+leftOff] = left; 
            data[offset+prevLeftOff] = 0;
            data[offset+rightOff] = right; 
            data[offset+prevRightOff] =0;
            data[offset+xOff] = left; 
            data[offset+yOff] = y; 
            data[offset+fromTopOff]=fromTop;
        }
        size++;
         
       }  
 
       public void pop(LineSeg s) { 
        s.x = data[offset+xOff]; 
        s.y = data[offset+yOff]; 
        s.left = data[offset+leftOff]; 
        s.right = data[offset+rightOff]; 
        s.prevLeft= data[offset+prevLeftOff];
        s.prevRight= data[offset+prevRightOff];
        s.fromTop= data[offset+fromTopOff];
        offset -= elementSize;  
        size--; 
       } 
      } 
      
      
      class LineSeg { 
       int left, right, x, y, doneTop,prevLeft,prevRight,fromTop;
       
       LineSeg(int left, int right, int y) { 
        this.left = left; 
        this.right = right; 
        this.x = left; 
        this.y = y; 
       } 
      }  

  
 
   public void linearFloodFill1(int startx,int starty,int width, int height,boolean[][] pixelsChecked,byte[][] frame,LineSegStack stack ) {
      //LineSegStack stack = new LineSegStack(); 
      LineSeg nextLine = new LineSeg(0, 0, 0); 
      int fcount=0;
      java.util.Random r=new java.util.Random((startx+starty)*(starty-startx));
      int RColour = r.nextInt(240);
      int GColour = r.nextInt(240);
      int BColour = r.nextInt(240);
      int fromTop=0; 
      int y,right;
      boolean topChecked[];
      boolean botChecked[];
      addLineSegToStack(startx, starty,width,height,frame,pixelsChecked,fcount,RColour,GColour,BColour,stack,fromTop); 
      while (stack.size > 0) { 
       stack.pop(nextLine); 
       y = nextLine.y; 
       right = nextLine.right; 
       fromTop=0;
       
       if (y>0) { 
            fromTop=1;
        // fill the line above this line 
        //int topLine[] = image[y-1]; 
        topChecked = pixelsChecked[y-1]; 
         // did we come from there? 
        if (nextLine.fromTop==1) { 
            // check on the left side of where we come from 
         int min = Math.min(nextLine.prevLeft, right); 
         for (int x=nextLine.left; x<=min; x++) { 
            if (!topChecked[x]) { 
             x = addLineSegToStack(x, y-1,width,height,frame,pixelsChecked,fcount,RColour,GColour,BColour,stack,fromTop); 
            } 
         } 
            // check on the right side of where we come from 
         for (int x=Math.max(nextLine.prevRight, nextLine.left); x<=right; x++) { 
            if (!topChecked[x]) { 
             x = addLineSegToStack(x, y-1,width,height,frame,pixelsChecked,fcount,RColour,GColour,BColour,stack,fromTop);
            } 
         } 
        } else { 
         // did not come from this side: check the whole line 
         for (int x=nextLine.left; x<=right; x++) { 
            if (!topChecked[x]) { 
             x = addLineSegToStack(x, y-1,width,height,frame,pixelsChecked,fcount,RColour,GColour,BColour,stack,fromTop);
            } 
         } 
        } 
       } 
       
/    
       if (y<height-1) {
            fromTop=2; 
        //int botLine[] = image[y+1]; 
        botChecked = pixelsChecked[y+1]; 
        for (int x=nextLine.left; x<=right; x++) { 
         if ( !botChecked[x]) { 
            x = addLineSegToStack(x, y+1,width,height,frame,pixelsChecked,fcount,RColour,GColour,BColour,stack,fromTop);  
         } 
        } 
       }
       

       
        
      } 
   } 
  
   private int addLineSegToStack(int x, int y,int width, int height, byte[][] frame, boolean pixelsChecked[][],int fcount,int RColour, int GColour,int BColour,LineSegStack stack,int fromTop)
    { 
      //int line[] = image[y]; 
      boolean checked[] = pixelsChecked[y]; 
      int left=x; 
      do { 
            fcount++;
            frame[y*width+left][0]=(byte) (RColour+(fcount%15));
            frame[y*width+left][1]=(byte) (GColour+(fcount%15));
            frame[y*width+left][2]=(byte) (BColour+(fcount%15));
       checked[left] = true; 
       left--; 
      } while (left>=0 &&  !checked[left]); 
      left++; 
      int right=x;  
      do { 
            fcount++;
            frame[y*width+right][0]=(byte) (RColour+(fcount%15));
            frame[y*width+right][1]=(byte) (GColour+(fcount%15));
            frame[y*width+right][2]=(byte) (BColour+(fcount%15));    
       checked[right] = true; 
       right++; 
      } while (right<width && !checked[right]); 
      right--; 
      stack.push(left, right, y,fromTop); 
      return right; 
   }

by comparing our two codes, it looks like we are effectivly doing the same thing.

thinking that we would do the same if coming from a bottom line, i implemented the same checking, however it made the algorithm slightly slower so i removed it.

The differance is that I also push prevleft and prevright on the stack. I use dummy values in the first push:


addLineSegToStack(startx, starty, -1, -1, 0)
while (stack.size > 0) {
 ...

which means that the previus left and right of the first call is outside the image. That way it will not effect the algorithm.

hmm, i thought that is what i was doing…

The previous left and right values are the values of left and right of the last stack push operation, So i set the current previous left and previous right to the left and right of the last push operation.

If the current push operation is the beginning of the stack then i just add dummy prev left and prev right, just like you do.

Well the variable names is a bit misleading I’m afraid :-[

With “previous” I mean the LineSeg that was prosessed when this LineSeg was added to the stack. It makes more sense in the recursive version. Where the previously prosessed lineSeg is the caller, or previous function.

A better name would be “callerLeft, callerRight and callerY”, representing the line segment prosessed by the caller of this “function”. Here a function is the code inside the “while (stack.size > 0)” loop. You do a function call by pushing a lineSeg onto the stack. It will be processed by the while loop later.

Thats why you can’t use the previous lineSeg on the stack, as it is probably not the caller.

fromTop can also be made more clear. It’s a boolean representing from what direction the current function was called. The caller is known to be on a neighbouring side, so it is used to find out if the caller is the top or bottom line. It would be better to just store the callers y, and check against that.

Hope this clears it up. If not, I can post a working version.

ah, i understand now :slight_smile:

I am currently on my home computer due to being sick today, so i cannot compare the filling times now with the previous times i have given, however as an indicator, that 2482x2688 image is being fillled in 469 msec on my Athlon XP2100. And the 512x512 is done in 47 msec.

I believe we have achieved a very fast fill!

Yeah, it’s a lean, mean flood-filling machine :slight_smile:

Okay, here’s a key difference between Java and C…

In C, recursion is kinda slow, and random access to an array is very fast (a stack is effectively random access as the next access is not deterministicly determinable at compile time).

In a Modern Java VM, thanks to array bounds checking and great JIT compilers, the reverse is true.

Fastest way to speed up something based on an array stack is to recode it as a true recursive routine. It’ll be simpler code, too.

For this and other great insights see:
http://java.sun.com/docs/books/performance

:wink:

JK

That’s true Jeff, and the recursive and non recursive version is equally fast. But it was made non recursive because of stack overflow problems, not speed.

So why not just increase your stack size?

is this in the main thread or a launched thread?

currently it is all in the main thread.

Obviously i would like the program to run with out the user having to specify switches for the jvm, so increasing stack size is not an option.

[quote]currently it is all in the main thread.

Obviously i would like the program to run with out the user having to specify switches for the jvm, so increasing stack size is not an option.
[/quote]
I don’t see how thats obvious, but if its a requirement then yes you are either going to have to spawn a thread with a large stack size or live with the default constraints.

:slight_smile:

minimizing the effort on the users side its always a high priority.

I am more than pleased with the current performance of the flood fill algorithm that Tom has suggested. If there comes a time that even faster fill is needed i will look into spawning a thread.

Thanks

[quote]:slight_smile:

minimizing the effort on the users side its always a high priority.
[/quote]
There are many solutions to that. Most installer generators (eg InstallAnywhere, InstallSheild for Java) allow you to package your program with command line flags as aprt of the installed execution front end. These front ends tend to be easier for the user to install and use then a Jar file as they even hide the java invocation.

On a simpler level a batch or .sh script can hide these things quite nicely as well/

OR you could spawn a secondary thread, as already mentioned.

Tom, can you post a working version of this ? This is very interesting .

Thanks

Jay

Sure. The following code generates the outline of a flower, and then fills it with a green color. The image is drawn in the recursive funtion, so you can see how it is filled. Might help you figure out how the algorithm works.


import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;

/** A very fast flood fill */
public class LinearFloodFill {
      private Frame frame;
      private BufferedImage bufferedImage;
      private int image[];
      private int fillcolor;
      private int startcolor;
      private int width;
      private int height;


    /** The progress is drawn on the specified frame. */
      public LinearFloodFill(Frame frame) {
            this.frame = frame;
      }
      
      
      /** Fills the image width fillcolor starting at the specified position */
      public synchronized void fill(int image[], int width, int height, int startx, int starty, int fillcolor) {
            this.image = image;
            this.width = width;
            this.height = height;
            this.fillcolor = fillcolor;
            startcolor = image[starty*width+startx];
            bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            linearFloodFill4(startx, starty, -1, -1, -2);
      }

      /**
       * Fills the line at (x, y). Then fills the line above and below the current line.
       * The border is defined as any color except the start color.
       */
      private int linearFloodFill4(int x, int y, int prevleft, int prevright, int prevy) {
            draw();
            int yOff = y*width;
            // fill left of (x,y) until border or edge of image
            int left=x;
            do {
                  image[yOff+left] = fillcolor;
                  left--;
            } while ((left >= 0) && (image[yOff+left] == startcolor));
            left++;
            // fill right of (x, y) until border or edge of image
            int right=x; 
            do {
                  image[yOff+right]=fillcolor;   
                  right++;
            } while ((right < width) && (image[yOff+right] == startcolor));
            right--;

            // check the line above this one. This is done by checking all the pixels
            // above current line. To speed it up we do not check the line segment
            // that we will fill next, or the line segment that was filled last.
            if (y>0) {
                  int topyOff = (y-1)*width;
                  if ((y-1) == prevy) {
                        int min = Math.min(prevleft, right);
                        for (int i=left; i<=min; i++) {
                              if (image[topyOff+i] == startcolor) {
                                    i = linearFloodFill4(i, y-1, left, right, y);
                              }
                        }
                        for (int i=Math.max(prevright, left); i<=right; i++) {
                              if (image[topyOff+i] == startcolor) {
                                    i = linearFloodFill4(i, y-1, left, right, y);
                              }
                        }
                  } else {
                        for (int i=left;i<=right;i++) {
                              if (image[topyOff+i] == startcolor) {
                                    i = linearFloodFill4(i, y-1, left, right, y);
                              }
                        }
                  }
            }
                  
            // check the line belov this one.
            if (y<height-1) {
                  int botyOff = (y+1)*width;
                  if (prevy == y+1) {
                        int min = Math.min(prevleft, right);
                        for (int i=left; i<=min; i++) {
                              if (image[botyOff+i] == startcolor) {
                                    i = linearFloodFill4(i, y+1, left, right, y);
                              }
                        }
                        for (int i=Math.max(prevright, left); i<=right; i++) {
                              if (image[botyOff+i] == startcolor) {
                                    i = linearFloodFill4(i, y+1, left, right, y);
                              }
                        }
                  } else {
                        for (int i=left;i<=right;i++) {
                              if (image[botyOff+i] == startcolor) {
                                    i = linearFloodFill4(i, y+1, left, right, y);
                              }
                        }
                  }
            }
            
            return right;
      }


      /**
       * Draws the last image to the frame.
       */
      public void draw() {
            if (frame != null && bufferedImage != null) {
                  Insets insets = frame.getInsets();
                  bufferedImage.setRGB(0, 0, width, height, image, 0, width);
                  Graphics g = frame.getGraphics();
                  g.drawImage(bufferedImage, insets.left, insets.top, null);
            }
      }
      
      
      
      /**
       * Tests the flood fill.
       */
      public static void main(String args[]) {
            Frame frame = new Frame();
            frame.setBounds(350, 250, 300, 300);
            frame.addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {System.exit(0);}});
            frame.show();

            // Create a flower. This should be replaced by the image you want to fill.
            int pixels[] = new int[256*256];
            for (int i=0; i<pixels.length; i++) {
                  pixels[i] = 0xffff0000;
            }
            for (double angle=0; angle<Math.PI*2; angle+=0.001) {
                  double radius = 50+Math.sin(angle*10)*45;
                  double x = 128+Math.cos(angle)*radius;
                  double y = 128+Math.sin(angle)*radius;
                  pixels[(int)y*256+(int)x] = 0xff000000;
            }

            // Flood fill the created image with a green color, starting at the center.
            // The image is drawn to the frame as it is filled.
            LinearFloodFill filler = new LinearFloodFill(frame);
            filler.fill(pixels, 256, 256, 128, 128, 0xff00ff00);
            filler.draw();
      }
}

Enjoy!!