Drawing a nice circle in Java

I use Java Graphics and I keep getting ugly circles.

Here is my java program enter image description here

And here the same thing is done in Matlab enter image description here

I think it is clear that Java alone is not as "beautiful" as Matlab, especially at the edges of the circle. Note that this has nothing to do with resolution ... these images are almost the same size. Also note that I am already setting rendering hints.

It is standalone here with the main function that you can run to test this.

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); } } } 
+6
source share
5 answers

EDIT: See below code for solution. This is noted correctly, because it was Joey Rohan who understood this initially!


I got a smooth edge when I tried the 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 a lot:

There is nothing wrong with the code, but

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

But also got a trick for the clips: Follow This link , you can achieve what you ask.

(Also, I got a smooth edge because I don't use the clip material in my image above)

+5
source

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

enter image description here

Here is 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 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); } 
+4
source

Update

OK Then the idea is not to use cropping at all , but instead to make the cropped shape by subtracting the 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 are usually not applied to the Clip , although they will be applied to the Shape when it is drawn. I usually decide that (last) we draw the Shape itself. For instance.

SimplePaint02

For the red circle, 1.5 pixels are used. BasicStroke - smoothing the rough edges created by 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); } } } 
+2
source

I used the drawPolygon method to draw a circle, creating an array of most points on the circumference of a circle with a suggested radius. The 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); } } 
+1
source

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 that you draw the Graphics object that you get from the paintComponent method, rather than creating an intermediate BufferedImage .

0
source

All Articles