Hello all, I am new here and fairly new to java and programming in general. I decided to make a little color chooser hoojie for fun (jar file attached), but it is tremendously slow. Partly it is my fault (code def not perfect..) but when I checked what was taking so long it was pretty clear that the calls to paint were the real bottleneck.

Basically, I am painting each pixel independently using g.fillRect(x,y,1,1) along with a bunch of linear algebra/geometry. This does not seem very elegant, so I tried again by painting the same stuff onto a VolatileImage and then painting the image onto the on-screen panel (thinking that it was the on-screen paint calls that were slowing it all up) but that made no real difference.

What am I missing here? Ideally what I would like is take (and assuming that getColors() returns a well-formed array[][])

Color[][] colors = getColors();
for(int i=0; i<colors.length; i++) for(int j=0; j<colors[0].length; j++){
  g.setColor(colors[i][j]);
  g.fillRect(i,j,1,1);
}

and replace it with something approaching the following

int firstPixelX=0;
int firstPixelY=0;
g.paintPixels(firstPixelX,firstPixelY,colors);

I know that this is not an option, but I also know that there's a lot I don't know.. So, if anyone has good suggestions I would be very grateful.

Thank you

Recommended Answers

All 8 Replies

No idea if this will improve the speed. Here's some code that uses PixelGrabber to change colors:

// Fill a polygon starting at a clicked on point

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

public class FillPolygon extends JPanel implements ActionListener {

   final int MaxLvl = 3000;            // Stop recursion at this level

   final int Width = 300;
   final int Height = 300;
    
    final static Color bg = Color.white;
    final static Color fg = Color.black;
    final static Color red = Color.red; 
    final static Color white = Color.white;

    final static JButton doneBtn = new JButton("Done");

    // Define the points in the polygon
    int[] xPts = {50, 250, 250,  50,  50, 100, 150};
    int[] yPts = {50,  50, 250, 250, 150, 200, 150};

    Polygon poly = new Polygon(xPts, yPts, xPts.length);

    Vector polyPts;                    // Save new points here

    BufferedImage theImage;
    Image anImage;
    Graphics g2d;
    int[] pixels;

    boolean debug = false; //true;


    //----------------------------------------------------------------------
    public FillPolygon() {
	     setBackground(bg);
        buildImage();

        // Get the image into an array of pixels
        int w = Width, h = Height;
        pixels = new int[w * h];
        try {
           PixelGrabber pg = new PixelGrabber(theImage, 0, 0, w, h, pixels, 0, w);
           pg.grabPixels();

           // Check for errors.
           if ((pg.status() & ImageObserver.ABORT) != 0) {
               System.err.println("Error while fetching image");
               System.exit(1);
           }
        } catch (Exception e) {
           e.printStackTrace();
        }

        addMouseListener(new MouseEventHandler());
        doneBtn.addActionListener(this);
    }
    //--------------------------------------------
    // Build the image to show & fill
    private void buildImage() {
        // Create an image to draw on
        theImage = new BufferedImage(Width, Height, BufferedImage.TYPE_INT_RGB);
        g2d = theImage.getGraphics();   // THis also works
        g2d.setColor(Color.yellow);     // Set fill/background color
        g2d.fillRect(0,0, Width, Height);
        g2d.setColor(Color.black);
        g2d.drawPolygon(poly);
        g2d.setColor(Color.white);
        g2d.fillPolygon(poly);
        repaint();
    } // end buildImage()

    private void blankImg() {
        g2d = theImage.getGraphics();   // THis also works
        g2d.setColor(Color.yellow);     // Set fill/background color
        g2d.fillRect(0,0, Width, Height);
        repaint();
    }

    //-----------------------------------------------------
    // Class to hold the loc of pixels
    class MyPoint {
      int x;
      int y;
      MyPoint(int x, int y) {
         this.x = x;
         this.y = y;
      }
      public boolean equals(MyPoint mp) {
         return x == mp.x && y == mp.y;
      }
      public String toString() {
         return "[" + x + "," + y + "]";
      }
    } // end class MyPoint

