原文: https://zetcode.com/gfx/java2d/basicdrawing/

在 Java 2D 教程的这一部分中,我们进行一些基本绘制。

最简单的图形原语就是点。 它是窗口上的一个点。 有一个Point类用于表示坐标空间中的一个点,但是没有绘制点的方法。 为了绘制一个点,我们使用了drawLine()方法,其中为该方法的两个参数都提供了一个点。

PointsEx.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import java.awt.event.ActionEvent;
  7. import java.awt.event.ActionListener;
  8. import java.awt.event.WindowAdapter;
  9. import java.awt.event.WindowEvent;
  10. import java.util.Random;
  11. import javax.swing.JFrame;
  12. import javax.swing.JPanel;
  13. import javax.swing.Timer;
  14. class Surface extends JPanel implements ActionListener {
  15. private final int DELAY = 150;
  16. private Timer timer;
  17. public Surface() {
  18. initTimer();
  19. }
  20. private void initTimer() {
  21. timer = new Timer(DELAY, this);
  22. timer.start();
  23. }
  24. public Timer getTimer() {
  25. return timer;
  26. }
  27. private void doDrawing(Graphics g) {
  28. Graphics2D g2d = (Graphics2D) g;
  29. g2d.setPaint(Color.blue);
  30. int w = getWidth();
  31. int h = getHeight();
  32. Random r = new Random();
  33. for (int i = 0; i < 2000; i++) {
  34. int x = Math.abs(r.nextInt()) % w;
  35. int y = Math.abs(r.nextInt()) % h;
  36. g2d.drawLine(x, y, x, y);
  37. }
  38. }
  39. @Override
  40. public void paintComponent(Graphics g) {
  41. super.paintComponent(g);
  42. doDrawing(g);
  43. }
  44. @Override
  45. public void actionPerformed(ActionEvent e) {
  46. repaint();
  47. }
  48. }
  49. public class PointsEx extends JFrame {
  50. public PointsEx() {
  51. initUI();
  52. }
  53. private void initUI() {
  54. final Surface surface = new Surface();
  55. add(surface);
  56. addWindowListener(new WindowAdapter() {
  57. @Override
  58. public void windowClosing(WindowEvent e) {
  59. Timer timer = surface.getTimer();
  60. timer.stop();
  61. }
  62. });
  63. setTitle("Points");
  64. setSize(350, 250);
  65. setLocationRelativeTo(null);
  66. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  67. }
  68. public static void main(String[] args) {
  69. EventQueue.invokeLater(new Runnable() {
  70. @Override
  71. public void run() {
  72. PointsEx ex = new PointsEx();
  73. ex.setVisible(true);
  74. }
  75. });
  76. }
  77. }

该示例在窗口上随机绘制 2000 个点。 计时器用于绘制循环中的点。

  1. private void initTimer() {
  2. timer = new Timer(DELAY, this);
  3. timer.start();
  4. }

javax.swing.Timer用于创建动画。 它以指定的时间间隔触发ActionEvents

  1. g2d.setPaint(Color.blue);

这些点被涂成蓝色。

  1. int w = getWidth();
  2. int h = getHeight();

我们得到组件的宽度和高度。

  1. Random r = new Random();
  2. int x = Math.abs(r.nextInt()) % w;
  3. int y = Math.abs(r.nextInt()) % h;

我们得到一个上面计算出的区域大小范围内的随机数。

  1. g2d.drawLine(x, y, x, y);

在这里,我们指出了这一点。 如前所述,我们使用drawLine()方法。 我们两次指定相同的点。

  1. @Override
  2. public void actionPerformed(ActionEvent e) {
  3. repaint();
  4. }

每个动作事件,我们都调用repaint()方法。 这将导致整个客户区被重绘。

  1. addWindowListener(new WindowAdapter() {
  2. @Override
  3. public void windowClosing(WindowEvent e) {
  4. Timer timer = surface.getTimer();
  5. timer.stop();
  6. }
  7. });

当窗口即将关闭时,我们检索计时器并使用其stop()方法将其关闭。 未明确取消的计时器可能无限期地占用资源。 EXIT_ON_CLOSE默认关闭操作将关闭 JVM 及其所有线程,因此对于我们的示例而言,这不是必需的。 但是,尽管如此,作为一种好的编程习惯,我们仍然会这样做。

