6

I'm using Java Graphics and I keep getting "ugly" circles.

Here's what my Java program makes enter image description here

And here's the same thing being made in Matlab enter image description here

I think it is clear that the Java one is not as "nice" looking as the Matlab one, particularly on the edges of the circle. Note that this has nothing to do with the resolution...these images are practically the same size. Also note that I am already setting rendering hints.

Here's a stand alone with a Main function you can run to test this out.

package test;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;

public class SimplePaint02 {

    private static final int LINE_THICKNESS = 4;
    private static final int LINE_GAP = 10;
    private Color lineColor = Color.red;

    public static void main(String[] args) {
        new SimplePaint02();
    }

    public SimplePaint02() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(100, 100);
        }

        @Override
        public void paintComponent(Graphics g) {

            int radius = 50;
            BufferedImage buffer = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = buffer.createGraphics();
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);

            Ellipse2D circle = new Ellipse2D.Float(0, 0, radius,radius);
            Shape clip = g2d.getClip();
            g2d.setClip(circle);
            AffineTransform at = g2d.getTransform();

            g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(45),radius / 2, radius / 2));

            int gap = LINE_GAP;

            g2d.setColor(Color.WHITE);
            g2d.fill(circle);

            g2d.setColor(lineColor);
            //g2d.setStroke(new BasicStroke(LINE_THICKNESS));
            for (int index = 0; index < 10; index++) {
                int x1 = index*gap-(LINE_THICKNESS/2);
                int y1 = 0;
                int x2 = index*gap+(LINE_THICKNESS/2);
                int y2 = radius;
                int width = x2 - x1;
                int height = y2 - y1;

                g2d.fillRect(x1, y1, width, height);
                //g2d.drawLine(index * gap, 0, index * gap, getRadius());
            }

            g2d.setTransform(at);
            g2d.setClip(clip);
            g2d.dispose();
            g.drawImage(buffer, 0, 0, this);
        }

    }

}
CodeGuy
  • 28,427
  • 76
  • 200
  • 317

5 Answers5

6

EDIT: Please see Code Guy's answer below for a solution. This is marked correct because it was Joey Rohan who figured it out initially!


I got smooth edge when i tried out same thing:

  g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,    RenderingHints.VALUE_ANTIALIAS_ON);

enter image description here

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

public class DrawSmoothCircle {
    public static void main(String[] argv) throws Exception {
        BufferedImage bufferedImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = bufferedImage.createGraphics();

        g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setPaint(Color.green);
        g2d.fillOval(10, 10, 50, 50);
        g2d.dispose();

        ImageIO.write(bufferedImage, "png", new File("e:\\newimage.png"));
    }
}

UPDATE:

After searching alot:

There is nothing wrong with the code but,

Well, unfortunately Java 2D (or at least Sun's current implementation) does not support "soft clipping."

But Also got a trick for the clips: Follow This link,you can achieve what you are asking for.

(Also, i got a smooth edge, cause i din't use clip stuff,in my above image)

Community
  • 1
  • 1
joey rohan
  • 3,505
  • 5
  • 33
  • 70
  • Where did the lines go? If you run the code, you'll get a circle with lines. – CodeGuy Jan 20 '13 at 08:29
  • No i mean din't with the middle lines.Ok will try out. – joey rohan Jan 20 '13 at 08:32
  • Yeah, it works fine without the lines. Something in the process of making the lines is doing it. – CodeGuy Jan 20 '13 at 08:37
  • The problem is not actually when drawing shapes, but instead when filling or painting to a `Clip`. – Andrew Thompson Jan 20 '13 at 08:37
  • @AndrewThompson I see. So do you think there's another way to draw this shape without using a clip? – CodeGuy Jan 20 '13 at 08:39
  • @joeyrohan thanks for looking. so it seems i'll need to think of a different way to draw this without using clipping. any ideas? – CodeGuy Jan 20 '13 at 09:24
  • @CodeGuy follow the link in my answer, there is a nice trick,you can do smooth edge with that trick (using clips only);) ,tell me if you are successful – joey rohan Jan 20 '13 at 09:26
  • @joeyrohan i did visit that link but in their "smooth edge" example, they didn't use clipping at all (never a clip() call). I need to use clip() in order for the lines inside the circle to be clipped properly. – CodeGuy Jan 20 '13 at 09:30
  • @CodeGuy they have shown how you can overcome(using clip) the non-smooth edge,with a smooth one(non clip). Well, other wise you can't using a clip. – joey rohan Jan 20 '13 at 09:36
  • @joeyrohan I'm not exactly sure how to adapt this to what I need :/ if you understand their code better than I do, could you try it out? – CodeGuy Jan 20 '13 at 09:39
  • @CodeGuy ok will try after a while :) – joey rohan Jan 20 '13 at 09:40