   int targetPixelValue = 0;
   final int blueRGB  = 0Xff0000ff;
   final int greenRGB = 0Xff00ff00;
   final int whiteRGB = 0XFFFFFFFF;
   final int blackRGB = 0XFF000000;

   //---------------------------------------------------------------------------------
   class MouseEventHandler extends MouseAdapter {
        public void mousePressed(MouseEvent evt) {
          final int x = evt.getX(), y = evt.getY();
          // Are we drawing new polygon or to fill existing one?
          if(drawPolyMI.isSelected()) {
            MyPoint mp = new MyPoint(x, y);
            polyPts.add(mp);           // Add new point to list
            if(polyPts.size() > 1) {
               // draw the polygon
               g2d = theImage.getGraphics();
               g2d.setColor(Color.black);
               MyPoint mp0 = (MyPoint)polyPts.get(0);  // get first
               for(int i=1; i < polyPts.size(); i++) {
                  MyPoint mp1 = (MyPoint)polyPts.get(i); // get next
                  g2d.drawLine(mp0.x, mp0.y, mp1.x,mp1.y);
                  mp0 = mp1;           // move forward
               } // end for()
               repaint();
            }else {
               theImage.setRGB(x, y, blackRGB); // change the color of the point
            }
          }else{
            if(!poly.contains(x, y)) {
               System.err.println("outside the polygon");
               return;
            }
            // Save color of pixel where mouse Pressed
            targetPixelValue = pixels[y * Width + x];
            int pix = theImage.getRGB(x, y);
            System.out.println("mP pix=" + pix + " vs " + targetPixelValue
                               + " at " + x + "," + y);

//            g2d.setColor(Color.blue);   // change the color of that pixel
//            g2d.fillRect(x, y, 1, 1);
//            theImage.setRGB(x, y, blueRGB); // Change the color of that pixel
//            repaint();
            // Start a thread to fill in the poly starting at this point
            new Thread(new Runnable() {
               public void run() {
//                  Vector v = new Vector();
//                  v.add(new MyPoint(x, y));
//                  fill4(v, 0);
//                  fourFill(x, y, whiteRGB, greenRGB, 0);
                  fillWithQueue(x, y, whiteRGB, greenRGB);
                  System.out.println(">>> Finished filling <<<");
               }
            }).start();
          }
        } // end mousePressed()

        public void mouseReleased(MouseEvent evt) {
            repaint();
        }
    } // end class

    //-----------------------------------------------------------------------
    /*
    Procedure Four_Fill (x, y, fill_col, bound_col: integer);
      var
           curr_color: integer;
      begin
           curr_color := inquire_color(x, y)
           if (curr_color <> bound color) and (curr_color <> fill_col) then
           begin
               set_pixel(x, y, fill_col)
               Four_Fill (x+1, y, fill_col, bound_col);
               Four_Fill (x-1, y, fill_col, bound_col);
               Four_Fill (x, y+1, fill_col, bound_col);
               Four_Fill( x, y-1, fill_col, bound_col);
      end;
    */
    public void fourFill(int x, int y, int mtPixel, int fillCol, int lvl) {
      lvl++;
      if(lvl > MaxLvl)
         return;
      try {
         if(theImage.getRGB(x, y) == mtPixel) {
            theImage.setRGB(x, y, fillCol); // change the color
            fourFill(x+1, y, whiteRGB, greenRGB, lvl);
            fourFill(x-1, y, whiteRGB, greenRGB, lvl);
            fourFill(x, y+1, whiteRGB, greenRGB, lvl);
            fourFill(x, y-1, whiteRGB, greenRGB, lvl);
         } // end if()
      }catch(Error e) {
         System.err.println("Error: " + e + ", lvl=" + lvl);
         //Error: java.lang.StackOverflowError, lvl=3154
      }
    } // end four_fill()

