Java在Swing上绘制圆圈和线条

时间:2014-03-22 04:15:07

标签: java swing random drawing

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

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

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);
     }
}

2 个答案:

答案 0 :(得分:3)

首先更改方法,根据其中心和半径绘制一个圆,然后返回一个返回表示圆的Ellipse2D个对象的方法。这将允许我们除了绘制它之外,还可以对形状进行剪裁和其他操作。

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

设置剪辑后,我们使用方法Line2D getVector(Point2D, double, length),其原点位于大圆的中心,随机角度和随机长度(上限以保持小圆圈内的小蓝圈)。可以想象这是一个随机的polar coordinate,以大圆的中心为原点。该向量的终点用于标记小圆的中心。

使用小圆的中心作为起点,我们可以通过使用随机方向角生成两个相反方向的矢量(只是否定一个矢量的长度以使其沿另一个方向前进)。我们使用的长度等于大圆的直径,以确保线总是一直到大圆的边缘(但由于我们的剪辑,不会过去)。

我们只需在我们的蓝色虚线的角度上添加60度和120度,并绘制两条绿线来计算矢量,就像我们对两条蓝色虚线所做的那样,除了我们不需要创建一个否定了长度。我们还可以通过向蓝色虚线的角度添加90度来添加法线向量,以获得良好的度量。

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

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

查看Draw a line at a specific angle in Java以了解我如何计算线条矢量的详细信息。

但足够的话题,这里是代码:

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;
    }

}

答案 1 :(得分:2)

几何学的逻辑比我猜想的要复杂得多,但这就是我认为你所追求的。

enter image description here enter 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);
    }
}