Java 在 Swing 上绘制圆和线

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22573385/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 16:34:44  来源:igfitidea点击:

Java draw circle and lines on Swing

javaswingrandomdrawing

提问by Tina J

I'm trying to draw a circle with a random center inside a big bigger circular surface. (I'm actually trying to simulate a human and his eyesight inside a room!) I need to draw a random line (call it line1) passing through its center which will intersect with the surface. line1 does not necessarily pass the center of circular surface. I also need to draw two lines forming 60 degree, facing on one side of line1. Can anyone help me with that?

我正在尝试在更大的圆形表面内绘制一个具有随机中心的圆。(我实际上是在尝试模拟房间内的人类和他的视力!)我需要绘制一条随机线(称为 line1),通过其中心与表面相交。line1 不一定通过圆形表面的中心。我还需要画两条成 60 度的线,面向 line1 的一侧。任何人都可以帮助我吗?

I created an example of what I need to draw.

我创建了一个我需要绘制的示例。

enter image description here

在此处输入图片说明

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;

import javax.swing.JFrame;

public class ShapeTest extends JFrame{
    int width=500;
    int height=500;

     public ShapeTest(){
          setSize(width,height);
          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          setResizable(false);
          setLocationRelativeTo(null);
          setVisible(true);
     }

     public static void main(String a[]){
         new ShapeTest();
     }

     public void paint(Graphics g){
         // Circular Surface
         drawCircleByCenter(g, width/2, height/2, width/2);
         Random r = new Random();
         Point center = new Point();
         center.x=r.nextInt(width/2);
         center.y=r.nextInt(width/2);
         drawCircleByCenter(g, center.x, center.y, width/15);
     }

     void drawCircleByCenter(Graphics g, int x, int y, int radius){
         //g.setColor(Color.LIGHT_GRAY);
         g.drawOval(x-radius, y-radius, 2*radius, 2*radius);
     }
}

采纳答案by Sam

Start by changing your method to draw a circle based on its center and radius to a method which returns a Ellipse2Dobject representing the circle. This will allow us to do some clipping and other things with the shape besides just draw it.

首先将基于圆心和半径绘制圆的方法更改为返回Ellipse2D表示圆的对象的方法。这将允许我们对形状进行一些剪辑和其他操作,而不仅仅是绘制它。

Setting the clip to be the shape of your large circle prevents stray marks from being made where you don't want them (think "color inside the lines"). This is important because when we draw the circles and lines inside the big circle, some of them will be too big and would otherwise mark outside the bounds of the big circle.

将剪辑设置为大圆圈的形状可防止在您不想要的地方留下杂散标记(想想“线条内的颜色”)。这很重要,因为当我们在大圆圈内绘制圆圈和线条时,其中一些会太大,否则会标记在大圆圈的边界之外。

Once we set the clip, we use the method Line2D getVector(Point2D, double, length)with an origin at the center of the large circle, a random angle and a random length (capped to keep the small blue circle inside the big circle). Think of this a random polar coordinatewith the center of the large circle as the origin. The end point of this vector is used to mark the center of the small circle.

一旦我们设置了剪辑,我们就使用Line2D getVector(Point2D, double, length)原点在大圆圈中心、随机角度和随机长度的方法(加盖以将小蓝色圆圈保持在大圆圈内)。将此视为以大圆的中心为原点的随机极坐标。这个向量的端点用于标记小圆的中心。

Using the center of the small circle as a starting point, we can generate two vectors in opposite directions (just negate the length of one to get it going the other direction) by using a random direction angle. We use a length equal to the diameter of the big circle to make certain that the lines will always go all the way up to the edge of the big circle (but not past, thanks to our clip).

使用小圆的中心作为起点,我们可以通过使用随机方向角生成两个相反方向的向量(只需取反一个的长度即可使其向另一个方向移动)。我们使用等于大圆直径的长度来确保线条始终一直延伸到大圆的边缘(但不会过去,感谢我们的剪辑)。

We simply add 60 and 120 degrees to the angle of our blue dashed line and draw two green lines calculating the vectors the same way we did for the two blue dashed lines, except we don't need to create ones with negated lengths. We can also add a normal vector in for good measure simply by adding 90 degrees to the angle of the blue dashed line.