    //-----------------------------------------------------------------------
    public void fill4(Vector vec, int lvl )   {
      lvl++;                           // To next level
      try {
         // Set the color for all the points in vec
         for(int i=0; i < vec.size(); i++) {
            MyPoint mp = (MyPoint)vec.get(i);
//            pixels[mp.y * Width + mp.x] = greenRGB;   // need to createImage below to see
    			theImage.setRGB(mp.x, mp.y, greenRGB); // set the color for this one
         } // end for(i)

         if(debug) System.out.println("Set colors for " + vec.size() + ", lvl=" + lvl);
//         MemoryImageSource mis = new MemoryImageSource(Width, Height, pixels, 0, Width);
//         anImage = createImage(mis);

         repaint();
         // Get all the neighbors to the points in vec
         Vector newVec = getNeighbors(vec, whiteRGB, lvl); // get all the empty neighbors
         if(newVec == null || newVec.size() == 0)
            return;                    // exit if none found
         fill4(newVec, lvl);
      }catch(Error e) {
         System.err.println("Error: " + e + ", lvl=" + lvl);
      }
  } // end fill4

  //------------------------------------------------------------------
  // Use a FIFO queue vs recursion to do the fill
  Vector theQueue = new Vector(256000);

  private void fillWithQueue(int x, int y, int mtPixel, int fillColor) {
    theQueue.add(new MyPoint(x, y));   // Prime the pump

    int setCnt = 0;
    int cnt = 0;
    int dups = 0;
    int maxQueueSize = 0;


    // Process the points until none left
    while(theQueue.size() > 0) {
      MyPoint mp = (MyPoint)theQueue.remove(0);
      if(theImage.getRGB(mp.x, mp.y) != mtPixel) {    // Test if only MT pixels in queue
         // Possible duplicates !!!            
//         System.out.println("Not MT pixel at "+ mp);  //Not MT pixel at [184,136] MANY!!!!
         dups++;
         continue;
      }
      cnt++;
		theImage.setRGB(mp.x, mp.y, fillColor); // set the color for this one
      if(cnt % 100 == 0)
         repaint();                    // Every 100 pixels   

      // Check/add the 4 neighbors
      // North = y-1
      if(theImage.getRGB(mp.x, mp.y-1) == mtPixel) {
         MyPoint nmp = new MyPoint(mp.x, mp.y-1);
//            if(!newVec.contains(nmp))
            theQueue.add(nmp);
      }else setCnt++;

      // East = x+1
      if(theImage.getRGB(mp.x+1, mp.y) == mtPixel) {
         MyPoint nmp = new MyPoint(mp.x+1, mp.y);
//            if(!newVec.contains(nmp))
            theQueue.add(nmp);
      }else setCnt++;

      // South = y+1
      if(theImage.getRGB(mp.x, mp.y+1) == mtPixel){
         MyPoint nmp = new MyPoint(mp.x, mp.y+1);
//            if(!newVec.contains(nmp))
            theQueue.add(nmp);
      }else setCnt++;

      // West = x-1
      if(theImage.getRGB(mp.x-1, mp.y) == mtPixel){
         MyPoint nmp = new MyPoint(mp.x-1, mp.y);
//            if(!newVec.contains(nmp))
            theQueue.add(nmp);
      }else setCnt++;

      if(theQueue.size() > maxQueueSize)
         maxQueueSize = theQueue.size();

//      if((cnt % 500) == 0)
//         System.out.print(" "+ theQueue.size());
    } // end while

    System.out.println("end of queue. setCnt=" + setCnt + ", maxQ=" + maxQueueSize
                        + ", #dups=" + dups);
    //end of queue. setCnt=65549, maxQ=298, #dups=31952
    //end of queue. setCnt=65549, maxQ=400, #dups=31952
    //end of queue. setCnt=65549, maxQ=589, #dups=31952
    //end of queue. setCnt=65549, maxQ=298, #dups=31952
  } // end fillWith Queue