基本绘图 - 图1

图:点

直线

线是简单的图形基元。 线是连接两个点的对象。 使用drawLine()方法绘制线。

LinesEx.java

  1. package com.zetcode;
  2. import java.awt.EventQueue;
  3. import java.awt.Graphics;
  4. import java.awt.Graphics2D;
  5. import javax.swing.JFrame;
  6. import javax.swing.JPanel;
  7. class Surface extends JPanel {
  8. private void doDrawing(Graphics g) {
  9. Graphics2D g2d = (Graphics2D) g;
  10. g2d.drawLine(30, 30, 200, 30);
  11. g2d.drawLine(200, 30, 30, 200);
  12. g2d.drawLine(30, 200, 200, 200);
  13. g2d.drawLine(200, 200, 30, 30);
  14. }
  15. @Override
  16. public void paintComponent(Graphics g) {
  17. super.paintComponent(g);
  18. doDrawing(g);
  19. }
  20. }
  21. public class LinesEx extends JFrame {
  22. public LinesEx() {
  23. initUI();
  24. }
  25. private void initUI() {
  26. add(new Surface());
  27. setTitle("Lines");
  28. setSize(350, 250);
  29. setLocationRelativeTo(null);
  30. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  31. }
  32. public static void main(String[] args) {
  33. EventQueue.invokeLater(new Runnable() {
  34. @Override
  35. public void run() {
  36. LinesEx ex = new LinesEx();
  37. ex.setVisible(true);
  38. }
  39. });
  40. }
  41. }

我们用四个线画一个简单的对象。

  1. g2d.drawLine(30, 30, 200, 30);

画一条直线。 该方法的参数是两点的 x,y 坐标。

基本绘图 - 图2

图:直线

基本轮廓

BasicStroke类为图形基元的轮廓定义了一组基本的渲染属性。 这些渲染属性包括宽度,端盖,线连接,斜接限制和笔划线。

BasicStrokesEx.java

  1. package com.zetcode;
  2. import java.awt.BasicStroke;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import javax.swing.JFrame;
  7. import javax.swing.JPanel;
  8. class Surface extends JPanel {
  9. private void doDrawing(Graphics g) {
  10. Graphics2D g2d = (Graphics2D) g.create();
  11. float[] dash1 = {2f, 0f, 2f};
  12. float[] dash2 = {1f, 1f, 1f};
  13. float[] dash3 = {4f, 0f, 2f};
  14. float[] dash4 = {4f, 4f, 1f};
  15. g2d.drawLine(20, 40, 250, 40);
  16. BasicStroke bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT,
  17. BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f);
  18. BasicStroke bs2 = new BasicStroke(1, BasicStroke.CAP_BUTT,
  19. BasicStroke.JOIN_ROUND, 1.0f, dash2, 2f);
  20. BasicStroke bs3 = new BasicStroke(1, BasicStroke.CAP_BUTT,
  21. BasicStroke.JOIN_ROUND, 1.0f, dash3, 2f);
  22. BasicStroke bs4 = new BasicStroke(1, BasicStroke.CAP_BUTT,
  23. BasicStroke.JOIN_ROUND, 1.0f, dash4, 2f);
  24. g2d.setStroke(bs1);
  25. g2d.drawLine(20, 80, 250, 80);
  26. g2d.setStroke(bs2);
  27. g2d.drawLine(20, 120, 250, 120);
  28. g2d.setStroke(bs3);
  29. g2d.drawLine(20, 160, 250, 160);
  30. g2d.setStroke(bs4);
  31. g2d.drawLine(20, 200, 250, 200);
  32. g2d.dispose();
  33. }
  34. @Override
  35. public void paintComponent(Graphics g) {
  36. super.paintComponent(g);
  37. doDrawing(g);
  38. }
  39. }
  40. public class BasicStrokesEx extends JFrame {
  41. public BasicStrokesEx() {
  42. initUI();
  43. }
  44. private void initUI() {
  45. add(new Surface());
  46. setTitle("Basic strokes");
  47. setSize(280, 270);
  48. setLocationRelativeTo(null);
  49. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  50. }
  51. public static void main(String[] args) {
  52. EventQueue.invokeLater(new Runnable() {
  53. @Override
  54. public void run() {
  55. BasicStrokesEx ex = new BasicStrokesEx();
  56. ex.setVisible(true);
  57. }
  58. });
  59. }
  60. }

