1.[代码][Java]代码
package crazyMonkey.draw;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.util.LinkedList;
import java.util.TreeSet;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.util.ArrayList;
/**
* 绘图类,此类实例提供基本的绘图支持,包括:
*
*
基本几何图形*
图片*
文本*
*
*
此类对象提供对基本事件的支持,例如鼠标移动,键盘按键处理等事件。用例只需要实现{@link DrawListener}接口,
* 就可以完成对基本事件的处理。另外,此类实例提供了将画布上的图像保存为实际图片的功能。
*
* @author crazyMonkey
*/
public final class Draw implements ActionListener, MouseListener,
MouseMotionListener, KeyListener {
/*
* 预定义颜色
*/
public static final Color BLACK = Color.BLACK;
public static final Color BLUE = Color.BLUE;
public static final Color CYAN = Color.CYAN;
public static final Color DARK_GRAY = Color.DARK_GRAY;
public static final Color GRAY = Color.GRAY;
public static final Color GREEN = Color.GREEN;
public static final Color LIGHT_GRAY = Color.LIGHT_GRAY;
public static final Color MAGENTA = Color.MAGENTA;
public static final Color ORANGE = Color.ORANGE;
public static final Color PINK = Color.PINK;
public static final Color RED = Color.RED;
public static final Color WHITE = Color.WHITE;
public static final Color YELLOW = Color.YELLOW;
/*
* 演示用蓝色 RGB值 (9, 90, 166)
*/
public static final Color BOOK_BLUE = new Color(9, 90, 166);
/*
* 算法演示用色 暗红 RGB值 (173, 32, 24)
*/
public static final Color BOOK_RED = new Color(173, 32, 24);
// 默认画笔颜色
private static final Color DEFAULT_PEN_COLOR = BLACK;
private static final Color DEFAULT_CLEAR_COLOR = WHITE;
// 画布边界宽度,默认占画布宽度的 5%
private static final double BORDER = 0.05;
private static final double DEFAULT_XMIN = 0.0;
private static final double DEFAULT_XMAX = 1.0;
private static final double DEFAULT_YMIN = 0.0;
private static final double DEFAULT_YMAX = 1.0;
// 默认的画布大小
private static final int DEFAULT_SIZE = 512;
// 默认画笔半径
private static final double DEFAULT_PEN_RADIUS = 0.002;
// 默认字体
private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16);
// 当前画笔颜色
private Color penColor;
// 画布大小
private int width = DEFAULT_SIZE;
private int height = DEFAULT_SIZE;
// 当前画笔半径
private double penRadius;
// 用于标识是否立即绘制画面,或者等待到下一次绘制时
private boolean defer = false;
private double xmin, ymin, xmax, ymax;
// 窗口默认标题
private String name = "Draw";
// 同步对象
private Object mouseLock = new Object();
private Object keyLock = new Object();
// 当前字体
private Font font;
// 缓冲绘制对象
private BufferedImage offscreenImage, onscreenImage;
private Graphics2D offscreen, onscreen;
// 绘制面板框架
private JFrame frame = new JFrame();
// 鼠标状态参数
private boolean mousePressed = false;
private double mouseX = 0;
private double mouseY = 0;
// 键盘按键状态集
private LinkedList keysTyped = new LinkedList();
private TreeSet keysDown = new TreeSet();
// 基本的事件监听类列表
private ArrayList listeners = new ArrayList();
/**
* 创建一个默认的绘图对象
*/
public Draw() {
init();
}
/**
* 创建一个指定标题的绘图对象
*
* @param name 绘图窗口标题
*/
public Draw(String name) {
this.name = name;
init();
}
/*
* 框架、面板、画布等初始化
*/
private void init() {
if (frame != null) frame.setVisible(false);
frame = new JFrame();
offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
onscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
offscreen = offscreenImage.createGraphics();
onscreen = onscreenImage.createGraphics();
setXscale();
setYscale();
offscreen.setColor(DEFAULT_CLEAR_COLOR);
offscreen.fillRect(0, 0, width, height);
setPenColor();
setPenRadius();
setFont();
clear();
// 抗锯齿处理
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
offscreen.addRenderingHints(hints);
// 填充框架
ImageIcon icon = new ImageIcon(onscreenImage);
JLabel draw = new JLabel(icon);
draw.addMouseListener(this);
draw.addMouseMotionListener(this);
frame.setContentPane(draw);
frame.addKeyListener(this);
frame.setResizable(false);
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setTitle(name);
frame.setJMenuBar(createMenuBar());
frame.pack();
frame.requestFocusInWindow();
frame.setVisible(true);
}
/**
* 设置绘图窗口在屏幕的位置,窗口左上角为参考点,以屏幕左上角为基准。默认的为(0,0)
*
* @param x 距离屏幕左侧的像素
* @param y 距离屏幕上侧的像素
* @throws 运行时异常,参数为负时产生
*/
public void setLocation(int x, int y) {
frame.setLocation(x, y);
}
/**
* 设置绘图窗口的大小
*
* @param w 窗口的宽度
* @param h 窗口的高度
* @throws 行时异常,参数为负或者为0时产生
*/
public void setCanvasSize(int w, int h) {
if (w < 1 || h < 1) throw new RuntimeException("宽度和高度必须为正数!");
width = w;
height = h;
init();
}
/**
* 为面板设置菜单栏
* @param menuBar 菜单栏
*/
public void setJMenuBar(JMenuBar menuBar){
if(menuBar == null)
frame.setJMenuBar(createMenuBar());
frame.setJMenuBar(menuBar);
init();
}
// 创建菜单栏
private JMenuBar createMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("文件");
menuBar.add(menu);
JMenuItem menuItem1 = new JMenuItem(" 保存为... ");
menuItem1.addActionListener(this);
menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
menu.add(menuItem1);
return menuBar;
}
/*
* 屏幕画布等坐标参数设置
*/
/**
* 将x坐标比例尺设置为默认(0.0~1.0)
*/
public void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); }
/**
* 将y坐标比例尺设置为默认(0.0~1.0)
*/
public void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); }
/**
* 设置画布x坐标比例尺,系统默认的画布带有10%的边界区域
* @param min x坐标的最小值
* @param max x坐标的最大值
*/
public void setXscale(double min, double max) {
double size = max - min;
xmin = min - BORDER * size;
xmax = max + BORDER * size;
}
/**
* 设置画布y坐标比例尺,系统默认的画布带有10%的边界区域
* @param min y坐标的最小值
* @param max y坐标的最大值
*/
public void setYscale(double min, double max) {
double size = max - min;
ymin = min - BORDER * size;
ymax = max + BORDER * size;
}
// 有关坐标参数的帮助函数
private double scaleX(double x) { return width * (x - xmin) / (xmax - xmin); }
private double scaleY(double y) { return height * (ymax - y) / (ymax - ymin); }
private double factorX(double w) { return w * width / Math.abs(xmax - xmin); }
private double factorY(double h) { return h * height / Math.abs(ymax - ymin); }
private double userX(double x) { return xmin + x * (xmax - xmin) / width; }
private double userY(double y) { return ymax - y * (ymax - ymin) / height; }
/**
* 将画布内容清除,画布背景色设置为默认白色
*/
public void clear() { clear(DEFAULT_CLEAR_COLOR); }
/**
* 将画布内容清除,画布背景色设置为指定颜色
* @param color 指定的画布背景色
*/
public void clear(Color color) {
offscreen.setColor(color);
offscreen.fillRect(0, 0, width, height);
offscreen.setColor(penColor);
draw();
}
/**
* 获取当前画笔半径
*/
public double getPenRadius() { return penRadius; }
/**
* 设置画笔半径为默认值 (.002)
*/
public void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); }
/**
* 设置画笔半径为指定的值
* @param r 待设置的画笔半径值
* @throws RuntimeException 运行时异常,参数为负时
*/
public void setPenRadius(double r) {
if (r < 0) throw new RuntimeException("画笔半径必须为正数!");
penRadius = r * DEFAULT_SIZE;
BasicStroke stroke = new BasicStroke((float) penRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
offscreen.setStroke(stroke);
}
/**
* 获取当前画笔颜色
*/
public Color getPenColor() { return penColor; }
/**
* 设置画笔颜色为默认颜色 黑色
*/
public void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); }
/**
* 设置画笔颜色为指定颜色
* @param color 指定的画笔颜色
*/
public void setPenColor(Color color) {
penColor = color;
offscreen.setColor(penColor);
}
public void xorOn() { offscreen.setXORMode(DEFAULT_CLEAR_COLOR); }
public void xorOff() { offscreen.setPaintMode(); }
/**
* 获取当前字体
*/
public Font getFont() { return font; }
/**
* 设置当前字体为默认(sans serif, 16 px).
*/
public void setFont() { setFont(DEFAULT_FONT); }
/**
* 设置当前字体为指定字体
* @param f 指定的字体
*/
public void setFont(Font f) { font = f; }
/*
* 几何图形绘制
*/
/**
* 由指定的起点坐标(x0, y0) 和终点坐标 (x1, y1) 绘制一条直线
* @param x0 起点的横坐标
* @param y0 起点的纵坐标
* @param x1 终点的横坐标
* @param y1 终点的纵坐标
*/
public void line(double x0, double y0, double x1, double y1) {
offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1)));
draw();
}
/**
* 由指定的坐标(x, y)绘制一个像素
* @param x 点的横坐标
* @param y 点的纵坐标
*/
private void pixel(double x, double y) {
offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1);
}
/**
* 由指定的坐标(x, y)绘制一个点
* @param x 点的横坐标
* @param y 点的纵坐标
*/
public void point(double x, double y) {
double xs = scaleX(x);
double ys = scaleY(y);
double r = penRadius;
if (r <= 1) pixel(x, y);
else offscreen.fill(new Ellipse2D.Double(xs - r/2, ys - r/2, r, r));
draw();
}
/**
* 绘制一个以(x, y)为圆心。r为半径的圆环
* @param x 圆心横坐标
* @param y 圆心纵坐标
* @param r 圆的半径
* @throws RuntimeException 指定的圆心或者半径参数为负
*/
public void circle(double x, double y, double r) {
if (r < 0) throw new RuntimeException("圆的半径不能为负数!");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*r);
double hs = factorY(2*r);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* 绘制一个以(x, y)为圆心。r为半径的圆面
* @param x 圆心横坐标
* @param y 圆心纵坐标
* @param r 圆的半径
* @throws RuntimeException 指定的圆心或者半径参数为负
*/
public void filledCircle(double x, double y, double r) {
if (r < 0) throw new RuntimeException("circle radius can't be negative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*r);
double hs = factorY(2*r);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* 绘制一个指定长半轴和短半轴并以(x,y)为椭圆圆心的椭圆环
* @param x 圆心横坐标
* @param y 圆心纵坐标
* @param 椭圆长半轴大小
* @param 椭圆短半轴大小
* @throws RuntimeException 参数指定错误,半轴长为负值
*/
public void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
if (semiMajorAxis < 0) throw new RuntimeException("椭圆半轴长为负值!");
if (semiMinorAxis < 0) throw new RuntimeException("椭圆半轴长为负值!");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*semiMajorAxis);
double hs = factorY(2*semiMinorAxis);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* 绘制一个指定长半轴和短半轴并以(x,y)为椭圆圆心的椭圆面
* @param x 圆心横坐标
* @param y 圆心纵坐标
* @param 椭圆长半轴大小
* @param 椭圆短半轴大小
* @throws RuntimeException 参数指定错误,半轴长为负值
*/
public void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
if (semiMajorAxis < 0) throw new RuntimeException("椭圆半轴长为负值!");
if (semiMinorAxis < 0) throw new RuntimeException("椭圆半轴长为负值!");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*semiMajorAxis);
double hs = factorY(2*semiMinorAxis);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* 绘制一段圆弧,该弧形半径为r,以(x,y)为弧形弧心,起始角度为 angle1 终止角度为 angle2
* @param x 圆弧中心横坐标
* @param y 圆弧中心纵坐标
* @param r 圆弧半径
* @param 圆弧起始角度,0为水平
* @param 圆弧终止角度,90为竖直
* @throws RuntimeException 半径为负值时产生
*/
public void arc(double x, double y, double r, double angle1, double angle2) {
if (r < 0) throw new RuntimeException("圆弧半径不能为负!");
while (angle2 < angle1) angle2 += 360;
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*r);
double hs = factorY(2*r);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN));
draw();
}
/**
* 绘制一个以(x,y)为中心,边长为2r的正方形
* @param x 正方形中心的横坐标
* @param y 正方形中心的纵坐标
* @param r 正方形的半长
* @throws RuntimeException 半径为负值
*/
public void square(double x, double y, double r) {
if (r < 0) throw new RuntimeException("正方形边长不能为负!");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*r);
double hs = factorY(2*r);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* 绘制一个以(x,y)为中心,边长为2r的正方形,并予以填充
* @param x 正方形中心的横坐标
* @param y 正方形中心的纵坐标
* @param r 正方形的半长
* @throws RuntimeException 半径为负值
*/
public void filledSquare(double x, double y, double r) {
if (r < 0) throw new RuntimeException("正方形边长不能为负!");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*r);
double hs = factorY(2*r);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* 绘制一个以(x,y)为中心,宽为2*halfWidth,高为2*halfHeight的长方形
* @param x 长方形中心的横坐标
* @param y 长方形中心的纵坐标
* @param halfWidth 长方形的半宽
* @param halfHeight 长方形的半高
* @throws RuntimeException 半高或者半宽为负值
*/
public void rectangle(double x, double y, double halfWidth, double halfHeight) {
if (halfWidth < 0) throw new RuntimeException("宽度不能为负值!");
if (halfHeight < 0) throw new RuntimeException("高度不能为负值!");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*halfWidth);
double hs = factorY(2*halfHeight);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* 绘制一个以(x,y)为中心,宽为2*halfWidth,高为2*halfHeight的长方形,并予以填充
* @param x 长方形中心的横坐标
* @param y 长方形中心的纵坐标
* @param halfWidth 长方形的半宽
* @param halfHeight 长方形的半高
* @throws RuntimeException 半高或者半宽为负值
*/
public void filledRectangle(double x, double y, double halfWidth, double halfHeight) {
if (halfWidth < 0) throw new RuntimeException("宽度不能为负值!");
if (halfHeight < 0) throw new RuntimeException("高度不能为负值!");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*halfWidth);
double hs = factorY(2*halfHeight);
if (ws <= 1 && hs <= 1) pixel(x, y);
else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* 由指定的坐标参数 (x[i], y[i]) 绘制一个正多边形
* @param x 正多边形的所有横坐标
* @param y 正多边形的所有纵坐标
*/
public void polygon(double[] x, double[] y) {
int N = x.length;
GeneralPath path = new GeneralPath();
path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0]));
for (int i = 0; i < N; i++)
path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i]));
path.closePath();
offscreen.draw(path);
draw();
}
/**
* 由指定的坐标参数 (x[i], y[i]) 绘制一个正多边形并予以填充
* @param x 正多边形的所有横坐标
* @param y 正多边形的所有纵坐标
*/
public void filledPolygon(double[] x, double[] y) {
int N = x.length;
GeneralPath path = new GeneralPath();
path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0]));
for (int i = 0; i < N; i++)
path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i]));
path.closePath();
offscreen.fill(path);
draw();
}
/*
* 绘制图像
*/
// 由指定文件获取图像
private Image getImage(String filename) {
// 读取文件
ImageIcon icon = new ImageIcon(filename);
// 尝试从URL中获取图像
if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
try {
URL url = new URL(filename);
icon = new ImageIcon(url);
} catch (Exception e) { /* 非URL */ }
}
if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
URL url = Draw.class.getResource(filename);
if (url == null) throw new RuntimeException("图像 " + filename + " 未找到");
icon = new ImageIcon(url);
}
return icon.getImage();
}
/**
* 绘制一幅以(x, y)为中心的图片,格式为(gif, jpg, or png)
* @param x 图片中心横坐标
* @param y 图片中心纵坐标
* @param s 图片名称
* @throws RuntimeException 图片文件损坏
*/
public void picture(double x, double y, String s) {
Image image = getImage(s);
double xs = scaleX(x);
double ys = scaleY(y);
int ws = image.getWidth(null);
int hs = image.getHeight(null);
if (ws < 0 || hs < 0) throw new RuntimeException("图片 " + s + " 损坏!");
offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
draw();
}
/**
* 绘制一幅以(x, y)为中心的图片,逆时针旋转角度为degrees ,格式为(gif, jpg, or png)
* @param x 图片中心横坐标
* @param y 图片中心纵坐标
* @param s 图片名称
* @param degrees 逆时针旋转角度
* @throws RuntimeException 图片文件损坏
*/
public void picture(double x, double y, String s, double degrees) {
Image image = getImage(s);
double xs = scaleX(x);
double ys = scaleY(y);
int ws = image.getWidth(null);
int hs = image.getHeight(null);
if (ws < 0 || hs < 0) throw new RuntimeException("图片 " + s + " 损坏!");
offscreen.rotate(Math.toRadians(-degrees), xs, ys);
offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
offscreen.rotate(Math.toRadians(+degrees), xs, ys);
draw();
}
/**
* 绘制一幅以(x, y)为中心的图片(gif, jpg, or png),并且指定图片的宽度和高度
* @param x 图片中心横坐标
* @param y 图片中心纵坐标
* @param s 图片名称
* @param w 图片宽度
* @param h 图片高度
* @throws RuntimeException 图片文件损坏
*/
public void picture(double x, double y, String s, double w, double h) {
Image image = getImage(s);
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(w);
double hs = factorY(h);
if (ws < 0 || hs < 0) throw new RuntimeException("文件" + s + " 损坏!");
if (ws <= 1 && hs <= 1) pixel(x, y);
else {
offscreen.drawImage(image, (int) Math.round(xs - ws/2.0),
(int) Math.round(ys - hs/2.0),
(int) Math.round(ws),
(int) Math.round(hs), null);
}
draw();
}
/**
* 制一幅以(x, y)为中心的图片(gif, jpg, or png),并且指定图片的宽度和高度以及逆时针旋转的角度
* given number of degrees, rescaled to w-by-h.
* @param x 图片中心横坐标
* @param y 图片中心纵坐标
* @param s 图片名称
* @param w 图片宽度
* @param h 图片高度
* @param degrees 逆时针旋转角度
* @throws RuntimeException 图片文件损坏
*/
public void picture(double x, double y, String s, double w, double h, double degrees) {
Image image = getImage(s);
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(w);
double hs = factorY(h);
if (ws < 0 || hs < 0) throw new RuntimeException("图片 " + s + " 损坏!");
if (ws <= 1 && hs <= 1) pixel(x, y);
offscreen.rotate(Math.toRadians(-degrees), xs, ys);
offscreen.drawImage(image, (int) Math.round(xs - ws/2.0),
(int) Math.round(ys - hs/2.0),
(int) Math.round(ws),
(int) Math.round(hs), null);
offscreen.rotate(Math.toRadians(+degrees), xs, ys);
draw();
}
/*
* 绘制文本
*/
/**
* 在画布上绘制一个以(x,y)为中心的指定的字符串
* @param x 文本中心的横坐标
* @param y 文本中心的纵坐标
* @param s 文本内容
*/
public void text(double x, double y, String s) {
offscreen.setFont(font);
FontMetrics metrics = offscreen.getFontMetrics();
double xs = scaleX(x);
double ys = scaleY(y);
int ws = metrics.stringWidth(s);
int hs = metrics.getDescent();
offscreen.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs));
draw();
}
/**
* 在画布上绘制一个以(x,y)为中心的并且指定旋转角度的字符串
* @param x 文本中心的横坐标
* @param y 文本中心的纵坐标
* @param s 文本内容
* @param degrees 文本逆时针旋转的角度
*/
public void text(double x, double y, String s, double degrees) {
double xs = scaleX(x);
double ys = scaleY(y);
offscreen.rotate(Math.toRadians(-degrees), xs, ys);
text(x, y, s);
offscreen.rotate(Math.toRadians(+degrees), xs, ys);
}
/**
* 以当前字体绘制一串文本并且以(x, y)左对齐
* @param x 文本横坐标
* @param y 文本纵坐标
* @param s 文本内容
*/
public void textLeft(double x, double y, String s) {
offscreen.setFont(font);
FontMetrics metrics = offscreen.getFontMetrics();
double xs = scaleX(x);
double ys = scaleY(y);
int hs = metrics.getDescent();
offscreen.drawString(s, (float) (xs), (float) (ys + hs));
show();
}
/**
* 显示屏幕内容,并且暂停t毫秒。通过此方法可以将绘图模式转为动画模式(清空屏幕,绘制图形,在屏幕上显示指定的时间,重复)
* @param t 屏幕显示的毫秒数
*/
public void show(int t) {
defer = false;
draw();
try { Thread.sleep(t); }
catch (InterruptedException e) { System.out.println("休眠错误!"); }
defer = true;
}
/**
* 显示屏幕内容,关闭动画模式,这是默认的显示方式
*/
public void show() {
defer = false;
draw();
}
private void draw() {
if (defer) return;
onscreen.drawImage(offscreenImage, 0, 0, null);
frame.repaint();
}
/*
* 将画布上的图像保存到文件中
*/
/**
* 保存到指定的位置,文件格式必须为( png, jpg, gif)中的一种
* @param filename 文件全名,需要指定格式
*/
public void save(String filename) {
File file = new File(filename);
String suffix = filename.substring(filename.lastIndexOf('.') + 1);
// png
if (suffix.toLowerCase().equals("png")) {
try { ImageIO.write(offscreenImage, suffix, file); }
catch (IOException e) { e.printStackTrace(); }
}
// jpg
else if (suffix.toLowerCase().equals("jpg")) {
WritableRaster raster = offscreenImage.getRaster();
WritableRaster newRaster;
newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2});
DirectColorModel cm = (DirectColorModel) offscreenImage.getColorModel();
DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(),
cm.getRedMask(),
cm.getGreenMask(),
cm.getBlueMask());
BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null);
try { ImageIO.write(rgbBuffer, suffix, file); }
catch (IOException e) { e.printStackTrace(); }
}
else {
System.out.println("不合法的文件格式: " + suffix);
}
}
/**
* 此方法用于处理事件,不能被直接调用
*/
public void actionPerformed(ActionEvent e) {
FileDialog chooser = new FileDialog(frame, "使用 .png 或者 .jpg 扩展名", FileDialog.SAVE);
chooser.setVisible(true);
String filename = chooser.getFile();
if (filename != null) {
save(chooser.getDirectory() + File.separator + chooser.getFile());
}
}
/*
* 基于事件的交互处理
*/
/**
* 为框架添加监听器
* @param listener 绘图监听器
*/
public void addListener(DrawListener listener) {
// 确保有窗口存在
show();
listeners.add(listener);
frame.addKeyListener(this);
frame.addMouseListener(this);
frame.addMouseMotionListener(this);
frame.setFocusable(true);
}
/*
* 鼠标系列事件
*/
/**
* 鼠标按键
* @return true 或者 false
*/
public boolean mousePressed() {
synchronized (mouseLock) {
return mousePressed;
}
}
/**
* 获取鼠标的横坐标
* @return 鼠标的横坐标
*/
public double mouseX() {
synchronized (mouseLock) {
return mouseX;
}
}
/**
* 获取鼠标的纵坐标
* @return 鼠标的纵坐标
*/
public double mouseY() {
synchronized (mouseLock) {
return mouseY;
}
}
/**
* 此方法不能被用户直接调用
*/
public void mouseClicked(MouseEvent e) { }
/**
* 此方法不能被用户直接调用
*/
public void mouseEntered(MouseEvent e) { }
/**
* 此方法不能被用户直接调用
*/
public void mouseExited(MouseEvent e) { }
/**
* 鼠标按键事件(按下)处理,此方法不能被用户直接调用
*/
public void mousePressed(MouseEvent e) {
synchronized (mouseLock) {
mouseX = userX(e.getX());
mouseY = userY(e.getY());
mousePressed = true;
}
if (e.getButton() == MouseEvent.BUTTON1) {
for (DrawListener listener : listeners)
listener.mousePressed(userX(e.getX()), userY(e.getY()));
}
}
/**
* 鼠标按键事件(松开)处理,此方法不能被用户直接调用
*/
public void mouseReleased(MouseEvent e) {
synchronized (mouseLock) {
mousePressed = false;
}
if (e.getButton() == MouseEvent.BUTTON1) {
for (DrawListener listener : listeners)
listener.mouseReleased(userX(e.getX()), userY(e.getY()));
}
}
/**
* 鼠标按键事件(鼠标拖动)处理,此方法不能被用户直接调用
*/
public void mouseDragged(MouseEvent e) {
synchronized (mouseLock) {
mouseX = userX(e.getX());
mouseY = userY(e.getY());
}
for (DrawListener listener : listeners)
listener.mouseDragged(userX(e.getX()), userY(e.getY()));
}
/**
* 鼠标按键事件(鼠标移动)处理,此方法不能被用户直接调用
*/
public void mouseMoved(MouseEvent e) {
synchronized (mouseLock) {
mouseX = userX(e.getX());
mouseY = userY(e.getY());
}
}
/*
* 键盘系列事件
*/
/**
* 确认用户是否按下一个按键
* @return true 按下, false 未按下
*/
public boolean hasNextKeyTyped() {
synchronized (keyLock) {
return !keysTyped.isEmpty();
}
}
/**
* 获取用户按键
* @return 下一个用户按键字符
*/
public char nextKeyTyped() {
synchronized (keyLock) {
return keysTyped.removeLast();
}
}
/**
* 确认指定键码的按键是否被按下,此方法可以处理方向键、功能键、以及修饰按键
* @return true 如果指定按键被按下, false 指定按键未被按下
*/
public boolean isKeyPressed(int keycode) {
synchronized (keyLock) {
return keysDown.contains(keycode);
}
}
/**
* 此方法不能被用户直接调用
*/
public void keyTyped(KeyEvent e) {
synchronized (keyLock) {
keysTyped.addFirst(e.getKeyChar());
}
// 唤醒所有监听者
for (DrawListener listener : listeners)
listener.keyTyped(e.getKeyChar());
}
/**
* 键盘事件(按下)处理,此方法不能被用户直接调用
*/
public void keyPressed(KeyEvent e) {
synchronized (keyLock) {
keysDown.add(e.getKeyCode());
}
}
/**
* 键盘事件(松开)处理,此方法不能被用户直接调用
*/
public void keyReleased(KeyEvent e) {
synchronized (keyLock) {
keysDown.remove(e.getKeyCode());
}
}
}