parsecer
parsecer

Reputation: 5106

Fill objects with random color

I have a code that has a button. When a button is pressed, circles appear at random positions with random colors. There can only be 10 circles.

Now that I added random colors functionality, the problem is that after each circle is drawn, its color starts changing infinetely.

How can I make it so the colors don't change?

class Panel extends JPanel  {

    private JButton button;
    private Ellipse2D.Double[] circles;
    Integer count;


    public Panel()  {
            setup();
    }

    private void setup()  {
            count=new Integer(0);
            circles=new Ellipse2D.Double[10];
            button=new JButton(count.toString());
            button.addActionListener(new ActionListener() {
               @Override
               public void actionPerformed(ActionEvent e) {
                            Random r=new Random();
                            //position circles with diameter 100 in a way 
                            //that it would fit in a window's size
                            int highX=getWidth()-100;
                            int highY=getHeight()-100;
                            circles[count]=new 
                                     Ellipse2D.Double(r.nextInt(highX), 
                                          r.nextInt(highY), 100, 100);
                            count++;

                            button.setText(count.toString());
                    }
                });

          add(button);
        }


    public void paintComponent(Graphics g)  {
            super.paintComponent(g);
            paintStuff(g);
            repaint(); 
        }

    private void paintStuff(Graphics g)  {
            Graphics2D g2=(Graphics2D) g;
            g2.setPaint(Color.RED);

            if (count!=0)  {
                for (int i=0; i<count; i++)  {
                        g2.draw(circles[i]);
                        Random r=new Random();
                        int red=r.nextInt(256);
                        int green=r.nextInt(256);
                        int blue=r.nextInt(256);
                        g2.setPaint(new Color(red, green, blue));
                        g2.fill(circles[i]);
                }
            }
    }
}

public class Frame extends JFrame  {
     private Panel panel;
     public Frame()  {
        panel=new Panel();
        add(panel);
     }

     public static void main(String[] args)  {
          Frame frame=new Frame();
     }
}

Upvotes: 1

Views: 1845

Answers (2)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285403

Never call repaint within a painting method as that causes a "poor-man's" animation to occur. Instead call it in your JButton's ActionListener. Also, don't randomize within the painting method, but rather do this within the ActionListener. The painting method is not under your control, and you don't want to use it to change your object's state, but rather only to display it.

Other suggestions:

  • Your code still needs to set the JFrame's setDefaultCloseOperation
  • and still needs to set the JFrame visible
  • You never suggest sizing in the code. Myself, I recommend overriding public Dimension getPreferredSize() of your JPanel and call pack() on the JFrame after adding the JPanel but before displaying it.
  • I'd rename your classes so that the names don't clash with core Java classes and cause confusion to your instructors, us, or your future self.
  • Don't keep re-creating a new Random object within the for loop. Rather why not simply give the class a Random field, create it once, but reuse the object repeatedly.
  • You will want to associate a color with your shape/Ellipse2D. For a one-to-one correspondence, consider using a Map such as a HashMap<Shape, Color>.

For example:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

import javax.swing.*;

@SuppressWarnings("serial")
public class Panel2 extends JPanel {
    // preferred size constants
    private static final int PREF_W = 600;
    private static final int PREF_H = PREF_W;

    // map to hold circles and colors
    private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();