在此示例中,我们显示了各种笔划线。 笔划线属性是一种模式,通过混合不透明部分和透明部分来创建。

  1. Graphics2D g2d = (Graphics2D) g.create();

我们将更改Graphics对象的stroke属性; 因此,我们使用Graphics对象的副本。 (请记住,如果我们更改字体,颜色或渲染提示以外的属性,则必须创建一个副本。)

  1. float[] dash1 = { 2f, 0f, 2f };
  2. float[] dash2 = { 1f, 1f, 1f };
  3. float[] dash3 = { 4f, 0f, 2f };
  4. float[] dash4 = { 4f, 4f, 1f };

在这里,我们定义了四种不同的笔划线模式。

  1. BasicStroke bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT,
  2. BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f );

该行构造一个BasicStroke对象。

  1. g2d.setStroke(bs1);

我们使用setStroke()方法将BasicStroke应用于当前图形上下文。

  1. g2d.drawLine(20, 80, 250, 80);

drawLine()方法画一条线。

  1. g2d.dispose();

最后,我们放置Graphics对象的副本。

基本绘图 - 图3

图:基本描边

端帽

上限是应用于未封闭子路径和破折线段末端的装饰。 Java 2D 中有三种不同的端盖:CAP_BUTTCAP_ROUNDCAP_SQUARE

  • CAP_BUTT - 结束未封闭的子路径和虚线段,不添加任何修饰。
  • CAP_ROUND - 用圆形装饰结束未封闭的子路径和虚线段,该圆形装饰的半径等于笔的宽度的一半。
  • CAP_SQUARE - 以方形投影结束未封闭的子路径和虚线段,该方形投影超出段的末端并延伸到等于线宽一半的距离。

CapsEx.java

  1. package com.zetcode;
  2. import java.awt.BasicStroke;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import java.awt.RenderingHints;
  7. import javax.swing.JFrame;
  8. import javax.swing.JPanel;
  9. class Surface extends JPanel {
  10. private void doDrawing(Graphics g) {
  11. Graphics2D g2d = (Graphics2D) g.create();
  12. RenderingHints rh = new RenderingHints(
  13. RenderingHints.KEY_ANTIALIASING,
  14. RenderingHints.VALUE_ANTIALIAS_ON);
  15. rh.put(RenderingHints.KEY_RENDERING,
  16. RenderingHints.VALUE_RENDER_QUALITY);
  17. g2d.setRenderingHints(rh);
  18. BasicStroke bs1 = new BasicStroke(8, BasicStroke.CAP_BUTT,
  19. BasicStroke.JOIN_BEVEL);
  20. g2d.setStroke(bs1);
  21. g2d.drawLine(20, 30, 250, 30);
  22. BasicStroke bs2 = new BasicStroke(8, BasicStroke.CAP_ROUND,
  23. BasicStroke.JOIN_BEVEL);
  24. g2d.setStroke(bs2);
  25. g2d.drawLine(20, 80, 250, 80);
  26. BasicStroke bs3 = new BasicStroke(8, BasicStroke.CAP_SQUARE,
  27. BasicStroke.JOIN_BEVEL);
  28. g2d.setStroke(bs3);
  29. g2d.drawLine(20, 130, 250, 130);
  30. BasicStroke bs4 = new BasicStroke();
  31. g2d.setStroke(bs4);
  32. g2d.drawLine(20, 20, 20, 140);
  33. g2d.drawLine(250, 20, 250, 140);
  34. g2d.drawLine(254, 20, 254, 140);
  35. g2d.dispose();
  36. }
  37. @Override
  38. public void paintComponent(Graphics g) {
  39. super.paintComponent(g);
  40. doDrawing(g);
  41. }
  42. }
  43. public class CapsEx extends JFrame {
  44. public CapsEx() {
  45. initUI();
  46. }
  47. private void initUI() {
  48. add(new Surface());
  49. setTitle("Caps");
  50. setSize(280, 270);
  51. setLocationRelativeTo(null);
  52. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  53. }
  54. public static void main(String[] args) {
  55. EventQueue.invokeLater(new Runnable() {
  56. @Override
  57. public void run() {
  58. CapsEx ex = new CapsEx();
  59. ex.setVisible(true);
  60. }
  61. });
  62. }
  63. }