4

Here was the answer. I adapted the majority of the code from this site. Take a look:

enter image description here

Here's the code:

public void paintComponent(Graphics g) {

        // Create a translucent intermediate image in which we can perform
        // the soft clipping
        GraphicsConfiguration gc = ((Graphics2D) g).getDeviceConfiguration();
        BufferedImage img = gc.createCompatibleImage(getWidth(), getHeight(), Transparency.TRANSLUCENT);
        Graphics2D g2 = img.createGraphics();

        // Clear the image so all pixels have zero alpha
        g2.setComposite(AlphaComposite.Clear);
        g2.fillRect(0, 0, getWidth(), getHeight());

        // Render our clip shape into the image.  Note that we enable
        // antialiasing to achieve the soft clipping effect.  Try
        // commenting out the line that enables antialiasing, and
        // you will see that you end up with the usual hard clipping.
        g2.setComposite(AlphaComposite.Src);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(Color.WHITE);
        g2.fillOval(0, 0, getRadius(), getRadius());

        // Here's the trick... We use SrcAtop, which effectively uses the
        // alpha value as a coverage value for each pixel stored in the
        // destination.  For the areas outside our clip shape, the destination
        // alpha will be zero, so nothing is rendered in those areas.  For
        // the areas inside our clip shape, the destination alpha will be fully
        // opaque, so the full color is rendered.  At the edges, the original
        // antialiasing is carried over to give us the desired soft clipping
        // effect.
        g2.setComposite(AlphaComposite.SrcAtop);
        g2.setColor(lineColor);
        int gap = LINE_GAP;
        AffineTransform at = g2.getTransform();

        g2.setTransform(AffineTransform.getRotateInstance(Math.toRadians(45),getRadius() / 2, getRadius() / 2));

        for (int index = 0; index < 10; index++) {
            int x1 = index*gap-(LINE_THICKNESS/2);
            int y1 = 0;
            int x2 = index*gap+(LINE_THICKNESS/2);
            int y2 = getRadius();
            int width = x2 - x1;
            int height = y2 - y1;

            g2.fillRect(x1, y1, width, height);
        }

        g2.setTransform(at);
        g2.dispose();

        // Copy our intermediate image to the screen
        g.drawImage(img, 0, 0, null);
    }
CodeGuy
  • 28,427
  • 76
  • 200
  • 317
2

Update

OK. Then the idea is to not use clipping at all but instead to make the clipped shape by subtracting areas from each other.

Clipping by subtracting areas

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class SimplePaint02 {

    private static final int LINE_THICKNESS = 4;
    private static final int LINE_GAP = 10;
    private Color lineColor = Color.red;

    public static void main(String[] args) {
        new SimplePaint02();
    }

    public SimplePaint02() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        int radius = 75;

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(radius, radius);
        }

        @Override
        public void paintComponent(Graphics g) {


            Ellipse2D circle = new Ellipse2D.Float(0, 0, radius, radius);
            Area lines = new Area();

            int gap = LINE_GAP;

            for (int index = 0; index < 10; index++) {
                int x1 = index * gap - (LINE_THICKNESS / 2);
                int y1 = 0;
                int x2 = index * gap + (LINE_THICKNESS / 2);
                int y2 = radius;
                int width = x2 - x1;
                int height = y2 - y1;

                Shape lineShape = new Rectangle2D.Double(x1, y1, width, height);
                lines.add(new Area(lineShape));

                //g3d.fillRect(x1, y1, width, height);
                //g2d.drawLine(index * gap, 0, index * gap, getRadius());
            }
            //g2d.setClip(circle);

            Area circleNoLines = new Area(circle);
            circleNoLines.subtract(lines);

            Area linesCutToCircle = new Area(circle);
            linesCutToCircle.subtract(circleNoLines);

            //g2d.setTransform(at);
            BufferedImage buffer = new BufferedImage(radius * 2, radius * 2, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = buffer.createGraphics();

            RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(45), radius / 2, radius / 2));
            g2d.setRenderingHints(rh);
            g2d.setColor(Color.ORANGE);
            g2d.fill(linesCutToCircle);
            g2d.setColor(Color.RED);
            g2d.fill(circleNoLines);

            g2d.dispose();
            g.drawImage(buffer, 0, 0, this);
        }
    }
}