    public Panel2() {
        add(new JButton(new RandomColorAction()));
    }

    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet()) {
            return super.getPreferredSize();
        }
        return new Dimension(PREF_W, PREF_H);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        // create *smooth* drawings
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        paintStuff(g2);
    }

    private void paintStuff(Graphics2D g2) {
        // iterate through our map extracting all circles and colors
        // and drawing them
        for (Entry<Shape, Color> entry : shapeColorMap.entrySet()) {
            Shape shape = entry.getKey();
            Color color = entry.getValue();
            g2.setColor(color);
            g2.fill(shape);
        }
    }

    // listener for our button
    private class RandomColorAction extends AbstractAction {
        private static final int CIRC_WIDTH = 100;
        private Random random = new Random();
        private int count = 0;

        public RandomColorAction() {
            super("Random Circle: 0");
            putValue(MNEMONIC_KEY, KeyEvent.VK_R);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // create our random ellipses
            int x = random.nextInt(getWidth() - CIRC_WIDTH);
            int y = random.nextInt(getHeight() - CIRC_WIDTH);
            Shape shape = new Ellipse2D.Double(x, y, CIRC_WIDTH, CIRC_WIDTH);

            // create our random color using HSB for brighter colors
            float hue = random.nextFloat();
            float saturation = (float) (0.8 + random.nextFloat() * 0.2);
            float brightness = (float) (0.8 + random.nextFloat() * 0.2);
            Color color = Color.getHSBColor(hue, saturation, brightness);
            shapeColorMap.put(shape, color);

            // increment count, place items into map, repaint
            count++;
            putValue(NAME, "Random Circle: " + count);
            repaint();
        }
    }

    private static void createAndShowGui() {
        Panel2 mainPanel = new Panel2();

        JFrame frame = new JFrame("Panel2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

In comments, Camickr astutely points out:

A painting method should paint the current state of the component. By using the HashMap you are introducing the possibility of randomness. The order of iteration through the map can't be guaranteed. Therefore as new entries are added to the map the order each Shape is painted could change. Generally not a problem, but if two random shapes ever overlap, the result good be flip flopping which shape is painted on top of one another.

And of course, he is absolutely correct, since there is no guaranteed order for a HashMap. Fortunately the variable itself was declared to be of Map type, and so to preserve order all one needs to do is to change the actual object type from HashMap to that of LinkedHashMap, a class which per its API:

This implementation spares its clients from the unspecified, generally chaotic ordering provided by HashMap (and Hashtable), without incurring the increased cost associated with TreeMap.

So for TLDR, change this:

private Map<Shape, Color> shapeColorMap = new HashMap<>();

to this:

private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();

Edited to fix the color calculation.


A just for the fun of it version that introduces Path2D and AffineTransform with a MouseListener/MouseMotionListener to allow for dragging the circles:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;

import javax.swing.*;

@SuppressWarnings("serial")
public class Panel2 extends JPanel {
    // preferred size constants
    private static final int PREF_W = 600;
    private static final int PREF_H = PREF_W;

    // map to hold circles and colors
    private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();

    public Panel2() {
        add(new JButton(new RandomColorAction()));
        MyMouse myMouse = new MyMouse();
        addMouseListener(myMouse);
        addMouseMotionListener(myMouse);
    }

    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet()) {
            return super.getPreferredSize();
        }
        return new Dimension(PREF_W, PREF_H);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        // create *smooth* drawings
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        paintStuff(g2);
    }

    private void paintStuff(Graphics2D g2) {
        // iterate through our map extracting all circles and colors
        // and drawing them
        for (Entry<Shape, Color> entry : shapeColorMap.entrySet()) {
            Shape shape = entry.getKey();
            Color color = entry.getValue();
            g2.setColor(color);
            g2.fill(shape);
        }
    }

    private class MyMouse extends MouseAdapter {
        private Entry<Shape, Color> selected = null;
        private Path2D path;
        private Point p = null;

        @Override
        public void mousePressed(MouseEvent e) {
            Set<Entry<Shape, Color>> entrySet = shapeColorMap.entrySet();
            // get Shape pressed
            for (Entry<Shape, Color> entry : entrySet) {
                if (entry.getKey().contains(e.getPoint())) {
                    selected = entry;
                }
            }

            if (selected != null) {
                path = new Path2D.Double(selected.getKey());
                // move it to the top
                entrySet.remove(selected);
                shapeColorMap.put(path, selected.getValue());

                p = e.getPoint();
                repaint();
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (selected != null) {
                moveSelected(e);
            }
            selected = null;
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (selected != null) {
                moveSelected(e);
            }
        }

        private void moveSelected(MouseEvent e) {
            int x = e.getX() - p.x;
            int y = e.getY() - p.y;
            p = e.getPoint();

            AffineTransform at = AffineTransform.getTranslateInstance(x, y);
            path.transform(at);
            repaint();
        }
    }

    // listener for our button
    private class RandomColorAction extends AbstractAction {
        private static final int CIRC_WIDTH = 100;
        private Random random = new Random();
        private int count = 0;

        public RandomColorAction() {
            super("Random Circle: 0");
            putValue(MNEMONIC_KEY, KeyEvent.VK_R);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // create our random ellipses
            int x = random.nextInt(getWidth() - CIRC_WIDTH);
            int y = random.nextInt(getHeight() - CIRC_WIDTH);
            Shape shape = new Ellipse2D.Double(x, y, CIRC_WIDTH, CIRC_WIDTH);

            // create our random color using HSB for brighter colors
            float hue = random.nextFloat();
            float saturation = (float) (0.8 + random.nextFloat() * 0.2);
            float brightness = (float) (0.8 + random.nextFloat() * 0.2);
            Color color = Color.getHSBColor(hue, saturation, brightness);
            shapeColorMap.put(shape, color);

            // increment count, place items into map, repaint
            count++;
            putValue(NAME, "Random Circle: " + count);
            repaint();
        }
    }

    private static void createAndShowGui() {
        Panel2 mainPanel = new Panel2();

        JFrame frame = new JFrame("Panel2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

Upvotes: 5

Flood2d
Flood2d

Reputation: 1358

paintStuff(Graphics g);

Is called many times, and each time it refreshes the circle color. That's the wrong place to set the color, you need to set it when you add the circle.

Create a java.awt.Color array as a global variable

private Color[] circlesColors;

Then just fill this array in the actionPerformed(...) method. This is the setupmethod with the changes

private void setup()  {
    count=new Integer(0);
    circles=new Ellipse2D.Double[10];
    circlesColors = new Color[10]; //Init the colors array to the same size of circles array
    button=new JButton(count.toString());
    button.addActionListener(new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent e) {
          Random r=new Random();

          int highX=getWidth()-100;
          int highY=getHeight()-100;

          circles[count]=new Ellipse2D.Double(r.nextInt(highX), r.nextInt(highY), 100, 100);
          circlesColors[count] = new Color(r.nextInt(256), r.nextInt(256), r.nextInt(256)); //Assign random color 
          count++;

          button.setText(count.toString());
      }
   });

   add(button);
}

Then in your paint(...) method

private void paintStuff(Graphics g)  {
     Graphics2D g2=(Graphics2D) g;
     g2.setPaint(Color.RED);

     if (count!=0)  {
         for (int i=0; i<count; i++)  {
             g2.draw(circles[i]);
             g2.setPaint(circlesColors[i]); //Get and set the color associated to the circle 
             g2.fill(circles[i]);
         }
     }
}

Upvotes: 2

Related Questions