使用 Java 的 Arc2D 的起始角度和范围值给出了意想不到的结果

问题描述

我正在尝试了解 Java 的 Arc2D 的使用。不知道从椭圆(不是圆)创建圆弧时如何计算起始角度和范围。我想要一个 ry = 2 * rx 的椭圆弧,其中起始角为 45 度,范围为 90 度。但相反,我必须发送 ~26 度的起始角度和 ~127 度的范围才能得到我的期望。包含的代码生成一个具有 3 个弧的图像:绿色是一个圆弧,其行为符合预期,红色是具有预期参数但结果意外的椭圆弧,蓝色是具有意外参数但预期结果的弧。我在这里误解了什么吗?此外,在 0 度的起始角和 90 度的范围内,结果符合预期???谢谢。

package example;

import java.awt.Basicstroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Arc2D;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

public class ExampleArc2D {
    public static void main(String[] args) {
        try {
            int width = 800;
            int height = 800;
            
            BufferedImage bi = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
            Graphics2D gfx2D = bi.createGraphics();
            gfx2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
            gfx2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            
            gfx2D.setFont(new Font(Font.MONOSPACED,Font.BOLD,16));
            FontMetrics fm = gfx2D.getFontMetrics();
            gfx2D.setColor(Color.BLACK);
            gfx2D.fillRect(0,bi.getWidth(),bi.getHeight());
            gfx2D.setstroke(new Basicstroke(2f));
            
            int x = 5;
            int y = 0;
            
            int centerX = width / 2;
            int centerY = height / 2 + 200;
            
            int circleRadius = 200;
            int ellipseRadiusX = circleRadius;
            int ellipseRadiusY = 2 * circleRadius;

//            gfx2D.setColor(new Color(255,255,50));
//            gfx2D.draw(new Ellipse2D.Double(centerX - circleRadius,centerY - circleRadius,2 * circleRadius,2 * circleRadius));
//            gfx2D.draw(new Ellipse2D.Double(centerX - ellipseRadiusY,centerY - ellipseRadiusY,2 * ellipseRadiusY,2 * ellipseRadiusY));
//            gfx2D.draw(new Ellipse2D.Double(centerX - ellipseRadiusX,2 * ellipseRadiusX,2 * ellipseRadiusY));
            
            double startAngle = 45.0;
            double extent = 90.0;
            Arc2D circle = new Arc2D.Double(centerX - circleRadius,startAngle,extent,Arc2D.PIE);
            gfx2D.setColor(Color.GREEN);
            gfx2D.draw(circle);
            y += fm.getAscent();
            gfx2D.drawString("ARC FROM CIRCLE WITH EXPECTED PARMS AND RESULT:",x,y);
            y += fm.getAscent();
            gfx2D.drawString(String.format("start angle %.1f° and extent %.1f°",extent),y);
            
            startAngle = 45.0;
            extent = 90.0;
            Arc2D unexpectedEllipse = new Arc2D.Double(centerX - ellipseRadiusX,Arc2D.PIE);
            gfx2D.setColor(Color.RED);
            gfx2D.draw(unexpectedEllipse);
            y += fm.getAscent();
            gfx2D.drawString("UNEXPECTED ARC FROM ELLIPSE WITH EXPECTED PARMS AND UNEXPECTED RESULT:",y);
            
            startAngle = 26.4;
            extent = 127.2;
            Arc2D expectedEllipse = new Arc2D.Double(centerX - ellipseRadiusX,Arc2D.PIE);
            gfx2D.setColor(Color.BLUE);
            gfx2D.draw(expectedEllipse);
            y += fm.getAscent();
            gfx2D.drawString("EXPECTED ARC FROM ELLIPSE WITH UNEXPECTED PARMS AND EXPECTED RESULT:",y);
            y += fm.getAscent();
            gfx2D.drawString("even though it is clear that the start angle is at 45° and the extent is 90°???",y);
            
            gfx2D.setColor(Color.YELLOW);
            gfx2D.drawRect(centerX - ellipseRadiusX,ellipseRadiusX * 2,ellipseRadiusY * 2);
            gfx2D.setColor(Color.ORANGE);
            gfx2D.drawLine(centerX,centerY,centerX + ellipseRadiusX,centerY - ellipseRadiusY);
            x = centerX + ellipseRadiusX + 5;
            y = centerY - ellipseRadiusY;
            gfx2D.drawString("this is 45° as far",y);
            y += fm.getAscent();
            gfx2D.drawString("as the ellipse is",y);
            y += fm.getAscent();
            gfx2D.drawString("concerned!",y);

            gfx2D.dispose();
            
            JOptionPane.showMessageDialog(null,new JLabel(new ImageIcon(bi)));
//            ImageIO.write(bi,"png",new File("examplearc2d.png"));
        }
        catch(Throwable t) {
            t.printstacktrace();
        }
    }
}