我们简单地将蓝色虚线的角度增加 60 度和 120 度,并绘制两条绿色线,以与两条蓝色虚线相同的方式计算向量,除了我们不需要创建具有否定长度的线。我们还可以通过将蓝色虚线的角度添加 90 度来添加一个法向量以进行良好的测量。

Lastly, we pick some random polar coordinates (just like we did for the small blue circle) to represent some people, and using the intersection of the people with the areas created by the various lines, we can see where they are at and draw them up with color coded values.

最后,我们选择一些随机的极坐标(就像我们为蓝色小圆圈所做的那样)来代表一些人,并利用这些人与各条线创建的区域的交集,我们可以看到他们所在的位置并绘制他们颜色编码值。

Now that we have all the people, we eliminate the clip and draw the big circle and voila!

现在我们拥有了所有人,我们消除了剪辑并绘制了大圆圈,瞧!

Check out Draw a line at a specific angle in Javafor details on how I calculated the vectors for the lines.

查看在 Java 中特定角度绘制一条线,了解有关我如何计算线向量的详细信息。

But enough talk, here's the code:

话不多说,代码如下:

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.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class ShapeTest extends JFrame {

    private static final long serialVersionUID = 1L;
    private int width = 500;
    private int height = 500;
    private int padding = 50;
    private BufferedImage graphicsContext;
    private JPanel contentPanel = new JPanel();
    private JLabel contextRender;
    private Stroke dashedStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 2f, new float[] {3f, 3f}, 0f);
    private Stroke solidStroke = new BasicStroke(3.0f);
    private RenderingHints antialiasing;
    private Random random = new Random();

    public static void main(String[] args) {
        //you should always use the SwingUtilities.invodeLater() method
        //to perform actions on swing elements to make certain everything
        //is happening on the correct swing thread
        Runnable swingStarter = new Runnable()
        {
            @Override
            public void run(){
                new ShapeTest();
            }
        };

        SwingUtilities.invokeLater(swingStarter);
    }

    public ShapeTest(){
        antialiasing = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphicsContext = new BufferedImage(width + (2 * padding), width + (2 * padding), BufferedImage.TYPE_INT_RGB);
        contextRender = new JLabel(new ImageIcon(graphicsContext));

        contentPanel.add(contextRender);
        contentPanel.setSize(width + padding * 2, height + padding * 2);

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);
        this.setContentPane(contentPanel);
        //take advantage of auto-sizing the window based on the size of its contents
        this.pack();
        this.setLocationRelativeTo(null);
        this.paint();
        setVisible(true);
    }

    public void paint() {

        Graphics2D g2d = graphicsContext.createGraphics();
        g2d.setRenderingHints(antialiasing);

        //Set up the font to print on the circles
        Font font = g2d.getFont();
        font = font.deriveFont(Font.BOLD, 14f);
        g2d.setFont(font);

        FontMetrics fontMetrics = g2d.getFontMetrics();

        //clear the background
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, graphicsContext.getWidth(), graphicsContext.getHeight());

        //set up the large circle
        Point2D largeCircleCenter = new Point2D.Double((double)width / 2 + padding, (double)height / 2 + padding);
        double largeCircleRadius = (double)width / 2;
        Ellipse2D largeCircle = getCircleByCenter(largeCircleCenter, largeCircleRadius);

        //here we build the small circle
        Point2D smallCircleCenter = new Point2D.Double();
        double smallCircleRadius = 15;
        //we need to make certain it is confined inside the larger circle
        //so we choose the following values carefully

        //we want to go a random direction from the circle, so chose an
        //angle randomly in any direction
        double smallCenterVectorAngle = random.nextDouble() * 360.0d;
        //and we want to be a random distance from the center of the large circle, but
        //we limit the distance based on the radius of the small circle to prevent it
        //from appearing outside the large circle
        double smallCenterVectorLength = random.nextDouble() * (largeCircleRadius - smallCircleRadius);
        Line2D vectorToSmallCenter = getVector(largeCircleCenter, smallCenterVectorAngle, smallCenterVectorLength);
        //the resulting end point of the vector is a random distance from the center of the large circle
        //in a random direction, and guaranteed to not place the small circle outside the large
        smallCircleCenter.setLocation(vectorToSmallCenter.getP2());
        Ellipse2D smallCircle = getCircleByCenter(smallCircleCenter, smallCircleRadius);

        //before we draw any of the circles or lines, set the clip to the large circle
        //to prevent drawing outside our boundaries
        g2d.setClip(largeCircle);



        //chose a random angle for the line through the center of the small circle
        double angle = random.nextDouble() * 360.0d;
        //we create two lines that start at the center and go out at the angle in
        //opposite directions. We use 2*largeCircleRadius to make certain they
        //will be large enough to fill the circle, and the clip we set prevent stray
        //marks outside the big circle
        Line2D centerLine1 = getVector(smallCircleCenter, angle, largeCircleRadius * 2);
        Line2D centerLine2 = getVector(smallCircleCenter, angle, -largeCircleRadius * 2);

        //now we just add 20 and 120 to our angle for the center-line, start at the center
        //and again, use largeCircleRadius*2 to make certain the lines are big enough
        Line2D sightVector1 = getVector(smallCircleCenter, angle + 60, largeCircleRadius * 2);
        Line2D sightVector2 = getVector(smallCircleCenter, angle + 120, largeCircleRadius * 2);



        Path2D visible = new Path2D.Double();
        visible.moveTo(sightVector1.getX2(), sightVector1.getY2());
        visible.lineTo(smallCircleCenter.getX(), smallCircleCenter.getY());
        visible.lineTo(sightVector2.getX2(), sightVector2.getY2());
        visible.closePath();

        Path2D greenSide = new Path2D.Double();
        greenSide.moveTo(centerLine1.getX2(), centerLine1.getY2());
        greenSide.lineTo(smallCircleCenter.getX(), smallCircleCenter.getY());
        greenSide.lineTo(centerLine2.getX2(), centerLine2.getY2());
        greenSide.lineTo(sightVector1.getX2(), sightVector1.getY2());
        greenSide.closePath();

        int personCount = 5;
        Area visibleArea = new Area(visible);
        visibleArea.intersect(new Area(largeCircle));

        Area greenSideArea = new Area(greenSide);
        greenSideArea.intersect(new Area(largeCircle));

        //we create a list of the people in the circle to
        //prevent overlap
        ArrayList<Shape> people = new ArrayList<Shape>();
        people.add(smallCircle);

        int i = 0;
        personLoop: while (i < personCount){
            double personCenterVectorAngle = random.nextDouble() * 360.0d;
            double personCenterVectorLength = random.nextDouble() * (largeCircleRadius - smallCircleRadius);
            Line2D vectorToPersonCenter = getVector(largeCircleCenter, personCenterVectorAngle, personCenterVectorLength);
            Point2D personCircleCenter = vectorToPersonCenter.getP2();
            Ellipse2D personCircle = getCircleByCenter(personCircleCenter, smallCircleRadius);

            //this little loop lets us skip a person if they have overlap
            //with another person, since people don't generally overlap
            Area personArea = new Area(personCircle);
            for (Shape person : people)
            {
                Area overlapArea = new Area(person);
                overlapArea.intersect(personArea);
                //this means that we have found a conflicting
                //person, so should skip them
                if (!overlapArea.isEmpty()){
                    continue personLoop;
                }
            }
            people.add(personCircle);

            personArea.intersect(visibleArea);

            Area greenSideAreaTest = new Area(personCircle);
            greenSideAreaTest.intersect(greenSideArea);
            if (personArea.isEmpty()){
                if (greenSideAreaTest.isEmpty()){
                    g2d.setColor(Color.orange);
                    System.out.println("Person " + i + " is behind the blue line");
                }
                else {
                    System.out.println("Person " + i + " is in front of the blue line");
                    g2d.setColor(Color.cyan);
                }
            }
            else
            {
                System.out.println("Person " + i + " is between the green lines");
                g2d.setColor(Color.magenta);
            }

            //alternatively to circles intersecting the area of interest, we can check whether the center
            //is in the area of interest which may make more intuitive sense visually
//          if (visibleArea.contains(personCircleCenter)){
//              System.out.println("Person " + i + " is between the green lines");
//              g2d.setColor(Color.magenta);
//          }
//          else {
//              if (greenSideArea.contains(personCircleCenter)) {
//                  System.out.println("Person " + i + " is in front of the blue line");
//                  g2d.setColor(Color.cyan);
//              }
//              else{
//                  g2d.setColor(Color.orange);
//                  System.out.println("Person " + i + " is behind the blue line");
//              }
//          }

            g2d.fill(personCircle);
            g2d.setColor(Color.black);
            String itemString = "" + i;
            Rectangle2D itemStringBounds = fontMetrics.getStringBounds(itemString, g2d);
            double textX = personCircleCenter.getX() - (itemStringBounds.getWidth() / 2);
            double textY = personCircleCenter.getY() + (itemStringBounds.getHeight()/ 2);
            g2d.drawString("" + i, (float)textX, (float)textY);
            i++;
        }



        //fill the small circle with blue
        g2d.setColor(Color.BLUE);
        g2d.fill(smallCircle);

        //draw the two center lines lines
        g2d.setStroke(dashedStroke);
        g2d.draw(centerLine1);
        g2d.draw(centerLine2);

        //create and draw the black offset vector
        Line2D normalVector = getVector(smallCircleCenter, angle + 90, largeCircleRadius * 2);
        g2d.setColor(Color.black);
        g2d.draw(normalVector);

        //draw the offset vectors
        g2d.setColor(new Color(0, 200, 0));
        g2d.draw(sightVector1);
        g2d.draw(sightVector2);


        //we save the big circle for last, to cover up any stray marks under the stroke
        //of its perimeter. We also set the clip back to null to prevent the large circle
        //itselft from accidentally getting clipped
        g2d.setClip(null);
        g2d.setStroke(solidStroke);
        g2d.setColor(Color.BLACK);
        g2d.draw(largeCircle);

        g2d.dispose();
        //force the container for the context to re-paint itself
        contextRender.repaint();

    }

    private static Line2D getVector(Point2D start, double degrees, double length){
        //we just multiply the unit vector in the direction we want by the length
        //we want to get a vector of correct direction and magnitute
        double endX = start.getX() + (length * Math.sin(Math.PI * degrees/ 180.0d));
        double endY = start.getY() + (length * Math.cos(Math.PI * degrees/ 180.0d));
        Point2D end = new Point2D.Double(endX, endY);
        Line2D vector = new Line2D.Double(start, end);
        return vector;
    }

    private static Ellipse2D getCircleByCenter(Point2D center, double radius)
    {
        Ellipse2D.Double myCircle = new Ellipse2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius);
        return myCircle;
    }

}