Old code

Part of the problem is that the rendering operations typically do not apply to a Clip, though they will apply to the Shape when it is drawn. I generally solve that by (last) painting the Shape itself. E.G.

SimplePaint02

A 1.5 pixel BasicStroke is used here for the red circle - smoothing the rough edges produced by the Clip.

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;

public class SimplePaint02 {

    private static final int LINE_THICKNESS = 4;
    private static final int LINE_GAP = 10;
    private Color lineColor = Color.red;

    public static void main(String[] args) {
        new SimplePaint02();
    }

    public SimplePaint02() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        int radius = 75;
        
        @Override
        public Dimension getPreferredSize() {
            return new Dimension((int)(1.1*radius), (int)(1.1*radius));
        }

        @Override
        public void paintComponent(Graphics g) {

            BufferedImage buffer = new BufferedImage(radius*2, radius*2, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = buffer.createGraphics();
            
            RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            rh.put(RenderingHints.KEY_DITHERING,RenderingHints.VALUE_DITHER_ENABLE);
            rh.put(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHints(rh);

            Ellipse2D circle = new Ellipse2D.Float(0, 0, radius,radius);
            Shape clip = g2d.getClip();
            g2d.setClip(circle);
            AffineTransform at = g2d.getTransform();

            g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(45),radius / 2, radius / 2));

            int gap = LINE_GAP;

            g2d.setColor(Color.WHITE);
            g2d.fill(circle);

            g2d.setColor(lineColor);
            //g2d.setStroke(new BasicStroke(LINE_THICKNESS));
            for (int index = 0; index < 10; index++) {
                int x1 = index*gap-(LINE_THICKNESS/2);
                int y1 = 0;
                int x2 = index*gap+(LINE_THICKNESS/2);
                int y2 = radius;
                int width = x2 - x1;
                int height = y2 - y1;

                g2d.fillRect(x1, y1, width, height);
                //g2d.drawLine(index * gap, 0, index * gap, getRadius());
            }

            g2d.setTransform(at);
            g2d.setClip(clip);
            g2d.setClip(null);
            g2d.setStroke(new BasicStroke(1.5f));
            g2d.draw(circle);
            g2d.dispose();
            g.drawImage(buffer, 0, 0, this);
        }
    }
}
Community
  • 1
  • 1
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
1

I used drawPolygon method to draw circle by generating array of most of the points on circumference of circle with proposed radius. Code:

           import java.awt.*;
           import java.applet.*;

            /*<applet code="OnlyCircle" width=500 height=500>
                 </applet>*/

          public class OnlyCircle extends Applet{

            public void paint(Graphics g){


              int r=200;//radius
              int x1=250;//center x coordinate
              int y1=250;//center y coordinate
              double x2,y2;
              double a=0;
              double pi=3.14159;
              int count=0; 
              int i=0;
              int f=0;
              int[] x22=new int[628319];
              int[] y22=new int[628319];

             while(a<=2*pi&&i<628319&&f<628319)
                  {
                   double k=Math.cos(a);
                   double l=Math.sin(a);
                     x2=x1+r*k;
                     y2=y1+r*l;
                  x22[i]=(int)x2;
                  y22[f]=(int)y2;
                   i++;
                   f++;
                   a+=0.00001;
                  }
               int length=x22.length;
               g.drawPolygon(x22,y22,length);
                 }
               }
Deepeshkumar
  • 395
  • 3
  • 13
0

You can enable anti-aliasing:

Graphics2D g2 = (Graphics2D) g;
Map<RenderingHints.Key, Object> hints = new HashMap<RenderingHints.Key, Object>();
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 
g2.setRenderingHints(hints);

I also suggest you draw to the Graphics object you get from the paintComponent method rather than creating an intermediate BufferedImage.

Dan D.
  • 32,246
  • 5
  • 63
  • 79
  • 1
    I'm already doing that I believe (see code I posted)...I'm just not doing it by creating a map, that's all. I'm using g2d.setRenderingHints – CodeGuy Jan 20 '13 at 08:25
  • You do it on the BufferedImage Graphics2D object, do it on the Graphics you get in the paintComponent method as well. – Dan D. Jan 20 '13 at 08:28
  • But that won't necessarily change the output, woudld it? – CodeGuy Jan 20 '13 at 08:37