更新: 找到了一种获取角度的方法,但它太笨重,因此采用了另一种方法。我使用了一个裁剪区域来解决这个问题。我解决了我的问题,但并没有真正回答这个问题。不会再追究了。所以基本上我画了椭圆,但应用了一个裁剪区域,所以只画了弧。

package example;

import java.awt.Basicstroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.changelistener;

public class ExampleArc2D3
extends JFrame {
    private Canvas canvas;
    private JSlider ryrxSld;
    private JSlider startAngleSld;
    private JSlider extentSld;
    private JSlider angleSld;
    
    public ExampleArc2D3() {
        super("Example Arc2D");
        setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
    
        Container cp = getContentPane();
        cp.add(canvas = new Canvas(),BorderLayout.CENTER);
        
        JPanel southPanel = new JPanel(new BorderLayout());
        southPanel.setBorder(BorderFactory.createEmptyBorder(3,3,3));
        cp.add(southPanel,BorderLayout.soUTH);
        
        JPanel labelsPanel = new JPanel(new GridLayout(0,1,0));
        southPanel.add(labelsPanel,BorderLayout.WEST);
        JPanel fieldsPanel = new JPanel(new GridLayout(0,0));
        southPanel.add(fieldsPanel,BorderLayout.CENTER);
        
        labelsPanel.add(new JLabel("ry/rx: "));
        fieldsPanel.add(ryrxSld = new JSlider(10,25,20));
        labelsPanel.add(new JLabel("Start Angle: "));
        fieldsPanel.add(startAngleSld = new JSlider(0,3600,0));
        labelsPanel.add(new JLabel("Extent: "));
        fieldsPanel.add(extentSld = new JSlider(0,300));
        labelsPanel.add(new JLabel("Protractor: "));
        fieldsPanel.add(angleSld = new JSlider(0,0));
        
        ChangeHandler ch = new ChangeHandler();
        ryrxSld.addchangelistener(ch);
        startAngleSld.addchangelistener(ch);
        extentSld.addchangelistener(ch);
        angleSld.addchangelistener(ch);
        
        setSize(1000,1020);
        setVisible(true);
    }
    
    private class Canvas
    extends JComponent {
        public void refresh() {
            invalidate();
            repaint();
        }
        
        @Override
        public void paintComponent(Graphics gfx) {
            super.paintComponent(gfx);
            Graphics2D gfx2D = (Graphics2D) gfx;
            Dimension size = getSize();
            try {
                int width = size.width;
                int height = size.height;
                
                gfx2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
                gfx2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,RenderingHints.VALUE_FRACTIONALMETRICS_ON);
                
                gfx2D.setFont(new Font(Font.MONOSPACED,16));
                FontMetrics fm = gfx2D.getFontMetrics();
                gfx2D.setColor(Color.BLACK);
                gfx2D.fillRect(0,width,height);

                double startAngle = startAngleSld.getValue() / 10.0; 
                double extent = extentSld.getValue() / 10.0;
                
                int x = 5;
                int y = 0;
                gfx2D.setColor(Color.WHITE);
                y += fm.getAscent();
                gfx2D.drawString(String.format("ry/rx: %.1f",ryrxSld.getValue() / 10.0),y);
                y += fm.getAscent();
                gfx2D.drawString(String.format("start angle %.1f° and extent %.1f°",y);
                
                int centerX = width / 2;
                int centerY = 460;
                
                double ellipseRadiusX = 200;
                double ellipseRadiusY = ryrxSld.getValue() * ellipseRadiusX / 10.0;

                gfx2D.setColor(new Color(255,60));
                gfx2D.setstroke(new Basicstroke(1f));
                gfx2D.draw(new Ellipse2D.Double(centerX - ellipseRadiusX,centerY - ellipseRadiusX,2 * ellipseRadiusX));
                gfx2D.draw(new Ellipse2D.Double(centerX - ellipseRadiusY,2 * ellipseRadiusY));
                gfx2D.draw(new Ellipse2D.Double(centerX - ellipseRadiusX,2 * ellipseRadiusY));

                gfx2D.setstroke(new Basicstroke(2f));
                
                gfx2D.setstroke(new Basicstroke(1f));
                gfx2D.setFont(new Font(Font.MONOSPACED,Font.PLAIN,10));
                fm = gfx2D.getFontMetrics();
                
                // clip area
                Shape clipArea = buildClipArea(centerX,ellipseRadiusX,ellipseRadiusY,extent);
                gfx2D.setColor(new Color(255,120));
                gfx2D.draw(clipArea);
                
                Shape oldClip = gfx2D.getClip();
                gfx2D.setClip(clipArea);
                gfx2D.setColor(Color.BLUE);
                gfx2D.setstroke(new Basicstroke(2f));
                gfx2D.draw(new Ellipse2D.Double(centerX - ellipseRadiusX,2 * ellipseRadiusY));
                gfx2D.setClip(oldClip);
                
                double angle = angleSld.getValue() / 10.0;
                gfx2D.setColor(Color.CYAN);
                double len = 800.0;
                gfx2D.draw(new Line2D.Double(centerX,centerX + len * Math.cos(Math.toradians(angle)),centerY - len * Math.sin(Math.toradians(angle))));
                gfx2D.drawString(String.format("%.1f°",angle),centerX + 5,centerY + fm.getAscent());
            }
            catch(Throwable t) {
                t.printstacktrace();
            }
        }
        
        private Shape buildClipArea(double centerX,double centerY,double ellipseRadiusX,double ellipseRadiusY,double startAngle,double extent) {
            double radius = Math.max(ellipseRadiusX,ellipseRadiusY);
            Arc2D arc = new Arc2D.Double(centerX - radius,centerY - radius,2 * radius,Arc2D.PIE);
            AffineTransform at = new AffineTransform();
            at.rotate(Math.toradians(-startAngle),centerX,centerY);
            return at.createTransformedShape(arc);
        }
    }

    private class ChangeHandler
    implements changelistener {
        @Override
        public void stateChanged(ChangeEvent e) {
            canvas.refresh();
        }
    }
    
    public static void main(String[] args) {
        Runnable runner = new Runnable() {
            @Override
            public void run() {
                new ExampleArc2D3();
            }
        };
        SwingUtilities.invokelater(runner);
    }
}

解决方法

Arc2D 类的 API 声明:

角度是相对于非方形框架矩形指定的,这样 45 度始终落在从椭圆中心到框架矩形右上角的直线上

不太确定这意味着什么或如何提供有关如何计算适当角度以实现所需结果的见解

但是,可以通过在类的底部添加以下代码来演示:

gfx2D.setColor( Color.YELLOW );
gfx2D.drawRect(centerX - ellipseRadiusX,centerY - ellipseRadiusY,ellipseRadiusX * 2,ellipseRadiusY * 2);

gfx2D.setColor( Color.ORANGE );
gfx2D.drawLine(centerX,centerY,centerX + ellipseRadiusX,centerY - ellipseRadiusY);