回答by Andrew Thompson

The logic of the geometry turned out to be more tricky than I'd presumed, but this is what I think you are after.

结果证明几何的逻辑比我想象的更棘手,但这就是我认为你所追求的。

enter image description hereenter image description here

在此处输入图片说明在此处输入图片说明

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.*;

class HumanEyesightLines {

    int rad = 150;
    int radSmall = 15;
    int pad = 10;
    JPanel gui = new JPanel(new BorderLayout());
    BufferedImage img = new BufferedImage(
            2 * (rad + pad),
            2 * (rad + pad),
            BufferedImage.TYPE_INT_RGB);
    Timer timer;
    JLabel imgDisplay;
    Random rnd = new Random();
    RenderingHints rh = new RenderingHints(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

    HumanEyesightLines() {
        imgDisplay = new JLabel(new ImageIcon(img));
        gui.add(imgDisplay);
        File f = new File(System.getProperty("user.home"));
        final File f0 = new File("HumanEyesiteLines");
        f0.mkdirs();
        try {
            Desktop.getDesktop().open(f0);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        ActionListener animationListener = new ActionListener() {

            int ii = 0;

            @Override
            public void actionPerformed(ActionEvent e) {
                paintImage();
                ii++;
                if (ii < 100) {
                    System.out.println(ii);
                    File f1 = new File(f0, "eg" + ii + ".png");
                    try {
                        ImageIO.write(img, "png", f1);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        };
        timer = new Timer(100, animationListener);
        paintImage();
    }
    float[] dash = {3f, 3f};
    float phase = 0f;

    private final void paintImage() {
        Graphics2D g = img.createGraphics();
        g.setRenderingHints(rh);
        g.setStroke(new BasicStroke(2f));

        // fill the BG
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, 2 * (rad + pad), 2 * (rad + pad));

        // draw the big circle
        Point center = new Point(rad + pad, rad + pad);
        Shape bigCircle = new Ellipse2D.Double(pad, pad, 2 * rad, 2 * rad);
        g.setColor(Color.MAGENTA.darker());
        g.fill(bigCircle);

        // set the clip to that of the big circle
        g.setClip(bigCircle);

        // draw the small circle
        int xOff = rnd.nextInt(rad) - rad / 2;
        int yOff = rnd.nextInt(rad) - rad / 2;
        int x = center.x - xOff;
        int y = center.y - yOff;
        Shape smallCircle = new Ellipse2D.Double(
                x - radSmall, y - radSmall,
                2 * radSmall, 2 * radSmall);
        g.setColor(Color.YELLOW);
        g.fill(smallCircle);
        g.setColor(Color.ORANGE);
        g.draw(smallCircle);

        g.setStroke(new BasicStroke(
                1.5f,
                BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_ROUND,
                2f,
                dash,
                phase));

        // I don't know what the rule is for where the blue line goes, so
        // will use the top left corner of the image as a 2nd anchor point.
        int x0 = 0;
        int y0 = 0;
        double grad = (double) (y - y0) / (double) (x - x0);

        // now calculate the RHS point from y = mx + b 
        // where b = 0 and m is the gradient
        int x1 = 2 * (pad + rad);
        int y1 = (int) (grad * x1);
        Line2D.Double line1 = new Line2D.Double(x0, y0, x1, y1);
        g.setColor(Color.BLUE);
        g.draw(line1);

        //find the perpendicular gradient.
        double perpGrad = -1d / grad;
        double perpTheta = Math.atan(perpGrad);
        // angle from perp
        double diffTheta = Math.PI / 6d;

        g.setColor(Color.GREEN);
        double viewLine1Theta = perpTheta + diffTheta;
        Line2D.Double viewLine1 = getLine(x, y, viewLine1Theta);
        double viewLine2Theta = perpTheta - diffTheta;
        Line2D.Double viewLine2 = getLine(x, y, viewLine2Theta);
        g.draw(viewLine1);
        g.draw(viewLine2);

        g.setColor(Color.BLACK);
        Line2D.Double viewPerp = getLine(x, y, perpTheta);
        g.draw(viewPerp);

        g.setColor(Color.RED);
        g.draw(bigCircle);

        g.dispose();
        imgDisplay.repaint();
    }

    /**
     * Returns a Line2D starting at the point x1,y1 at angle theta.
     */
    private final Line2D.Double getLine(double x1, double y1, double theta) {
        double m;
        double b;
        double x2;
        double y2;
        if (theta < (-Math.PI / 2d)) {
            System.out.println("CHANGE IT! " + theta);
            m = Math.tan(theta);
            b = y1 - (m * x1);
            x2 = 0;
            y2 = (m * x2) + b;
        } else {
            m = Math.tan(theta);
            b = y1 - (m * x1);
            x2 = 2 * (rad + pad);
            y2 = (m * x2) + b;
        }
        /*
         * System.out.println("Perp theta: " + theta); System.out.println("Line
         * grad: " + m); System.out.println("Line off: " + b);
         * System.out.println("x1,y1: " + x1 + "," + y1);
         * System.out.println("x2,y2: " + x2 + "," + y2);
         *
         */

        return new Line2D.Double(x1, y1, x2, y2);
    }

    public JComponent getGui() {
        return gui;
    }

    public void start() {
        timer.start();
    }

    public void stop() {
        timer.stop();
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                HumanEyesightLines hel = new HumanEyesightLines();

                hel.start();
                JOptionPane.showMessageDialog(null, hel.getGui());
                hel.stop();
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency
        SwingUtilities.invokeLater(r);
    }
}