1

I need to display a large number (500+) of small circles on a form to simulate LEDs. However, these circles need to be quite small, around 8 or 9 pixels diameter.

So far, in my testing, I've put together some code that creates an Led class that uses a Shape (Ellipse2D.Double) and displays it directly on the JFrame from the JFrame's paint method.

This has led me to two observations/issues:

1) Firstly, unless there is an alternate method, Java appears to have trouble in drawing small circles. They appear to 'break' in the lower right corner with a pen width of default (or 1 pixel), which cuts this part off leaving a deformed circle. If there any way I can draw (lots of) small circles and have them look right?

2) My subclassed JFrame overrides the paint method to draw these 'leds', although calls the super.paint as well to ensure the JFrame gets drawn. However, I'm seeing that it rarely draws the led on the first appearance, or when the form is moved off-screen and back, or when an application it put in front and moved away again, and the only time the paint method is called is when I minimize/maximize the form. Shouldn't paint be called every time the form needs painting?

Dave
  • 1,696
  • 4
  • 23
  • 47

4 Answers4

3

You shouldn't override paint(). Use paintComponent() instead. Also, JFrames are slightly strange things, I'd use JPanel as my BaseClass.

About your observation: Might this be caused by antialiasing? Did you try to turn antialiasing off via setRenderingHints()?

EDIT: After the comment below, I've written a small test program. Circles look nice with this:

import javax.swing.*;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.RenderingHints;

class Test extends JFrame {

    public Test() {
        setContentPane(new JPanel() {
                public void paintComponent(Graphics g){
                    super.paintComponent(g);
                    Graphics2D g2d = (Graphics2D) g;
                    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                    for (int i = 0; i < 500; i++){
                        int x = (int) (Math.random() * getWidth());
                    int y = (int) (Math.random() * getHeight());
                    g.fillOval(x,y,8,8);
                    }
                }
        });
    }

    public static void main(String[] args){
        Test t = new Test();
        t.setSize(new Dimension(640, 480));
        t.setVisible(true);
    }
}
zedoo
  • 10,562
  • 12
  • 44
  • 55
  • No, I have altered anything to do with antialiasing. This is just very basic "create & display a circle on a JFrame" – Dave Jan 04 '10 at 14:55
  • Hmm, I think the default is to have antialiasing on. This shows how to turn it on (to turn it off use RenderingHints.VALUE_ANTIALIAS_OFF): http://www.java2s.com/Code/JavaAPI/java.awt/Graphics2DsetRenderingHintsMaphints.htm – zedoo Jan 04 '10 at 15:15
  • I thought the default was *off*. It needs to be *on*, especially for small circles: `g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)`. – trashgod Jan 04 '10 at 18:24
  • trashgod, I've tested this on my box and you're right. Here it's off by default. Also, when enabling antialiasing 8px circles look neat here. – zedoo Jan 04 '10 at 20:00
  • Thank you, this is exactly what I was looking to achieve. Circles that are actually round :) I guess the anti-aliasing is far more critical in smaller draws to ensure that they look right. Although, I just noticed that the fix appears to come from using fillOval instead of Ellipse2D. Even antialised, the Ellipse2D has the breaking in the lower right. – Dave Jan 05 '10 at 07:52
2

I've been banging my head against this issue for a while, with similar results to those described here, when I finally was tipped off to the fact that fillOval() does a much better job achieving roundness than either drawOval() or Ellipse2D. The following slightly hacky approach got me what I needed:

g2d.setColor(Color.black);                    
g2d.fillOval((int)x, (int)(y - w / 2), (int)w, (int)w);
g2d.setColor(Color.white);           
g2d.fillOval((int)x+1, (int)(y - w / 2)+1, (int)w-2, (int)w-2);
Andro Selva
  • 53,910
  • 52
  • 193
  • 240
Siegfried
  • 21
  • 1
  • This seems to be true for the fill functions in general (i.e. if I create a new `Shape circle = Ellipse2D(…)` for my circle and then call `g2d.fill(circle);` I get much rounder small circles. Using your 'hacky' approach gets me round circles with round borders. Upvoting +1 for solving my problem better than the accepted answer, but only because StackOverflow has not yet implemented 'buy a beer' buttons instead. :^) – JVMATL Dec 19 '13 at 21:51