在我们的示例中,我们显示了所有三种类型的端盖。

  1. BasicStroke bs1 = new BasicStroke(8, BasicStroke.CAP_BUTT,
  2. BasicStroke.JOIN_BEVEL);
  3. g2d.setStroke(bs1);

创建并应用带有对接盖的基本描边。 CAP_BUTT不添加装饰。

  1. g2d.drawLine(20, 20, 20, 140);
  2. g2d.drawLine(250, 20, 250, 140);
  3. g2d.drawLine(254, 20, 254, 140);

我们画了三根垂直线来解释端盖之间的差异。 具有CAP_ROUNDCAP_SQUARE的线比具有CAP_BUTT的线大。 究竟多大取决于行的大小。 在我们的情况下,线的宽度为 8 像素。 线长 8 像素-左侧 4 像素,右侧 4 像素。 从图片中应该清楚。

基本绘图 - 图4

图:端帽

连接

线连接是应用于两个路径段的交点以及子路径端点的交点的修饰。 一共有三种装饰:JOIN_BEVELJOIN_MITERJOIN_ROUND

  • JOIN_BEVEL - 通过将宽轮廓的外角与直线段相连来连接路径段。
  • JOIN_MITER - 通过扩展路径段的外部边缘直到它们交汇来连接路径段。
  • JOIN_ROUND - 通过以线宽一半的半径四舍五入拐角来连接路径段。

JoinsEx.java

  1. package com.zetcode;
  2. import java.awt.BasicStroke;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import javax.swing.JFrame;
  7. import javax.swing.JPanel;
  8. class Surface extends JPanel {
  9. private void doDrawing(Graphics g) {
  10. Graphics2D g2d = (Graphics2D) g.create();
  11. BasicStroke bs1 = new BasicStroke(8, BasicStroke.CAP_ROUND,
  12. BasicStroke.JOIN_BEVEL);
  13. g2d.setStroke(bs1);
  14. g2d.drawRect(15, 15, 80, 50);
  15. BasicStroke bs2 = new BasicStroke(8, BasicStroke.CAP_ROUND,
  16. BasicStroke.JOIN_MITER);
  17. g2d.setStroke(bs2);
  18. g2d.drawRect(125, 15, 80, 50);
  19. BasicStroke bs3 = new BasicStroke(8, BasicStroke.CAP_ROUND,
  20. BasicStroke.JOIN_ROUND);
  21. g2d.setStroke(bs3);
  22. g2d.drawRect(235, 15, 80, 50);
  23. g2d.dispose();
  24. }
  25. @Override
  26. public void paintComponent(Graphics g) {
  27. super.paintComponent(g);
  28. doDrawing(g);
  29. }
  30. }
  31. public class JoinsEx extends JFrame {
  32. public JoinsEx() {
  33. initUI();
  34. }
  35. private void initUI() {
  36. add(new Surface());
  37. setTitle("Joins");
  38. setSize(340, 110);
  39. setLocationRelativeTo(null);
  40. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  41. }
  42. public static void main(String[] args) {
  43. EventQueue.invokeLater(new Runnable() {
  44. @Override
  45. public void run() {
  46. JoinsEx ex = new JoinsEx();
  47. ex.setVisible(true);
  48. }
  49. });
  50. }
  51. }

此代码示例显示了三个不同的线联接在起作用。

  1. BasicStroke bs1 = new BasicStroke(8, BasicStroke.CAP_ROUND,
  2. BasicStroke.JOIN_BEVEL);
  3. g2d.setStroke(bs1);
  4. g2d.drawRect(15, 15, 80, 50);

在这里,我们创建一个带有JOIN_BEVEL联接的矩形。

基本绘图 - 图5

图:Joins

在 Java 2D 教程的这一部分中,我们做了一些基本的绘制。