  //-------------------------------------------------------------------------------------------
  // Return a Vector holding the points for all the neighbors to the points in incoming vector
  private Vector getNeighbors(Vector vec, int mtPixel, int lvl) {
      if(lvl > MaxLvl)
         return null;                  // Q&D exit
       int setCnt = 0;
       Vector newVec = new Vector(vec.size()*3);

       // Look at the four neighbors: N, E, S, W  
       for(int i=0; i < vec.size(); i++) {
         MyPoint mp = (MyPoint)vec.get(i);
         // North = y-1
         if(theImage.getRGB(mp.x, mp.y-1) == mtPixel) {
            MyPoint nmp = new MyPoint(mp.x, mp.y-1);
//            if(!newVec.contains(nmp))
               newVec.add(nmp);
         }else setCnt++;
         // East = x+1
         if(theImage.getRGB(mp.x+1, mp.y) == mtPixel) {
            MyPoint nmp = new MyPoint(mp.x+1, mp.y);
//            if(!newVec.contains(nmp))
               newVec.add(nmp);
         }else setCnt++;
         // South = y+1
         if(theImage.getRGB(mp.x, mp.y+1) == mtPixel){
            MyPoint nmp = new MyPoint(mp.x, mp.y+1);
//            if(!newVec.contains(nmp))
               newVec.add(nmp);
         }else setCnt++;
         // West = x-1
         if(theImage.getRGB(mp.x-1, mp.y) == mtPixel){
            MyPoint nmp = new MyPoint(mp.x-1, mp.y);
//            if(!newVec.contains(nmp))
               newVec.add(nmp);
         }else setCnt++;
       } // end for(i) thru vec

       if(debug) System.out.println(newVec.size() + " neighbors at lvl=" + lvl + ", setCnt=" + setCnt
                                    + ", # incoming pts=" + vec.size());
       return newVec;
  } // end getNeighbors()

  //-----------------------------------------------------------
  // Handle GUI events
   public void actionPerformed(ActionEvent e) {
      Object obj = e.getSource();
        if (obj == doneBtn) {
          if(drawPolyMI.isSelected()) {
            doneBtn.setEnabled(false); // done - turn off
          }else{
            System.err.println("doneBtn called without drawPoly");
            return;
          }
          drawPolyMI.setSelected(false);  // turn off when done

          // Copy points from polyPts to xPts and yPts
          xPts = new int[polyPts.size()];
          yPts = new int[polyPts.size()];
          for(int i=0; i < polyPts.size(); i++) {
            MyPoint mp = (MyPoint)polyPts.get(i);
            xPts[i] = mp.x;
            yPts[i] = mp.y;
          }
          poly = new Polygon(xPts, yPts, xPts.length); // create new Polygon
          buildImage();                // go draw the new one

        }else if(obj == clearPolyMI) {
          buildImage();

        }else if(obj == drawPolyMI) {
          if(drawPolyMI.isSelected()) {
            doneBtn.setEnabled(true);   // turn on
            polyPts = new Vector();
            blankImg();

          }else{
            doneBtn.setEnabled(false); // done - turn off
          }
	     }else{
         System.out.println("unkn ae " + e);
        }
   }

   public void paintComponent(Graphics g) {
//      System.out.println("g=" + g);   //g=sun.java2d.SunGraphics2D
   	super.paintComponent(g);
      if(anImage != null)
         g.drawImage(anImage, 0, 0, this);
      else   
         g.drawImage(theImage, 0, 0, this);
//      g.setColor(Color.red);
//      g.fillPolygon(poly);
   } // end paintComponent()

   JMenu optionsM = new JMenu("Options");
   JCheckBoxMenuItem drawPolyMI = new JCheckBoxMenuItem("Draw Polygon");
   JMenuItem clearPolyMI = new JMenuItem("Clear poly");

   private JMenuBar createMB() {
      JMenuBar mb = new JMenuBar();
      optionsM.add(drawPolyMI);  
      drawPolyMI.addActionListener(this);
      optionsM.add(clearPolyMI);  
      clearPolyMI.addActionListener(this);
      mb.add(optionsM);
      return mb;
   }

