Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Graphics drawArc with high precision

I'm trying to draw a high precision arc in Java using AWT/Swing. The Graphics class offers the "drawArc" method to do this and in most cases this is fine. However, when the arc has a large radius (I.e. The "width" and "height" parameters are large) then the arc will not be drawn precisely. This is because the "startAngle" and "arcAngle" parameters are given as integer degrees. I'm forced to convert an angle (given in radians) to degrees and then rounding it. The loss of precision leads to the arc falling short of its actual end point (if you round down) or go further than it should (if you round up).

I'm looking for an alternative method of drawing arcs. Preferable this should work in the same way but accept floating point angles as parameters.

If such a function does not exist (I've not been able to find one) I would like to write it myself, but I have no idea how to go about this as the Graphics class already offers the lowest level drawing methods I can find. The only idea I had is to approximate the arc with a sequence of straight lines but to get a smooth arc you would need a lot of lines and this seems dreadfully inefficient.

like image 435
Richard Avatar asked Feb 02 '13 20:02

Richard


2 Answers

If you are desiring to use double values for your angle parameters, why not use an Arc2D object such as an Arc2D.Double? All the parameters that you mention are of double type.

Also, are you properly setting your Graphics2D object's RenderingHints to allow for anti-aliasing?

e.g.,

import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.Arc2D;

import javax.swing.*;

@SuppressWarnings("serial")
public class TestArc2D extends JPanel {
   private static final int PREF_W = 1000;
   private static final int PREF_H = 400;
   private static final Stroke STROKE = new BasicStroke(5f);
   private static final Color ARC_COLOR = Color.red;
   private Arc2D arc;

   public TestArc2D() {
      addComponentListener(new ComponentAdapter() {
         @Override
         public void componentResized(ComponentEvent e) {
            double x = 10;
            double y = x;
            double w = getWidth() - 2 * x;
            double h = 2 * getHeight() - 2 * y - 50;
            double start = 0;
            double extent = 180.0;
            arc = new Arc2D.Double(x, y, w, h, start , extent, Arc2D.OPEN);
         }
      });
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setStroke(STROKE);
      g2.setColor(ARC_COLOR);
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
      if (arc != null) {
         g2.draw(arc);
      }
      g2.dispose();
   }

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

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

      JFrame frame = new JFrame("TestArc2D");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}
like image 95
Hovercraft Full Of Eels Avatar answered Nov 07 '22 11:11

Hovercraft Full Of Eels


Using Graphics2D, you can use the rotate(theta,x,y) function to precisely draw one end of your arc:

g2.rotate(-1*thetaStartRadians,xc,yc);
g2.drawArc(x1,y1,width,height,0, thetaLengthDegrees-1);
g2.rotate(thetaStartRadians,xc,yc);

Then you just need to precisely draw the last little bit on the other side:

g2.rotate(-1*thetaLengthRadians,xc,yc);
g2.drawArc(x1,y1,width,height,0,-2));
g2.rotate(thetaLengthRadians,xc,yc);

And you'll have one arc drawn to precise theta values. It's probably not the most efficient way to do it, machine-wise, because of the bit of redraw overlap; but it's the simplest way to do it that I can think of.

like image 33
AnDrew the Awesome Avatar answered Nov 07 '22 10:11

AnDrew the Awesome