1

These LEDs should be Components like everything else on the form. I think you should use Icons, maybe ImageIcons, to represent your LEDs. That way, you can essentially have them rendered once and after that the same image will be displayed whenever needed. It's handy that you can use images, because then you can use an image that has exactly the shape you'd like to see.

Carl Smotricz
  • 66,391
  • 18
  • 125
  • 167
1

As far as the 'break' goes, I would look at the bevel setting of your graphics object.

But, I would recommend reading a .png at program start and then displaying that instead of drawing it on your own.


RE: paint() not being called all the time.

Yep, thats how it works. If you need your component to be redrawn at a certain time, you need to force it. Call repaint() to force a redraw.

If you a going to call repaint() from another thread, (ie. a timer thread), be sure to wrap the call in SwingUtilities.invokeLater():

SwingUtilities.invokeLater( new Runnable()
{
    @Override
    public void run()
    {
        myForm.repaint();
    }
} );

Update: maybe you should post some code... I threw together a small test app and didn't see any problems with small circles.

public class MyPanel extends JPanel
{
    public void paint(Graphics _g)
    {
        Graphics2D g = (Graphics2D) _g;

        g.setStroke( new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND) );
        for(int x = 10, w = 1; w < 20; x += w*2, w++)
        {
            Ellipse2D.Double ed = new Ellipse2D.Double(x, 10, w, w);
            g.fill( ed );
        }

        for(int x = 10, w = 1; w < 20; x += w*2, w++)
        {
            Ellipse2D.Double ed = new Ellipse2D.Double(x, 80, w, w);
            g.draw( ed );
        }
    }

    public static void main(String[] args)
    {
        JFrame frame = new JFrame();
        frame.add( new MyPanel() );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setSize( 400, 400 );

        frame.setVisible( true );
    }
}

I tried varying the stroke params to see if I could cause a visual break, but was unsuccessful. What do you see?


Not to beat a dead horse, but when I zoom into the output of that program, my circles are pretty much symmetric, minus a little pixel turd on the left:

circle pics

Is this similar to what you are getting?

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Trevor Harrison
  • 1,744
  • 1
  • 14
  • 20
  • I haven't altered any bevel settings. All I want is a simple circle (outline & fill). Would I really need to set bevel on this? I've pondered using images to draw them, although I was concerned that using images would be much slower than renedering directly to the frame. These 'leds' would be updated regularly in real time (from a communications class) and given that I'll have a lot of these things, any slowness in the led would be exaggerated quite a bit. – Dave Jan 04 '10 at 14:59
  • I was kinda vague and misleading. Look at BasicStroke, and its join setting. (thats where my memory got the bevel from...) You probably want JOIN_MITER or JOIN_ROUND. After creating the BasicStroke object, do a g.setStroke( basicStroke ). – Trevor Harrison Jan 04 '10 at 15:08
  • 1
    Also, regarding your speed questions... if anything, copying a pre-rendered circle image onto your canvas will be faster than drawing circles each time. – Trevor Harrison Jan 04 '10 at 15:36
  • When using the code you posted, I see 7/9/11 circles having a straight diagonal in the lower right corner. This isn't obvious on the filled circles, but clearly visible on the outline ones. – Dave Jan 05 '10 at 07:42
  • No, this isn't what I'm getting. That's what I was expecting. On my side, it appears to be more visibly wrong with odd pixel sizes. I'll post an image when I get back to work tomorrow and show you what I mean. – Dave Jan 05 '10 at 21:45
  • Sorry, I've been snowed in for 2 days and only just got back to work. http://www.sleeping-dragons.co.uk/example.png Hrm, apparently I can't post actual images in my posts yet, so you'll need to click the link :/ – Dave Jan 08 '10 at 08:51
  • wow, that is pretty icky. Did you say what platform you are running on? – Trevor Harrison Jan 08 '10 at 16:18
  • Yeah, this is Windows XP SP3, using a pretty much default install of Netbeans 6.7 – Dave Jan 11 '10 at 10:35
  • pretty similar to what I'm running. I give up. :) – Trevor Harrison Jan 11 '10 at 14:07