   //--------------------------------------------------------------------
   public static void main(String s[]){
   	WindowListener wl = new WindowAdapter() {
   		public void windowClosing(WindowEvent e) {System.exit(0);}
   		public void windowClosed(WindowEvent e) {System.exit(0);}
   	};
   	JFrame f = new JFrame("Fill Polygon");
   	f.addWindowListener(wl);
      FillPolygon fp = new FillPolygon();
      f.setJMenuBar(fp.createMB());
   	f.getContentPane().add(BorderLayout.CENTER, fp);

   	JPanel panel = new JPanel();
   	panel.add(doneBtn);
      doneBtn.setEnabled(false);
    	f.getContentPane().add(BorderLayout.SOUTH, panel);
      f.setResizable(false);
   	f.setSize(300, 400);
      f.setLocationRelativeTo(null);
   	f.setVisible(true);
    }  // end main()

} // end class FillPolygon

The pixel grabber is interesting (I had been wondering if that functionality was built in somehow) but I do not see how this will solve my problem. If I understand the code correctly, using this implementation, I would still be invoking the same number of paint calls. Much appreciation though.

Perhaps this code is not how you'd want to do it. The idea in this code was to show the progression of the color as the area was filled. I'd think you could set ALL the pixel's colors before going to paint.

Perhaps this code is not how you'd want to do it. The idea in this code was to show the progression of the color as the area was filled. I'd think you could set ALL the pixel's colors before going to paint.

Yes. Exactly. Is there a way to do that without having to invoke multiple paint methods on a Graphics object (e.g. taken from an offscreen image)?

From the above program. I believe the following will change the color of a pixel
theImage.setRGB(x, y, blackRGB); // change the color of the point

Use this method to change all the pixels before painting the image.

Excellent. I think the way to go will be to use the theImage.setData( aRaster );// to update all the pixel colors. Unfortunately can't implement it right now (got everything on my comp at home), but will say how it went tomorrow.

Thanks for the help Norm, it worked out really well using the BufferedImage. The time it took to actually paint was reduced by well over a factor of 100 (although this number is dependent upon the size of panel). The way I got it to work was to use the method:

bufferedImage.getRaster().setPixels(int x0, int y0, int width, int height, int[] colorData)

I wrote up a little program as an example usage:

package buffered_image_example;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * @author fresidue
 */
public class Main extends JFrame
{
    private static final Dimension DIM = new Dimension(256,256);
    private final ExamplePanel panel;

    public Main()
    {
        this.setTitle("BufferedImage Example");
        this.setVisible(true);
        this.setResizable(false);

        panel = new ExamplePanel();
        panel.setPreferredSize(DIM);

        this.getContentPane().add(panel);
        pack();
    }

    private class ExamplePanel extends JPanel
    {
        private BufferedImage image = null;

        @Override
        public void paintComponent(Graphics g)
        {
            setImage();
            if( image != null )
            {
                g.drawImage(image, 0, 0, rootPane);
            }
        }

        private void setImage()
        {
            Dimension dim = this.getSize();
            if( dim.width*dim.height != 0 )
            {
                if( image == null )
                {
                    image = new BufferedImage(dim.width, dim.height, 
                                          BufferedImage.TYPE_3BYTE_BGR);
                }

                int[] colorVals = getColorValues(dim);
                image.getRaster().setPixels(0, 0, dim.width, dim.height, colorVals);
            }
        }

        /**
         * This creates a 1-dimensional array of the correct size and
         * format that can be used to update the BufferedImage image
         * with type = BufferedImage.TYPE_3BYTE_BGR
         */
        private int[] getColorValues(Dimension size)
        {
            int[] rv = new int[ 3 * size.width * size.height ];
            int fillIndex = 0;

            /**
             * In the following double-loop I switch the "natural" order of i and j
             * to emphasize the fact that the WritableRaster rasters in the X-direction
             * first and then increments Y-index at the end of each line
             */
            for(int j=0; j<size.height; j++) for(int i=0; i<size.width; i++)
            {
                int r_value = i;   // changes red in x-dimension
                int g_value = j;   // changes green in y-dimension
                int b_value = 0;   // no blue included

                // fill her up
                rv[fillIndex] = r_value;
                fillIndex++;
                rv[fillIndex] = g_value;
                fillIndex++;
                rv[fillIndex] = b_value;
                fillIndex++;

            }
            return rv;
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        ( new Main() ).setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    }

}

thanks for the sample code. Into the library it goes.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.