绘图板程序设计及其具体实现

成果展示

首先展示几个使用本绘图板程序的绘图成果。

这里写图片描述

这里写图片描述

下面是程序的功能介绍(也是由本绘图板程序绘制的)。

这里写图片描述

这里写图片描述

这里写图片描述


#总体架构

由三个包和一个主窗口类构成,utils包中为绘制图形中用到的所有工具类,element包中为所有的图形元素类,strategy包中为所有的绘图策略类。

大致的设计思想是,utils包中存储本程序中用到的所有工具,有绘图处理工具,输入处理工具,渲染处理工具;element包中存储所有需要绘制的图形元素;strategy包中存储所有所需的绘图策略;最后由一个主程序调用策略去执行图形元素的绘制。所有类的简要说明如下表。

  • 类及接口的一览表
类名/接口名说明
utilsVector2f类表示一个二维向量的类,包含向量的一些运算操作,用于表示平面空间中一个点的位置
utilsMatrix3x3f类表示三维矩阵的类,包含矩阵的一些运算操作,用于平面中图形的变换操作
utilsFrameRate类计算帧速率的类
utilsFramework抽象类一个抽象的利用双缓冲策略的窗口框架
utilsSwingFramework抽象类一个使用Swing做绘制实现的窗口框架
utilsSafeKeyboardInput类一个安全的处理键盘输入的类
utilsRelativeMouseInput类一个可以切换相对/绝对鼠标模式的处理鼠标输入的类
utilsUtilty类处理坐标变换,图形绘制,文字绘制的工具类
elementImageElement抽象类所有可以绘制图形类的父类
elementPoints类一个包含一系列点的类
elementBeginEndImageElement抽象类一个可以选择起点和终点由拖动鼠标来形成的图形类
elementFillPolygon类填充多边形类
elementSimilarable接口可以进行快捷选择操作的图形,关于快捷选择操作,后面会有详细介绍
elementStringElement类绘制字符串的图形类
elementSaveImageElement类当前绘制图像被保存之后,添加此类表示当前图像已被保存
strategyStrategy抽象类所有绘制策略类的父类
strategyNullStrategy类一个不做任何处理的策略类
strategyPointsStrategy类处理绘制一系列点(Points类)的策略类
strategyBeginEndStrategy抽象类处理绘制可以选择起点和终点由拖动鼠标来形成的图形类(BeginEndImageElement抽象类)的策略类
strategyFillPolygon类处理绘制填充多边形类(FillPolygon类)的策略类
strategyStringStrategy类处理绘制字符串的图形类(StringElement类)的策略类
defaultEditor类主窗口类

#工具包详细解析

##(一) 绘图处理工具类

###Vector2f类

Vector2f类是一个表示二维向量的类,可以表示平面中的一个点或者一个向量。包含3个字段,x,y,w,w表示这个实例是一个点还是一个向量。对于点来说w=1,表示平移有效,可以用来变换矩阵;对于向量来说w=0,表示平移无效,不可以用来变换矩阵。

Vector2f类还包含一个静态字段EPSILON,表示可以区分Vector2f的最小值。因为浮点数的存储是不连续的,所以当两个Vector2f的距离小于EPSILON时就可以判断两个Vector2f是相等的。

  • Vector2f类方法一览表
修饰符返回值函数名参数说明
publicVector2f()默认构造函数,默认值为(0.0f, 0.0f, 1.0f)
publicVector2f(Vector2f v)构造函数
publicVector2f(float x, float y)构造函数,默认w=1
publicVector2f(float x, float y, float w)构造函数
publicvoidtranslate(float tx, float ty)平移
publicvoidscale(float s)x,y同比例缩放
publicvoidscale(float sx, float sy)x,y不同比例缩放
publicvoidrotate(float rad)旋转
publicvoidshear(float s)x,y同比例切变
publicvoidshear(float sx, float sy)x,y不同比例切变
publicVector2finv()返回相反的向量(-x,-y)
publicVector2fadd(Vector2f v)返回两个向量相加
publicVector2fsub(Vector2f v)返回两个向量相减
publicVector2fmul(float scalar)返回向量和实数相乘
publicVector2fdiv(float scalar)返回向量和实数相除
publicfloatlen()返回向量的长度
publicfloatlenSqr()返回向量长度的平方
publicVector2fnorm()返回向量的标准化向量
publicVector2fperp()返回向量的垂直向量(-y,x)
publicfloatdot(Vector2f v)返回两个向量点乘
publicfloatangle()返回向量与x轴正半轴的夹角,Pi~-Pi
public staticVector2fpolar(float angle, float radius)由向量的角度和长度构造向量
publicStringtoString()打印向量
publicbooleanequals(Object obj)比较两个向量是否相等
public finalbooleansimilar(Vector2f v, float EPSILON)判断两个向量是否在EPSILON程度上相似,即两向量之间的距离小于EPSILON
  • Vector2f类源代码
package Rendering.utils;

/**
 * This class is similar to Two-dimensional vector and 
 * has some operating functions.
 */
public class Vector2f {
    public static final float EPSILON = 0.00001f;

    public final boolean similar(Vector2f v, float EPSILON) {
        return this.sub(v).len() < EPSILON;
    }

    /**
     * The first dimension of the vector.
     */
    public float x;

    /**
     * The second dimension of the vector.
     */
    public float y;

    /**
     * The flag of the vector.
     * As for a point, the w should be 1;
     * But as a vector, the w should be 0;
     * Only when the w is 1 that the vector can translate a Matrix.
     */
    public float w;

    /**
     * Constructs a zero vector.
     */
    public Vector2f() {
        this.x = 0.0f;
        this.y = 0.0f;
        this.w = 1.0f;
    }

    /**
     * Constructs a vector copying from another vector.
     * @param v The vector to copy.
     */
    public Vector2f(Vector2f v) {
        this.x = v.x;
        this.y = v.y;
        this.w = v.w;
    }

    /**
     * Constructs a vector from two dimension.
     * @param x The first dimension of the vector.
     * @param y The second dimension of the vector.
     */
    public Vector2f(float x, float y) {
        this.x = x;
        this.y = y;
        this.w = 1.0f;
    }

    /**
     * Constructs a vector from two dimension
     * @param x The first dimension of the vector.
     * @param y The second dimension of the vector.
     * @param w The flag of the vector.
     */
    public Vector2f(float x, float y, float w) {
        this.x = x;
        this.y = y;
        this.w = w;
    }

    /**
     * Translate the vector.
     * @param tx The first dimension need to translate.
     * @param ty The second dimension need to translate.
     */
    public void translate(float tx, float ty) {
        x += tx;
        y += ty;
    }

    /**
     * Scale the vector.
     * @param s The multiple that need to scale.
     */
    public void scale(float s) {
        x *= s;
        y *= s;
    }

    /**
     * Scale the vector.
     * @param sx The multiple that need to scale in the first dimension.
     * @param sy The multiple that need to scale in the second dimension.
     */
    public void scale(float sx, float sy) {
        x *= sx;
        y *= sy;
    }

    /**
     * Rotate the vector.
     * @param rad The radian that need to rotate. 
     * And an angle should call the function Math.toRadians() 
     * to become a radian.
     */
    public void rotate(float rad) {
        float tmp = (float) (x * Math.cos(rad) - y * Math.sin(rad));
        y = (float) (x * Math.sin(rad) + y * Math.cos(rad));
        x=tmp;
    }

    /**
     * Shear the vector.
     * @param s The parameter the need to shear.
     */
    public void shear(float s) {
        float tmp = x + s * y;
        y = y + s * x;
        x = tmp;
    }

    /**
     * Shear the vector.
     * @param sx The parameter the need to shear in the first dimension.
     * @param sy The parameter the need to shear in the second dimension.
     */
    public void shear(float sx, float sy) {
        float tmp = x + sx * y;
        y = y + sy * x;
        x=tmp;
    }

    /**
     * Change the vector to the opposite one.
     * @return The opposite vector.
     */
    public Vector2f inv() {
        return new Vector2f(-x, -y);
    }

    /**
     * Add two vectors together.
     * @param v The add vector.
     * @return The result vector after adding the two vectors together.
     */
    public Vector2f add(Vector2f v) {
        return new Vector2f(x + v.x, y + v.y);
    }

    /**
     * Subtract two vectors.
     * @param v The subtract vector.
     * @return The result vector after subtracting the two vectors.
     */
    public Vector2f sub(Vector2f v) {
        return new Vector2f(x - v.x, y - v.y);
    }

    /**
     * Multiply the vector with a float number.
     * @param scalar The multiply float number.
     * @return The result vector after multiplying the vector 
     * with a float number.
     */
    public Vector2f mul(float scalar) {
        return new Vector2f(scalar * x, scalar * y);
    }

    /**
     * Divide the vector with a float number.
     * @param scalar The divide float number.
     * @return The result vector after dividing the vector 
     * with a float number.
     */
    public Vector2f div(float scalar) {
        return new Vector2f(x / scalar, y / scalar);
    }

    /**
     * Calculate the square of length of the vector.
     * @return The square of length of the vector.
     */
    public float len() {
        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * Calculate the length of the vector.
     * @return The length of the vector.
     */
    public float lenSqr() {
        return x * x + y * y;
    }

    /**
     * Normalizing the vector.
     * @return The normalization of the vector.
     */
    public Vector2f norm() {
        return div(len());
    }

    /**
     * Calculate the vertical vector of the vector.
     * @return A vertical vector of the vector.
     */
    public Vector2f perp() {
        return new Vector2f(-y, x);
    }

    /**
     * Dot multiply two vectors.
     * @param v The multiply vector.
     * @return The result vector after dot multiplying the two vectors.
     */
    public float dot(Vector2f v) {
        return x * v.x + y * v.y;
    }

    /**
     * Calculate the radian of the vector with the axis.
     * @return The radian of the vector with the axis. 
     * Ranging from Pi to -Pi.
     */
    public float angle() {
        return (float) Math.atan2(y, x);
    }

    /**
     * Create a vector with the radian and the length in 
     * the Polar coordinate system.
     * @param angle The radian of the vector.
     * @param radius The length of the vector.
     * @return The vector that has this radian and length.
     */
    public static Vector2f polar(float angle, float radius) {
        return new Vector2f(
                radius * (float) (Math.cos(angle)),
                radius * (float) (Math.sin(angle))
        );
    }

    @Override
    public String toString() {
        return String.format("(%s,%s)", x, y);
    }

    @Override
    public boolean equals(Object obj) {
        return this.similar((Vector2f) obj, EPSILON);
    }
}

###Matrix3x3f类

Matrix3x3f类是一个表示三维矩阵的类,包含矩阵之间的基本运算以及矩阵与向量的运算。包含一个存储矩阵的3×3数组字段和一个表示维度的静态字段。

在平面的图形绘制过程中,Vector2f类可以表示平面中的一个点,利用Matrix3x3f类可以对Vector2f类执行变换操作,例如,平移,缩放,旋转,切变。可以由一个向量构造一个可以执行变换操作的变换矩阵,乘以需要执行变换操作的点即得到变换之后的点。若需要连续变换多次,可以先将变换矩阵相乘,再乘以需要执行变换操作的点,但是要注意矩阵相乘的顺序决定着变换的顺序。这两个类是平面图形绘制中非常重要的两个类。

  • Matrix3x3f类方法一览表
    |修饰符|返回值|函数名|参数|说明|
    |—|---|—|---|—|
    |public||Matrix3x3f|()|空构造函数|
    |public||Matrix3x3f|(float[][] m)|构造函数|
    |public|Matrix3x3f|add|(Matrix3x3f m1)|返回两个矩阵相加|
    |public|Matrix3x3f|sub|(Matrix3x3f m1)|返回两个矩阵相减|
    |public|Matrix3x3f|mul|(Matrix3x3f m1)|返回两个矩阵相乘|
    |public|void|setMatrix|(float[][] m)|设置矩阵的值|
    |public static|Matrix3x3f|zero|()|返回一个全零矩阵|
    |public static|Matrix3x3f|identity|()|返回一个单位矩阵|
    |public static|Matrix3x3f|translate|(Vector2f v)|返回一个执行平移操作的矩阵|
    |public static|Matrix3x3f|translate|(float x,float y)|返回一个执行平移操作的矩阵|
    |public static|Matrix3x3f|scale|(Vector2f v)|返回一个执行缩放操作的矩阵|
    |public static|Matrix3x3f|scale|(float x,float y)|返回一个执行缩放操作的矩阵|
    |public static|Matrix3x3f|shear|(Vector2f v)|返回一个执行切变操作的矩阵|
    |public static|Matrix3x3f|shear|(float x,float y)|返回一个执行切变操作的矩阵|
    |public static|Matrix3x3f|rotate|(float rad)|返回一个执行旋转操作的矩阵|
    |public|Vector2f|mul|(Vector2f vec)|对需要执行变换的点执行变换操作|
    |public|String|toString|()|打印矩阵|

  • Matrix3x3f类源代码

package Rendering.utils;

/**
 * This class is similar to Tri-dimensional matrix and 
 * has some operating functions.
 */
public class Matrix3x3f {
    public static final int DIMENSION = 3;
    private float[][] m = new float[DIMENSION][DIMENSION];

    public Matrix3x3f() {}

    public Matrix3x3f(float[][] m) {
        setMatrix(m);
    }

    public Matrix3x3f add(Matrix3x3f m1) {
        float[][] tmp = new float[DIMENSION][DIMENSION];
        for (int i = 0; i < DIMENSION; i++) {
            for (int j = 0; j < DIMENSION; j++) {
                tmp[i][j] = m[i][j] + m1.m[i][j];
            }
        }
        return new Matrix3x3f(tmp);
    }

    public Matrix3x3f sub(Matrix3x3f m1) {
        float[][] tmp = new float[DIMENSION][DIMENSION];
        for (int i = 0; i < DIMENSION; i++) {
            for (int j = 0; j < DIMENSION; j++) {
                tmp[i][j] = m[i][j] - m1.m[i][j];
            }
        }
        return new Matrix3x3f(tmp);
    }

    public Matrix3x3f mul(Matrix3x3f m1) {
        float[][] tmp = new float[DIMENSION][DIMENSION];
        for (int i = 0; i < DIMENSION; i++) {
            for (int k = 0; k < DIMENSION; k++) {
                for(int j=0;j<DIMENSION;j++) {
                    tmp[i][j] += m[i][k] * m1.m[k][j];
                }
            }
        }
        return new Matrix3x3f(tmp);
    }

    public void setMatrix(float[][] m) {
        if (m.length == DIMENSION && m[0].length == DIMENSION) {
            this.m = m;
        }
    }

    public static Matrix3x3f zero() {
        return new Matrix3x3f(new float[DIMENSION][DIMENSION]);
    }

    public static Matrix3x3f identity() {
        float[][] tmp = new float[DIMENSION][DIMENSION];
        for (int i = 0; i < DIMENSION; i++) {
            tmp[i][i] = 1.0f;
        }
        return new Matrix3x3f(tmp);
    }

    public static Matrix3x3f translate(Vector2f v) {
        return translate(v.x, v.y);
    }

    public static Matrix3x3f translate(float x, float y) {
        return new Matrix3x3f(new float[][]{
                {1.0f, 0.0f, 0.0f},
                {0.0f, 1.0f, 0.0f},
                {x, y, 1.0f}
        });
    }

    public static Matrix3x3f scale(Vector2f v) {
        return scale(v.x, v.y);
    }

    public static Matrix3x3f scale(float x, float y) {
        return new Matrix3x3f(new float[][]{
                {x, 0.0f, 0.0f},
                {0.0f, y, 0.0f},
                {0.0f, 0.0f, 1.0f}
        });
    }

    public static Matrix3x3f shear(Vector2f v) {
        return shear(v.x, v.y);
    }

    public static Matrix3x3f shear(float x, float y) {
        return new Matrix3x3f(new float[][]{
                {1.0f, y, 0.0f},
                {x, 1.0f, 0.0f},
                {0.0f, 0.0f, 1.0f}
        });
    }

    public static Matrix3x3f rotate(float rad) {
        return new Matrix3x3f(new float[][]{
                {(float) Math.cos(rad), (float) Math.sin(rad), 0.0f},
                {(float) -Math.sin(rad), (float) Math.cos(rad), 0.0f},
                {0.0f, 0.0f, 1.0f}
        });
    }

    public Vector2f mul(Vector2f vec) {
        return new Vector2f(
                vec.x * this.m[0][0]
                        + vec.y * this.m[1][0]
                        + vec.w * this.m[2][0],
                vec.x * this.m[0][1]
                        + vec.y * this.m[1][1]
                        + vec.w * this.m[2][1],
                vec.x * this.m[0][2]
                        + vec.y * this.m[1][2]
                        + vec.w * this.m[2][2]
        );
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        for(int i=0;i<DIMENSION;i++) {
            buf.append("[");
            buf.append(m[i][0]);
            buf.append(",\t");
            buf.append(m[i][1]);
            buf.append(",\t");
            buf.append(m[i][2]);
            buf.append("]\n");
        }
        return buf.toString();
    }
}

###Utility类

Utility类是所有的工具函数的集合类,其中包括坐标转换工具,图形绘制工具,文字绘制工具。Utility类可以引用到任何使用Graphics进行图形绘制的程序中,其中的点,坐标变换分别用Vector2f和Matrix3x3f来表示。

  • Utility类方法一览表
修饰符返回值函数名参数说明
public staticMatrix3x3fcreateViewport(float worldWidth,float worldHeight,float screenWidth,float screenHeight)返回由世界坐标转换到屏幕坐标的转换矩阵
public staticMatrix3x3fcreateReverseViewport(float worldWidth,float worldHeight,float screenWidth,float screenHeight)返回由屏幕坐标转换到世界坐标的转换矩阵
public staticVector2f[]transform(Vector2f[] poly,Matrix3x3f mat)对一系列的点进行指定的转换
public staticvoiddrawPolygon(Graphics2D g2d,Vector2f[] polygon,Matrix3x3f view,Color color)根据点的个数绘制多边形,点,或直线
public staticvoiddrawPolygon(Graphics2D g2d,List< Vector2f> polygon,Matrix3x3f view,Color color)根据点的个数绘制多边形,点,或直线
public staticvoidfillPolygon(Graphics2D g2d,Vector2f[] polygon,Matrix3x3f view,Color color)根据点的个数绘制填充多边形,点,或直线
public staticvoidfillPolygon(Graphics2D g2d,List< Vector2f> polygon,Matrix3x3f view,Color color)根据点的个数填充绘制多边形,点,或直线
public staticvoiddrawRect(Graphics2D g2d,Vector2f topLeft,Vector2f bottomRight,Matrix3x3f view,Color color)由矩形的两个角的位置绘制矩形
public staticvoidfillRect(Graphics2D g2d,Vector2f topLeft,Vector2f bottomRight,Matrix3x3f view,Color color)由矩形的两个角的位置绘制填充矩形
public staticvoiddrawCircle(Graphics2D g2d,Vector2f center,float radius,Matrix3x3f view,Color color)由圆心的位置和半径绘制圆
public staticvoidfillCircle(Graphics2D g2d,Vector2f center,float radius,Matrix3x3f view,Color color)由圆心的位置和半径绘制填充圆
public staticvoiddrawCircle(Graphics2D g2d,Vector2f begin,Vector2f end,Matrix3x3f view,Color color)由圆心和圆上一点的位置绘制圆
public staticvoidfillCircle(Graphics2D g2d,Vector2f begin,Vector2f end,Matrix3x3f view,Color color)由圆心和圆上一点的位置绘制填充圆
public staticvoiddrawOval(Graphics2D g2d,Vector2f topLeft,Vector2f bottomRight,Matrix3x3f view,Color color)由椭圆的外切矩形的两个角的位置绘制椭圆
public staticvoidfillOval(Graphics2D g2d,Vector2f topLeft,Vector2f bottomRight,Matrix3x3f view,Color color)由椭圆的外切矩形的两个角的位置绘制填充椭圆
public staticvoiddrawTriangle(Graphics2D g2d,Vector2f begin,Vector2f center,Matrix3x3f view,Color color)由三角形的一个角和中心的位置绘制等边三角形
public staticvoidfillTriangle(Graphics2D g2d,Vector2f begin,Vector2f center,Matrix3x3f view,Color color)由三角形的一个角和中心的位置绘制填充等边三角形
public staticvoidfillSimilarCircle(Graphics2D g2d,Vector2f position,Matrix3x3f view)由圆心的位置绘制指定的快捷选择操作圆(快捷选择操作中用到)
public staticvoiddrawString(Graphics2D g2d,Vector2f position,Matrix3x3f view,Color color,String… str)在指定位置绘制字符串
public staticvoiddrawString(Graphics2D g2d,Vector2f position,Matrix3x3f view,Color color,List< String> str)在指定位置绘制字符串
public staticvoiddrawString(Graphics2D g2d,Vector2f position,Matrix3x3f view,Color color,String str)在指定位置绘制字符串
  • Utility类源代码
package Rendering.utils;

import Rendering.element.BeginEndImageElement;

import java.awt.*;
import java.util.List;

public class Utility {
    /****************************************************************************/
    /*********************Coordinate transformation tool*************************/
    /****************************************************************************/
    public static Matrix3x3f createViewport(
            float worldWidth, float worldHeight,
            float screenWidth, float screenHeight) {
        float sx = (screenWidth - 1) / worldWidth;
        float sy = (screenHeight - 1) / worldHeight;
        float tx = (screenWidth - 1) / 2.0f;
        float ty = (screenHeight - 1) / 2.0f;
        Matrix3x3f viewport = Matrix3x3f.scale(sx, -sy);
        viewport = viewport.mul(Matrix3x3f.translate(tx, ty));
        return viewport;
    }

    public static Matrix3x3f createReverseViewport(
            float worldWidth, float worldHeight,
            float screenWidth, float screenHeight) {
        float sx = worldWidth / (screenWidth - 1);
        float sy = worldHeight / (screenHeight - 1);
        float tx = (screenWidth - 1) / 2.0f;
        float ty = (screenHeight - 1) / 2.0f;
        Matrix3x3f viewport = Matrix3x3f.translate(-tx, -ty);
        viewport = viewport.mul(Matrix3x3f.scale(sx, -sy));
        return viewport;
    }

    public static Vector2f[] transform(Vector2f[] poly, Matrix3x3f mat) {
        Vector2f[] copy = new Vector2f[poly.length];
        for (int i = 0; i < poly.length; i++) {
            copy[i] = mat.mul(poly[i]);
        }
        return copy;
    }

    /****************************************************************************/
    /*********************Graphic drawing tool***********************************/
    /****************************************************************************/
    public static void drawPolygon(
            Graphics2D g2d, Vector2f[] polygon,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f[] tmp = transform(polygon, view);
        if (tmp.length == 1) {
            g2d.drawRect((int) tmp[0].x, (int) tmp[0].y, 1, 1);
        }
        Vector2f P;
        Vector2f S = tmp[tmp.length - 1];
        for (int i = 0; i < tmp.length; i++) {
            P = tmp[i];
            g2d.drawLine((int) S.x, (int) S.y, (int) P.x, (int) P.y);
            S = P;
        }
    }

    public static void drawPolygon(
            Graphics2D g2d, List<Vector2f> polygon,
            Matrix3x3f view, Color color) {
        drawPolygon(g2d, polygon.toArray(new Vector2f[0]), view, color);
    }

    public static void fillPolygon(
            Graphics2D g2d, Vector2f[] polygon,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f[] tmp = transform(polygon, view);
        Polygon p = new Polygon();
        for (Vector2f v : tmp) {
            p.addPoint((int) v.x, (int) v.y);
        }
        g2d.fillPolygon(p);
    }

    public static void fillPolygon(
            Graphics2D g2d, List<Vector2f> polygon,
            Matrix3x3f view, Color color) {
        fillPolygon(g2d, polygon.toArray(new Vector2f[0]), view, color);
    }

    public static void drawRect(
            Graphics2D g2d, Vector2f topLeft, Vector2f bottomRight,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f topLeftTmp = view.mul(topLeft);
        Vector2f bottomRightTmp = view.mul(bottomRight);
        if (bottomRight.y > topLeft.y) {
            float tmp = topLeftTmp.y;
            topLeftTmp.y = bottomRightTmp.y;
            bottomRightTmp.y = tmp;
        }
        if (bottomRight.x < topLeft.x) {
            float tmp = topLeftTmp.x;
            topLeftTmp.x = bottomRightTmp.x;
            bottomRightTmp.x = tmp;
        }
        int rectX = (int) topLeftTmp.x;
        int rectY = (int) topLeftTmp.y;
        int rectWidth = (int) (bottomRightTmp.x - topLeftTmp.x);
        int rectHeight = (int) (bottomRightTmp.y - topLeftTmp.y);
        g2d.drawRect(rectX, rectY, rectWidth, rectHeight);
    }

    public static void fillRect(
            Graphics2D g2d, Vector2f topLeft, Vector2f bottomRight,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f topLeftTmp = view.mul(topLeft);
        Vector2f bottomRightTmp = view.mul(bottomRight);
        if (bottomRight.y > topLeft.y) {
            float tmp = topLeftTmp.y;
            topLeftTmp.y = bottomRightTmp.y;
            bottomRightTmp.y = tmp;
        }
        if (bottomRight.x < topLeft.x) {
            float tmp = topLeftTmp.x;
            topLeftTmp.x = bottomRightTmp.x;
            bottomRightTmp.x = tmp;
        }
        int rectX = (int) topLeftTmp.x;
        int rectY = (int) topLeftTmp.y;
        int rectWidth = (int) (bottomRightTmp.x - topLeftTmp.x);
        int rectHeight = (int) (bottomRightTmp.y - topLeftTmp.y);
        g2d.fillRect(rectX, rectY, rectWidth, rectHeight);
    }

    public static void drawCircle(
            Graphics2D g2d, Vector2f center, float radius,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f topLeft =
                new Vector2f(center.x - radius, center.y + radius);
        topLeft = view.mul(topLeft);
        Vector2f bottomRight =
                new Vector2f(center.x + radius, center.y - radius);
        bottomRight = view.mul(bottomRight);
        Vector2f centerTmp = view.mul(center);
        int circleX = (int) topLeft.x;
        int circleY = (int) topLeft.y;
        int circleWidth = (int) (bottomRight.x - topLeft.x);
        int circleHeight = (int) (bottomRight.y - topLeft.y);
        g2d.drawRect((int) centerTmp.x, (int) centerTmp.y, 1, 1);
        g2d.drawOval(circleX, circleY, circleWidth, circleHeight);
    }

    public static void fillCircle(
            Graphics2D g2d, Vector2f center, float radius,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f topLeft =
                new Vector2f(center.x - radius, center.y + radius);
        topLeft = view.mul(topLeft);
        Vector2f bottomRight =
                new Vector2f(center.x + radius, center.y - radius);
        bottomRight = view.mul(bottomRight);
        int circleX = (int) topLeft.x;
        int circleY = (int) topLeft.y;
        int circleWidth = (int) (bottomRight.x - topLeft.x);
        int circleHeight = (int) (bottomRight.y - topLeft.y);
        g2d.fillOval(circleX, circleY, circleWidth, circleHeight);
    }

    public static void drawCircle(
            Graphics2D g2d, Vector2f begin, Vector2f end,
            Matrix3x3f view, Color color) {
        drawCircle(g2d, begin, begin.sub(end).len(), view, color);
    }

    public static void fillCircle(
            Graphics2D g2d, Vector2f begin, Vector2f end,
            Matrix3x3f view, Color color) {
        fillCircle(g2d, begin, begin.sub(end).len(), view, color);
    }

    public static void drawOval(
            Graphics2D g2d, Vector2f topLeft, Vector2f bottomRight,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f topLeftTmp = view.mul(topLeft);
        Vector2f bottomRightTmp = view.mul(bottomRight);
        if (bottomRight.y > topLeft.y) {
            float tmp = topLeftTmp.y;
            topLeftTmp.y = bottomRightTmp.y;
            bottomRightTmp.y = tmp;
        }
        if (bottomRight.x < topLeft.x) {
            float tmp = topLeftTmp.x;
            topLeftTmp.x = bottomRightTmp.x;
            bottomRightTmp.x = tmp;
        }
        int ovalX = (int) topLeftTmp.x;
        int ovalY = (int) topLeftTmp.y;
        int ovalWidth = (int) (bottomRightTmp.x - topLeftTmp.x);
        int ovalHeight = (int) (bottomRightTmp.y - topLeftTmp.y);
        g2d.drawOval(ovalX, ovalY, ovalWidth, ovalHeight);
    }

    public static void fillOval(
            Graphics2D g2d, Vector2f topLeft, Vector2f bottomRight,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f topLeftTmp = view.mul(topLeft);
        Vector2f bottomRightTmp = view.mul(bottomRight);
        if (bottomRight.y > topLeft.y) {
            float tmp = topLeftTmp.y;
            topLeftTmp.y = bottomRightTmp.y;
            bottomRightTmp.y = tmp;
        }
        if (bottomRight.x < topLeft.x) {
            float tmp = topLeftTmp.x;
            topLeftTmp.x = bottomRightTmp.x;
            bottomRightTmp.x = tmp;
        }
        int ovalX = (int) topLeftTmp.x;
        int ovalY = (int) topLeftTmp.y;
        int ovalWidth = (int) (bottomRightTmp.x - topLeftTmp.x);
        int ovalHeight = (int) (bottomRightTmp.y - topLeftTmp.y);
        g2d.fillOval(ovalX, ovalY, ovalWidth, ovalHeight);
    }

    public static void drawTriangle(
            Graphics2D g2d, Vector2f begin, Vector2f center,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f tmp = center.sub(begin);
        Vector2f topTmp = begin.add(
                Vector2f.polar(
                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
        Vector2f bottomTmp = begin.add(
                Vector2f.polar(
                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
        drawPolygon(g2d, new Vector2f[]{begin, topTmp, bottomTmp}, view, color);
    }

    public static void fillTriangle(
            Graphics2D g2d, Vector2f begin, Vector2f center,
            Matrix3x3f view, Color color) {
        g2d.setColor(color);
        Vector2f tmp = center.sub(begin);
        Vector2f topTmp = begin.add(
                Vector2f.polar(
                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
        Vector2f bottomTmp = begin.add(
                Vector2f.polar(
                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
        fillPolygon(g2d, new Vector2f[]{begin, topTmp, bottomTmp}, view, color);
    }

    public static void fillSimilarCircle(
            Graphics2D g2d, Vector2f position, Matrix3x3f view) {
        fillCircle(g2d, position, BeginEndImageElement.EPSILON, view, Color.YELLOW);
        Vector2f r = Vector2f.polar((float) (Math.PI / 4), BeginEndImageElement.EPSILON);
        Vector2f rightTop = position.add(r);
        Vector2f leftBottom = position.sub(r);
        Vector2f rightBottom = position.add(r.perp());
        Vector2f leftTop = position.sub(r.perp());
        drawPolygon(g2d, new Vector2f[]{rightTop, leftBottom}, view, Color.BLACK);
        drawPolygon(g2d, new Vector2f[]{leftTop, rightBottom}, view, Color.BLACK);
    }

    /****************************************************************************/
    /************************String drawing tool*********************************/
    /****************************************************************************/
    public static void drawString(
            Graphics2D g2d, Vector2f position,
            Matrix3x3f view, Color color, 
            String... str) {
        g2d.setColor(color);
        Vector2f positionTmp = view.mul(position);
        float x = positionTmp.x;
        float y = positionTmp.y;
        FontMetrics fm = g2d.getFontMetrics();
        int height = fm.getAscent() + fm.getDescent() + fm.getLeading();
        g2d.setFont(new Font("Courier New", Font.PLAIN, 20));
        g2d.setRenderingHint(
                RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        for (String s : str) {
            g2d.drawString(s, x, y + fm.getAscent());
            y += height;
        }
    }

    public static void drawString(
            Graphics2D g2d, Vector2f position,
            Matrix3x3f view, Color color, 
            List<String> str) {
        drawString(g2d, position, view, color, str.toArray(new String[0]));
    }

    public static void drawString(
            Graphics2D g2d, Vector2f position,
            Matrix3x3f view, Color color, 
            String str) {
        drawString(g2d, position, view, color, new String[]{str});
    }
}

##(二) 输入处理工具类

###SafeKeyboardInput类

SafeKeyboardInput类是一个安全的键盘输入处理类,实现了KeyListener接口。当由于种种原因导致帧速率过低时,普通的键盘输入处理可能会漏掉一些事件。本类中使用将所有接受到的键盘事件储存在一个数组中,当需要处理的时候再按照顺序依次处理所有的键盘事件,因此不会漏掉任何键盘事件。但是这样也存在一个问题,如果之前接受到的键盘事件不需要处理,则需要调用flush函数来清空之前接收到的键盘事件。

  • SafeKeyboardInput类字段一览表
修饰符类型名称说明
privateenumEventType表示键盘事件类型的枚举类
privateclassEvent表示键盘事件的类
privateLinkedList< Event>eventThread接收到的键盘事件列表
privateLinkedList< Event>gameThread正在处理的键盘事件列表
privateEventevent当前正在处理的键盘事件
privateint[]polled表示键盘中所有键的数组(共256个)
  • SafeKeyboardInput类方法一览表
修饰符返回值函数名参数说明
publicSafeKeyboardInput()默认构造函数
public synchronizedbooleankeyDown(int keyCode)返回一个键是否被按下
public synchronizedbooleankeyDownOnce(int keyCode)返回一个键是否有且被按下一次
public synchronizedbooleanprocessEvent()处理事件,返回是否还有时间未处理
publicCharactergetKeyTyped()返回单击的字符
public synchronizedvoidpoll()每帧调用,刷新待处理的键盘事件
public synchronizedvoidflush()清除之前未处理的所有事件
public synchronizedvoidkeyPressed(KeyEvent e)键盘按下事件触发
public synchronizedvoidkeyReleased(KeyEvent e)键盘释放事件触发
public synchronizedvoidkeyTyped(KeyEvent e)键盘单击事件触发
  • SafeKeyboardInput类源代码
package Rendering.utils;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.LinkedList;

public class SafeKeyboardInput implements KeyListener {
    private enum EventType {
        PRESSED,
        RELEASED,
        TYPED,
    }

    private class Event {
        KeyEvent event;
        EventType type;

        public Event(KeyEvent event, EventType type) {
            this.event = event;
            this.type = type;
        }
    }

    private LinkedList<Event> eventThread = new LinkedList<Event>();
    private LinkedList<Event> gameThread = new LinkedList<Event>();
    private Event event = null;
    private int[] polled;

    public SafeKeyboardInput() {
        polled = new int[256];
    }

    public synchronized boolean keyDown(int keyCode) {
        return keyCode == event.event.getKeyCode() && polled[keyCode] > 0;
    }

    public synchronized boolean keyDownOnce(int keyCode) {
        return keyCode == event.event.getKeyCode() && polled[keyCode] == 1;
    }

    public synchronized boolean processEvent() {
        event = gameThread.poll();
        if (event != null) {
            int keyCode = event.event.getKeyCode();
            if (keyCode >= 0 && keyCode < polled.length) {
                if (event.type == EventType.PRESSED) {
                    polled[keyCode]++;
                } else if (event.type == EventType.RELEASED) {
                    polled[keyCode] = 0;
                }
            }
        }
        return event != null;
    }

    public Character getKeyTyped() {
        if (event.type != EventType.TYPED) {
            return null;
        } else {
            return event.event.getKeyChar();
        }
    }

    public synchronized void poll() {
        LinkedList<Event> swap = eventThread;
        eventThread = gameThread;
        gameThread = swap;
    }

    public synchronized void flush() {
        eventThread = new LinkedList<Event>();
        gameThread = new LinkedList<Event>();
    }

    public synchronized void keyPressed(KeyEvent e) {
        eventThread.add(new Event(e, EventType.PRESSED));
    }

    public synchronized void keyReleased(KeyEvent e) {
        eventThread.add(new Event(e, EventType.RELEASED));
    }

    public synchronized void keyTyped(KeyEvent e) {
        eventThread.add(new Event(e, EventType.TYPED));
    }
}

###RelativeMouseInput类

RelativeMouseInput类是一个既可以处理绝对鼠标事件,还可以处理相对鼠标事件的鼠标事件处理类,实现了MouseListener接口, MouseMotionListener接口和MouseWheelListener接口。当设置relative字段为false时,处理绝对鼠标事件;设置relative字段为true时,处理相对鼠标事件。

  • RelativeMouseInput类字段一览表
修饰符类型名称说明
private static finalintBUTTON_COUNT表示鼠标所有按键的数目(3)
privatePointmousePos鼠标当前帧的位置
privatePointcurrentPos鼠标的实时位置
privateboolean[]mouse存储鼠标的按键是否被按下的数组
privateint[]polled表示鼠标所有键的数组
privateintnotches鼠标滚轮的转动距离
privateintpolledNotches当前帧鼠标滚轮的转动距离
privateintdx鼠标相对上一帧移动的x值
privateintdy鼠标相对上一帧移动的y值
privateRobotrobot自动实现移动鼠标
privateComponentcomponent鼠标所在的组件
privatebooleanrelative处理绝对或相对鼠标事件
  • RelativeMouseInput类方法一览表
修饰符返回值函数名参数说明
publicRelativeMouseInput(Component component)构造函数
public synchronizedvoidpoll()每帧调用,刷新当前帧的鼠标位置和滚轮位置
public synchronizedbooleanisRelative()返回是否为相对鼠标事件
public synchronizedvoidsetRelative(boolean relative)更改绝对/相对鼠标事件
public synchronizedPointgetPosition()返回鼠标当前帧的绝对位置/相对上一帧的位置
public synchronizedintgetNotches()返回当前帧鼠标滚轮的转动距离
public synchronizedbooleanbuttonDown(int button)返回一个键是否被按下
public synchronizedbooleanbuttonDownOnce(int button)返回一个键是否有且被按下一次
public synchronizedvoidmousePressed(MouseEvent e)鼠标按下事件触发
public synchronizedvoidmouseReleased(MouseEvent e)鼠标释放事件触发
publicvoidmouseClicked(MouseEvent e)鼠标单击事件触发
public synchronizedvoidmouseEntered(MouseEvent e)鼠标进入事件触发
public synchronizedvoidmouseExited(MouseEvent e)鼠标移出事件触发
public synchronizedvoidmouseDragged(MouseEvent e)鼠标拖动事件触发
public synchronizedvoidmouseMoved(MouseEvent e)鼠标移动事件触发
public synchronizedvoidmouseWheelMoved(MouseWheelEvent e)
privatePointgetComponentCenter()返回鼠标所在的组件
privatevoidcenterMouse()移动鼠标到所在组件的中央
publicvoidmoveMouse(Vector2f mousePos)移动鼠标到组件的指定位置
  • RelativeMouseInput类源代码
package Rendering.utils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class RelativeMouseInput
        implements MouseListener, MouseMotionListener, MouseWheelListener {
    private static final int BUTTON_COUNT=3;
    private Point mousePos;
    private Point currentPos;
    private boolean[] mouse;
    private int[] polled;
    private int notches;
    private int polledNotches;
    private int dx, dy;
    private Robot robot;
    private Component component;
    private boolean relative;

    public RelativeMouseInput(Component component) {
        this.component = component;
        try {
            robot = new Robot();
        } catch (Exception e) {
            e.printStackTrace();
        }
        mousePos = new Point(0, 0);
        currentPos = new Point(0, 0);
        mouse = new boolean[BUTTON_COUNT];
        polled = new int[BUTTON_COUNT];
    }

    public synchronized void poll(){
        if (isRelative()) {
            mousePos = new Point(dx, dy);
        } else {
            mousePos = new Point(currentPos);
        }
        dx = dy = 0;
        polledNotches = notches;
        notches = 0;
        for (int i = 0; i < mouse.length; i++) {
            if (mouse[i]) {
                polled[i]++;
            } else {
                polled[i] = 0;
            }
        }
    }

    public synchronized boolean isRelative() {
        return relative;
    }

    public synchronized void setRelative(boolean relative) {
        this.relative = relative;
        if (relative) {
            centerMouse();
        }
    }

    public synchronized Point getPosition() {
        return mousePos;
    }

    public synchronized int getNotches() {
        return polledNotches;
    }

    public synchronized boolean buttonDown(int button) {
        return polled[button-1]>0;
    }

    public synchronized boolean buttonDownOnce(int button) {
        return polled[button-1]==1;
    }

    public synchronized void mousePressed(MouseEvent e) {
        int button=e.getButton()-1;
        if (button >= 0 && button < mouse.length) {
            mouse[button] = true;
        }
    }

    public synchronized void mouseReleased(MouseEvent e) {
        int button=e.getButton()-1;
        if (button >= 0 && button < mouse.length) {
            mouse[button] = false;
        }
    }

    public void mouseClicked(MouseEvent e) {
        //Not needed
    }

    public synchronized void mouseEntered(MouseEvent e) {
        mouseMoved(e);
    }

    public synchronized void mouseExited(MouseEvent e) {
        mouseMoved(e);
    }

    public synchronized void mouseDragged(MouseEvent e) {
        mouseMoved(e);
    }

    public synchronized void mouseMoved(MouseEvent e) {
        if (isRelative()) {
            Point p = e.getPoint();
            Point center = getComponentCenter();
            dx += p.x - center.x;
            dy += p.y - center.y;
            centerMouse();
        } else {
            currentPos = e.getPoint();
        }
    }

    public synchronized void mouseWheelMoved(MouseWheelEvent e) {
        notches += e.getWheelRotation();
    }

    private Point getComponentCenter() {
        int w = component.getWidth();
        int h = component.getHeight();
        return new Point(w / 2, h / 2);
    }

    private void centerMouse() {
        if (robot != null && component.isShowing()) {
            Point center = getComponentCenter();
            SwingUtilities.convertPointToScreen(center, component);
            robot.mouseMove(center.x, center.y);
        }
    }

    public void moveMouse(Vector2f mousePos) {
        if (robot != null && component.isShowing()) {
            Point tmp = new Point((int) mousePos.x, (int) mousePos.y);
            SwingUtilities.convertPointToScreen(tmp, component);
            robot.mouseMove(tmp.x, tmp.y);
        } else {
            JOptionPane.showMessageDialog(
                    component, "Can not support similar!!!",
                    "Warning", JOptionPane.INFORMATION_MESSAGE
            );
        }
    }
}

##(三) 渲染处理工具类

###FrameRate类

FrameRate类是计算帧速率的工具类,在所有需要每帧绘制的程序中都可以引用。

  • FrameRate类字段一览表
修饰符类型名称说明
privateStringframeRate表示帧速率的字符串
privatelonglastTime上一帧的时间
privatelongdelta所有帧的总时间,大于1秒时清零
privateintframeCount表示帧速率的数值
  • FrameRate类方法一览表
修饰符返回值函数名参数说明
publicvoidinitialize()初始化,程序开始时调用一次
publicvoidcalculate()计算帧速率,每帧调用
publicStringgetFrameRate()返回表示帧速率的字符串
  • FrameRate类源代码
package Rendering.utils;

/**
 * This class is used to calculate the FPS(Frames Per Second).
 *
 * At first, you need to call the function initialize() to 
 * initialize the parameters.
 * Then call the function calculate() every time when rendering.
 * Finally, call the function getFrameRate() when you need to use the FPS.
 * @author Mortal
 * @since 1.0
 */
public class FrameRate {
    /**
     * The message String that is needed to display the FPS.
     */
    private String frameRate;

    /**
     * Last time to call the function calculate() or initialize().
     */
    private long lastTime;

    /**
     * Add the millisecond every time when call the function calculate(),
     * When it comes to one second, update the message String 
     * and reset it to zero.
     */
    private long delta;

    /**
     * The count of calling calculate() in one second.
     */
    private int frameCount;

    /**
     * Initialize the parameters.
     */
    public void initialize() {
        lastTime = System.currentTimeMillis();
        frameRate = "FPS 0";
    }

    /**
     * Add the millisecond every time and update the message String 
     * when it comes to one second.
     */
    public void calculate() {
        long current = System.currentTimeMillis();
        delta += current - lastTime;
        lastTime = current;
        frameCount++;
        if (delta > 1000) {
            delta -= 1000;
            frameRate = String.format("FPS %s", frameCount);
            frameCount = 0;
        }
    }

    /**
     * To get to message String of FPS(Frames Per Second).
     * @return The message String of FPS(Frames Per Second).
     */
    public String getFrameRate() {
        return frameRate;
    }
}

###Framework类

Framework类是表示窗口的抽象类,具有一个窗口所需的所用功能及接口(API)。它继承了JFrame类,实现了Runnable接口。调用时使run方法在 AWT 事件指派线程上异步执行。 Framework类是采用了抽象方法的设计模式,在父类中定义方法调用的抽象方法和一些接口(API),而在子类,即主程序类中再根据具体的方法来进行实现,Framework类本身可以引用到任何的窗口程序中。

  • Framework类字段一览表
修饰符类型名称说明
privateBufferStrategybufferStrategy缓冲策略(双缓冲)
private volatilebooleanrunning线程是否正在运行
privateThreadgameThread主线程
protectedintvx视图窗口的x位置
protectedintvy视图窗口的y位置
protectedintvw视图窗口的宽度
protectedintvh视图窗口的高度
protectedFrameRateframeRate计算帧速率的工具
protectedRelativeMouseInputmouse鼠标输入处理类
protectedSafeKeyboardInputkeyboard键盘输入处理类
protectedColorappBackground窗口的背景颜色
protectedColorappBorder窗口的边框颜色
protectedColorappFPSColor显示帧速率的颜色
protectedFontappFont窗口的字体
protectedStringappTitle窗口的题目
protectedfloatappBorderScale视图窗口在主窗口的占比
protectedintappWidth主窗口的宽度
protectedintappHeight主窗口的高度
protectedfloatappWorldWidth世界地图的宽度
protectedfloatappWorldHeight世界地图的高度
protectedlongappSleep每帧的休眠时间
protectedbooleanappMaintainRatio视图窗口是否占据整个主窗口
protectedbooleanappDisableCursor鼠标光标是否可见
  • Framework类方法一览表
修饰符返回值函数名参数说明
publicFramework()默认构造函数
protected abstractvoidcreateFramework()创建整个窗口
protected abstractvoidrenderFrame(Graphics g)渲染整个窗口
public abstractintgetScreenWidth()返回视图窗口的宽度
public abstractintgetScreenHeight()返回视图窗口的高度
protectedvoidcreateAndShowGUI()创建整个窗口并运行主线程
protectedvoidsetupInput(Component component)为指定组件设置键盘事件处理和鼠标事件处理
protectedvoidcreateBufferStrategy(Canvas component)根据窗口组件构造双缓冲策略
protectedvoidcreateBufferStrategy(Window window)根据屏幕窗口构造双缓冲策略(全屏时调用)
protectedvoidsetupViewport(int sw,int sh)根据主窗口的大小和比例设置视图窗口的位置和大小
protectedMatrix3x3fgetViewportTransform()返回世界坐标到视图窗口坐标的转换矩阵
protectedMatrix3x3fgetReverseViewportTransform()返回视图窗口坐标到世界坐标的转换矩阵
publicVector2fgetWorldMousePosition()返回映射到世界坐标上的鼠标绝对位置
protectedVector2fgetRelativeWorldMousePosition()返回映射到世界坐标上的鼠标相对上一帧的位置
publicvoidrun()初始化并运行主线程
protectedvoidinitialize()执行初始化操作
protectedvoidterminate()执行资源的释放操作
privatevoidgameLoop(float delta)主线程中的主循环
privatevoidrenderFrame()从双缓冲策略中获取需要绘制的Graphics实例并进行绘制
privatevoidsleep(long sleep)主线程休眠指定时间
protectedvoidprocessInput(float delta)处理输入
protectedvoidupdateObject(float delta)更新每个需要绘制的图形对象
protectedvoidrender(Graphics g)每帧渲染视图窗口
privatevoiddisableCursor()设置鼠标光标不可见
protectedvoidshutDown()关闭窗口
protectedvoidonShutDown()关闭窗口后需要执行的操作
protected staticvoidlaunchApp(final Frameworkapp)使用该静态执行主程序,使run方法在 AWT 事件指派线程上异步执行
  • Framework类源代码
package Rendering.utils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;

public abstract class Framework extends JFrame implements Runnable {
    private BufferStrategy bufferStrategy;
    private volatile boolean running;
    private Thread gameThread;

    protected int vx;
    protected int vy;
    protected int vw;
    protected int vh;
    protected FrameRate frameRate;
    protected RelativeMouseInput mouse;
    protected SafeKeyboardInput keyboard;
    protected Color appBackground = Color.BLACK;
    protected Color appBorder = Color.LIGHT_GRAY;
    protected Color appFPSColor = Color.GREEN;
    protected Font appFont = new Font("Courier New", Font.PLAIN, 14);
    protected String appTitle = "TBD-Title";
    protected float appBorderScale = 0.8f;
    protected int appWidth = 640;
    protected int appHeight = 640;
    protected float appWorldWidth = 2.0f;
    protected float appWorldHeight = 2.0f;
    protected long appSleep = 10L;
    protected boolean appMaintainRatio = false;
    protected boolean appDisableCursor = false;

    public Framework() {}

    protected abstract void createFramework();

    protected abstract void renderFrame(Graphics g);

    public abstract int getScreenWidth();

    public abstract int getScreenHeight();

    protected void createAndShowGUI() {
        createFramework();
        if (appDisableCursor) {
            disableCursor();
        }
        gameThread = new Thread(this);
        gameThread.start();
    }

    protected void setupInput(Component component) {
        keyboard = new SafeKeyboardInput();
        component.addKeyListener(keyboard);
        mouse = new RelativeMouseInput(component);
        component.addMouseListener(mouse);
        component.addMouseMotionListener(mouse);
        component.addMouseWheelListener(mouse);
    }

    protected void createBufferStrategy(Canvas component) {
        component.createBufferStrategy(2);
        bufferStrategy = component.getBufferStrategy();
    }

    protected void createBufferStrategy(Window window) {
        window.createBufferStrategy(2);
        bufferStrategy = window.getBufferStrategy();
    }

    protected void setupViewport(int sw, int sh) {
        int w = (int) (sw * appBorderScale);
        int h = (int) (sh * appBorderScale);
        vw = w;
        vh = (int) (w * appWorldHeight / appWorldWidth);
        if (vh > h) {
            vw = (int) (h * appWorldWidth / appWorldHeight);
            vh = h;
        }
        vx = (sw - vw) / 2;
        vy = (sh - vh) / 2;
    }

    protected Matrix3x3f getViewportTransform() {
        return Utility.createViewport(
                appWorldWidth, appWorldHeight,
                getScreenWidth(), getScreenHeight());
    }

    protected Matrix3x3f getReverseViewportTransform() {
        return Utility.createReverseViewport(
                appWorldWidth, appWorldHeight,
                getScreenWidth(), getScreenHeight());
    }

    public Vector2f getWorldMousePosition() {
        Matrix3x3f screenToWorld = getReverseViewportTransform();
        Point mousePos = mouse.getPosition();
        Vector2f screenPos = new Vector2f(mousePos.x, mousePos.y);
        return screenToWorld.mul(screenPos);
    }

    protected Vector2f getRelativeWorldMousePosition() {
        float sx = appWorldWidth / (getScreenWidth() - 1);
        float sy = appWorldHeight / (getScreenHeight() - 1);
        Matrix3x3f viewport = Matrix3x3f.scale(sx, -sy);
        Point p = mouse.getPosition();
        return viewport.mul(new Vector2f(p.x, p.y));
    }

    public void run() {
        running = true;
        initialize();
        long curTime = System.nanoTime();
        long lastTime = curTime;
        double nsPerTime;
        while (running) {
            curTime = System.nanoTime();
            nsPerTime = curTime - lastTime;
            gameLoop((float) (nsPerTime / 1.0E9));
            lastTime = curTime;
        }
        terminate();
    }

    protected void initialize() {
        frameRate = new FrameRate();
        frameRate.initialize();
    }

    protected void terminate() {}

    private void gameLoop(float delta) {
        processInput(delta);
        updateObject(delta);
        renderFrame();
        sleep(appSleep);
    }

    private void renderFrame() {
        do{
            do{
                Graphics g = null;
                try{
                    g = bufferStrategy.getDrawGraphics();
                    renderFrame(g);
                }finally {
                    if(g!=null){
                        g.dispose();
                    }
                }
            }while(bufferStrategy.contentsRestored());
            bufferStrategy.show();
        }while(bufferStrategy.contentsLost());
    }

    private void sleep(long sleep) {
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException ex) {}
    }

    protected void processInput(float delta) {
        keyboard.poll();
        mouse.poll();
    }

    protected void updateObject(float delta) {}

    protected void render(Graphics g) {
        g.setFont(appFont);
        g.setColor(appFPSColor);
        frameRate.calculate();
        g.drawString(frameRate.getFrameRate(),20, 20);
    }

    private void disableCursor() {
        Toolkit tk = Toolkit.getDefaultToolkit();
        Image image = tk.createImage("");
        Point point = new Point(0, 0);
        String name = "CanBeAnything";
        Cursor cursor = tk.createCustomCursor(image, point, name);
        setCursor(cursor);
    }

    protected void shutDown() {
        if (Thread.currentThread() != gameThread) {
            try {
                running = false;
                gameThread.join();
                onShutDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.exit(0);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    shutDown();
                }
            });
        }
    }

    protected void onShutDown() {}

    protected static void launchApp(final Framework app) {
        app.addWindowListener(new WindowAdapter() {
            /**
             * Invoked when a window is in the process of being closed.
             * The close operation can be overridden at this point.
             *
             * @param e
             */
            @Override
            public void windowClosing(WindowEvent e) {
                app.shutDown();
            }
        });
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                app.createAndShowGUI();
            }
        });
    }
}

###SwingFramework类

SwingFramework类是使用Swing实现的Framework类,主窗口和视图窗口为JPanel实例,在视图窗口的Canvas实例上进行图形的绘制。

  • SwingFramework类字段一览表
修饰符类型名称说明
protectedCanvascanvas进行图形绘制的画布
privateJPanelmainPanel主窗口
privateJPanelcenterPanel视图窗口
  • SwingFramework类方法一览表
修饰符返回值函数名参数说明
protectedJPanelgetMainPanel()返回主窗口
privateJPanelgetCenterPanel()返回视图窗口
privateCanvasgetCanvas()返回绘制的画布
privatevoidsetUpLookAndFeel()设置主题
protectedvoidonCreateAndShowGUI()创建窗口细节
protectedvoidcreateFramework()创建整个窗口
protectedvoidonComponentResized(ComponentEvent e)窗口大小改变时调用,重新绘制画布
protectedvoidrenderFrame(Graphics g)渲染整个窗口
publicintgetScreenWidth()返回视图窗口的宽度,即画布的宽度
publicintgetScreenHeight()返回视图窗口的高度,即画布的高度
  • SwingFramework类源代码
package Rendering.utils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

public class SwingFramework extends Framework {
    protected Canvas canvas;
    private JPanel mainPanel;
    private JPanel centerPanel;

    protected JPanel getMainPanel() {
        if (mainPanel == null) {
            mainPanel = new JPanel();
            mainPanel.setLayout(new BorderLayout());
            mainPanel.add(getCenterPanel(), BorderLayout.CENTER);
        }
        return mainPanel;
    }

    private JPanel getCenterPanel() {
        if (centerPanel == null) {
            centerPanel = new JPanel();
            centerPanel.setBackground(appBorder);
            centerPanel.setLayout(null);
            centerPanel.add(getCanvas());
        }
        return centerPanel;
    }

    private Canvas getCanvas() {
        if (canvas == null) {
            canvas = new Canvas();
            canvas.setBackground(appBackground);
        }
        return canvas;
    }

    private void setUpLookAndFeel() {
        try {
            UIManager.setLookAndFeel(
                    "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected void onCreateAndShowGUI() {}

    @Override
    protected void createFramework() {
        setUpLookAndFeel();
        getContentPane().add(getMainPanel());
        setLocationByPlatform(true);
        setSize(appWidth, appHeight);
        setTitle(appTitle);
        getContentPane().setBackground(appBorder);
        getContentPane().addComponentListener(new ComponentAdapter() {
            /**
             * Invoked when the component's size changes.
             *
             * @param e
             */
            @Override
            public void componentResized(ComponentEvent e) {
                onComponentResized(e);
            }
        });
        setupInput(getCanvas());
        onCreateAndShowGUI();
        setVisible(true);
        createBufferStrategy(getCanvas());
        getCanvas().requestFocus();
    }

    protected void onComponentResized(ComponentEvent e) {
        Dimension size = getContentPane().getSize();
        setupViewport(size.width, size.height);
        getCanvas().setLocation(vx, vy);
        getCanvas().setSize(vw, vh);
        //改变大小时重新绘制
        getCanvas().repaint();
    }

    @Override
    protected void renderFrame(Graphics g) {
        g.clearRect(0, 0, getScreenWidth(), getScreenHeight());
        render(g);
    }

    @Override
    public int getScreenWidth() {
        return getCanvas().getWidth();
    }

    @Override
    public int getScreenHeight() {
        return getCanvas().getHeight();
    }

    public static void main(String[] args) {
        launchApp(new SwingFramework());
    }
}

#图形元素包详细解析

##ImageElement类

ImageElement类是所有图形元素类的父类,默认颜色为黑色,默认为非空,且有一个抽象函数draw,参数为绘制的Graphics对象和坐标转换矩阵,表示当前图形元素的绘制策略。

  • ImageElement类源代码
package Rendering.element;

import Rendering.utils.Matrix3x3f;

import java.awt.*;

public abstract class ImageElement {
    protected Color color = Color.BLACK;

    public boolean empty() {
        return false;
    }

    public abstract void draw(Graphics2D g2d, Matrix3x3f view);
}

##Points类

Points类表示一系列点的集合,由一个列表来存储所有的点,并拥有以下方法:

  • public boolean addPoint(Vector2f point):添加一个点

  • public void draw(Graphics2D g2d, Matrix3x3f view):在指定画布上根据坐标转换矩阵绘制

  • public Points remove():移除一些连续的点,即移除一条曲线

  • public boolean empty():列表中不包含任何点时则表示此对象为空

  • Points类源代码

package Rendering.element;

import Rendering.utils.Matrix3x3f;
import Rendering.utils.Utility;
import Rendering.utils.Vector2f;

import java.awt.*;
import java.util.Vector;

public class Points extends ImageElement {
    private Vector<Vector2f> points = new Vector<>();

    public Points() {}

    public Points(Color color) {
        this.color = color;
    }

    public boolean addPoint(Vector2f point) {
        return points.add(point);
    }

    @Override
    public void draw(Graphics2D g2d, Matrix3x3f view) {
        if (points.size() > 0) {
            Vector2f P = points.get(0);
            Vector2f S;
            for (int i = 1; i < points.size(); i++) {
                S = points.get(i);
                if (!(P == null || S == null)) {
                    Utility.drawPolygon(g2d, new Vector2f[]{P, S},
                            view, color);
                }
                P = S;
            }
        }
    }

    public Points remove() {
        if (points.size() > 0 && points.get(points.size() - 1) == null) {
            points.remove(points.size() - 1);
        }
        while (points.size() > 0) {
            Vector2f tmp = points.remove(points.size() - 1);
            if (tmp == null) {
                return this;
            }
        }
        return this;
    }

    @Override
    public boolean empty() {
        return points.size() == 0;
    }
}

##Similarable接口

Similarable接口声明了快捷选择操作接口(API),包含一个EPSILON字段和一个similar函数。快捷选择操作的理念是:当你想要选择到一个特殊点时,例如一条线段的中点,直接通过肉眼来使用鼠标点击的方式并不准确,所以本程序中设置了当鼠标距离一个特殊点的距离足够近时,会有选择特殊点的提示,通过单击鼠标的滚轮,可以快捷选择到这个特殊点。

  • EPSILON字段表示快捷选择操作可以执行时距离特殊点的最小距离。
  • similar函数的参数为鼠标当前位置的世界坐标,返回值为当前可以快捷选择到的点,若没有这样的点则返回值为空。
  • Similarable接口源代码
package Rendering.element;

import Rendering.utils.Vector2f;

public interface Similarable {
    public static final float EPSILON = 0.2f;

    public abstract Vector2f similar(Vector2f v);
}

##BeginEndImageElement类

BeginEndImageElement类是本程序中使用到的最重要的一个图形元素类,它继承了ImageElement类,实现了Similarable接口,表示这个图形元素是可以执行快捷选择操作的。(关于快捷选择操作的具体实现,后续会详细介绍)这个图形元素类代表了所有的可以通过起点拖动到终点绘制的图形元素。包含,线段,矩形,等边三角形,圆和椭圆

BeginEndImageElement类除了必须的构造函数和继承自父类的draw函数之外,新增了两个字段,begin和end,分别表示图形元素的起点和终点;重载了empty方法,当两个点没有全部确定或两个点相同时则判定为空。这个类在后面并没有具体实现,而只是在每次具体绘制时使用匿名类的方式实现具体的绘制函数,这样减少了很多代码量,也使程序更加简洁明了。

  • BeginEndImageElement类源代码
package Rendering.element;

import Rendering.utils.Matrix3x3f;
import Rendering.utils.Vector2f;

import java.awt.*;

public abstract class BeginEndImageElement
        extends ImageElement implements Similarable {
    protected Vector2f begin;
    protected Vector2f end;

    public BeginEndImageElement(Vector2f begin, Vector2f end) {
        this.begin = begin;
        this.end = end;
    }

    public BeginEndImageElement(Vector2f begin, Vector2f end, Color color) {
        this(begin, end);
        this.color = color;
    }

    @Override
    public abstract void draw(Graphics2D g2d, Matrix3x3f view);
    
    @Override
    public boolean empty() {
        if (begin == null || end == null) {
            return true;
        } else {
            return begin.equals(end);
        }
    }
}

##FillPolygon类

FillPolygon类表示填充多边形类,它继承了ImageElement类,实现了Similarable接口。**因为快捷选择操作的存在,多边形可以通过线段的连接来绘制,但是填充多边形则不可以,所以在本程序中对填充多边形做特殊处理。**这是本程序中除了点列只外第二个不能由起点拖动到终点绘制的图形元素,也是本程序中实现最复杂的一个图形元素。

  • FillPolygon类字段一览表
修饰符类型名称说明
protectedVector< Vector2f>polygon表示填充多边形中的点
protectedbooleanclosed表示填充多边形是否闭合,即是否完成绘制
  • FillPolygon类方法一览表
修饰符返回值函数名参数说明
publicFillPolygon()默认构造函数,默认颜色为继承自父类的黑色
publicFillPolygon(Color color)构造函数
publicFillPolygon(FillPolygon fillPolygon,Color color)构造函数,更改颜色时调用
publicbooleanaddPoint(Vector2f point)向填充多边形中添加一个点
publicbooleanremovePoint()在填充多边形中删除一个点
publicvoidclose()设置填充多边形闭合
publicbooleanisClosed()返回填充多边形是否闭合
publicvoiddraw(Graphics2D g2d,Matrix3x3f view)实现了父类的具体的绘制策略
publicbooleanempty()返回是否为空,当点数小于2时判定为空
publicVector2fsimilar(Vector2f mousePos)实现了快捷选择操作接口(API),可以快捷选择到填充多边形上的所有点
  • FillPolygon类源代码
package Rendering.element;

import Rendering.utils.Matrix3x3f;
import Rendering.utils.Utility;
import Rendering.utils.Vector2f;

import java.awt.*;
import java.util.Vector;

public class FillPolygon
        extends ImageElement implements Similarable {
    protected Vector<Vector2f> polygon = new Vector<>();
    protected boolean closed = false;

    public FillPolygon() {}

    public FillPolygon(Color color) {
        this.color = color;
    }

    public FillPolygon(FillPolygon fillPolygon, Color color) {
        this(color);
        polygon = fillPolygon.polygon;
        closed = fillPolygon.isClosed();
    }

    public boolean addPoint(Vector2f point) {
        return polygon.add(point);
    }

    public boolean removePoint() {
        if (closed) {
            closed = false;
            return true;
        } else if (polygon.size() > 0) {
            polygon.remove(polygon.size() - 1);
            return true;
        } else {
            return false;
        }
    }

    public void close() {
        closed = true;
    }

    public boolean isClosed() {
        return closed;
    }

    @Override
    public void draw(Graphics2D g2d, Matrix3x3f view) {
        if (polygon.size() > 2 && closed) {
            Utility.fillPolygon(g2d, polygon, view, color);
        } else if (polygon.size() > 0) {
            Vector2f P = polygon.get(0);
            Vector2f S;
            for (int i = 0; i < polygon.size(); i++) {
                S = polygon.get(i);
                Utility.drawPolygon(g2d, new Vector2f[]{P, S}, view, color);
                P = S;
            }
        }
    }

    @Override
    public boolean empty() {
        return polygon.size() < 2;
    }

    @Override
    public Vector2f similar(Vector2f mousePos) {
        for (Vector2f v : polygon) {
            if (v.similar(mousePos, EPSILON)) {
                return v;
            }
        }
        return null;
    }
}

##StringElement类

StringElement类是表示字符串绘制的类,它继承了ImageElement类,新增了两个字段——字符串左上角的位置和字符串的内容,且实现了父类的具体的绘制策略。

  • StringElement类源代码
package Rendering.element;

import Rendering.utils.Matrix3x3f;
import Rendering.utils.Utility;
import Rendering.utils.Vector2f;

import java.awt.*;

public class StringElement extends ImageElement {
    protected Vector2f position;
    protected String message;

    public StringElement(Vector2f position, String message) {
        this.position = position;
        this.message = message;
    }

    public StringElement(Vector2f position, String message, Color color) {
        this(position, message);
        this.color = color;
    }

    @Override
    public void draw(Graphics2D g2d, Matrix3x3f view) {
        Utility.drawPolygon(g2d, new Vector2f[]{position}, view, color);
        Utility.drawString(g2d, position, view, color, message);
    }
}

##SaveImageElement类

SaveImageElement类继承了ImageElement类,但是没有做任何的具体实现。在绘制过程中,图形元素列表顶端插入一个SaveImageElement实例以表示之前的所有图形元素已经保存。

  • SaveImageElement类源代码
package Rendering.element;

import Rendering.utils.Matrix3x3f;

import java.awt.*;

public class SaveImageElement extends ImageElement {
    @Override
    public void draw(Graphics2D g2d, Matrix3x3f view) {}
}

#绘制策略包详细解析

##Strategy类

Strategy类是所有绘图策略类的父类,绘图策略类是采用了策略模式的设计模式,思想是根据用户绘图的选择,把画布和输入的处理委托给一个策略实例来执行,而用户更换选择时,只需要更换具体的策略即可,不需要对程序进行修改。

  • Strategy类字段一览表
修饰符类型名称说明
protectedEditoreditor策略类所要处理的主程序
protectedSafeKeyboardInputkeyboard策略类需要处理的键盘输入
protectedRelativeMouseInputmouse策略类需要处理的鼠标输入
protectedArrayList< ImageElement>elements策略类可以直接对将要绘制的元素进行更改
protectedColorcolor当前绘图颜色,默认值为黑色
protectedbooleandrawing当前是否开始绘制,默认值为false
  • Strategy类方法一览表
修饰符返回值函数名参数说明
publicStrategy(Editor editor,SafeKeyboardInput keyboard,RelativeMouseInput mouse,ArrayList< ImageElement> elements)构造函数
publicvoidsetColor(Color color)设置当前绘制的颜色
publicvoidfinishDrawing()完成一个图形的绘制后需要调用的处理操作
protetedvoidgoBackForOneStep()撤销一步操作,移除图形元素列表最顶端的一个非空元素
publicvoidprocessInput()处理输入,先处理撤销操作,再处理绘制操作
proteted abstractvoidprocess()处理输入进行绘制操作
protetedvoidjump(Vector2f mousePos)快捷选择到某位置之后的操作
publicvoidsimilar(Vector2f mousePos,Matrix3x3f view)快捷选择到某位置
  • Strategy类源代码
package Rendering.strategy;

import Rendering.Editor;
import Rendering.element.ImageElement;
import Rendering.element.Points;
import Rendering.utils.Matrix3x3f;
import Rendering.utils.RelativeMouseInput;
import Rendering.utils.SafeKeyboardInput;
import Rendering.utils.Vector2f;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

public abstract class Strategy {
    protected Editor editor;
    protected SafeKeyboardInput keyboard;
    protected RelativeMouseInput mouse;
    protected ArrayList<ImageElement> elements;
    protected Color color;
    protected boolean drawing;

    public Strategy(Editor editor,
                    SafeKeyboardInput keyboard,
                    RelativeMouseInput mouse,
                    ArrayList<ImageElement> elements) {
        this.editor = editor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.elements = elements;
        this.color = Color.BLACK;
        this.drawing = false;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public void finishDrawing() {
        drawing = false;
    }

    protected void goBackForOneStep() {
        if (mouse.buttonDownOnce(MouseEvent.BUTTON3) && elements.size() > 0) {
            finishDrawing();
            ImageElement tmp = elements.remove(elements.size() - 1);
            while (elements.size() > 0 && tmp.empty()) {
                tmp = elements.remove(elements.size() - 1);
            }
            if (tmp instanceof Points) {
                tmp = ((Points) tmp).remove();
                if (!tmp.empty()) {
                    elements.add(tmp);
                }
            } else if (tmp instanceof FillPolygon) {
                if (((FillPolygon) tmp).removePoint()) {
                    if (!tmp.empty()) {
                        elements.add(tmp);
                        if (this instanceof FillPolygonStrategy) {
                            ((FillPolygonStrategy) this).polygon = (FillPolygon) tmp;
                            ((FillPolygonStrategy) this).drawing = true;
                        }
                    }
                }
            }
        }
    }

    public void processInput() {
        goBackForOneStep();
        process();
    }

    protected abstract void process();

    protected void jump(Vector2f mousePos) {}

    public void similar(Vector2f mousePos, Matrix3x3f view) {
        mouse.moveMouse(view.mul(mousePos));
        jump(mousePos);
    }
}

##NullStrategy类

NullStrategy类是一个对输入不做任何处理的类。适用于对图像的观察模式,无法对当前绘制的图形做任何修改。

  • NullStrategy类源代码
package Rendering.strategy;

import Rendering.Editor;
import Rendering.element.ImageElement;
import Rendering.utils.RelativeMouseInput;
import Rendering.utils.SafeKeyboardInput;

import java.util.ArrayList;

public class NullStrategy extends Strategy {
    public NullStrategy(Editor editor,
                        SafeKeyboardInput keyboard,
                        RelativeMouseInput mouse,
                        ArrayList<ImageElement> elements) {
        super(editor, keyboard, mouse, elements);
    }

    @Override
    protected void goBackForOneStep() {}

    @Override
    public void process() {}
}

##PointsStrategy类

PointsStrategy类是对点列的绘制策略,继承了Strategy类。新增Points字段,代表当前绘制的点列实例;重写了setColor和process方法,使其适用于对点列的绘制。

  • PointsStrategy类源代码
package Rendering.strategy;

import Rendering.Editor;
import Rendering.element.ImageElement;
import Rendering.element.Points;
import Rendering.utils.RelativeMouseInput;
import Rendering.utils.SafeKeyboardInput;;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

public class PointsStrategy extends Strategy {
    protected Points points;

    public PointsStrategy(Editor editor,
                          SafeKeyboardInput keyboard,
                          RelativeMouseInput mouse,
                          ArrayList<ImageElement> elements) {
        super(editor, keyboard, mouse, elements);
    }

    @Override
    public void setColor(Color color) {
        super.setColor(color);
        points = new Points(this.color);
        elements.add(points);
    }

    @Override
    public void process() {
        if (elements.size() < 1 || elements.get(elements.size() - 1) != points) {
            points = new Points(this.color);
            elements.add(points);
        }
        if (mouse.buttonDownOnce(MouseEvent.BUTTON1)) {
            drawing = true;
        }
        if (mouse.buttonDown(MouseEvent.BUTTON1)) {
            points.addPoint(editor.getWorldMousePosition());
        } else if (drawing) {
            points.addPoint(null);
            drawing =false;
        }
    }
}

##BeginEndStrategy类

BeginEndStrategy类继承了Strategy类,是对所有的可以通过起点拖动到终点绘制的图形元素的一种绘制策略。除了新增两个字段表示起点和终点,重写了finishDrawing,process和jump方法以适应快捷选择操作之外,还新增了以下3个对于拖动绘制的图形通用的方法。

  • protected abstract void addElement():添加一个图形元素。
  • protected void removeElement():移除顶层图形元素。
  • protected void updateElement():每帧更新图形元素。

其中addElement方法在实际使用时需要根据具体绘制的图形来进行重写。

  • BeginEndStrategy类源代码
package Rendering.strategy;

import Rendering.Editor;
import Rendering.element.ImageElement;
import Rendering.utils.Matrix3x3f;
import Rendering.utils.RelativeMouseInput;
import Rendering.utils.SafeKeyboardInput;
import Rendering.utils.Vector2f;

import java.awt.event.MouseEvent;
import java.util.ArrayList;

public abstract class BeginEndStrategy extends Strategy {
    protected Vector2f begin;
    protected Vector2f end;

    public BeginEndStrategy(Editor editor,
                            SafeKeyboardInput keyboard,
                            RelativeMouseInput mouse,
                            ArrayList<ImageElement> elements) {
        super(editor, keyboard, mouse, elements);
    }

    @Override
    public void finishDrawing() {
        super.finishDrawing();
        begin = null;
        end = null;
    }

    protected abstract void addElement();

    protected void removeElement() {
        elements.remove(elements.size() - 1);
    }

    protected void updateElement() {
        removeElement();
        addElement();
    }

    @Override
    public void process() {
        if (mouse.buttonDownOnce(MouseEvent.BUTTON1) && begin == null) {
            drawing = true;
            begin = editor.getWorldMousePosition();
            end = editor.getWorldMousePosition();
            addElement();
            mouse.poll();
        }
        if (drawing) {
            end = editor.getWorldMousePosition();
            updateElement();
        }
        if (mouse.buttonDownOnce(MouseEvent.BUTTON1) && begin != null) {
            finishDrawing();
        }
    }

    @Override
    protected void jump(Vector2f mousePos) {
        if (begin == null) {
            drawing = true;
            begin = mousePos;
            end = mousePos;
            addElement();
            mouse.poll();
        } else {
            end = mousePos;
            updateElement();
            finishDrawing();
        }
    }
}

##FillPolygonStrategy类

FillPolygonStrategy类继承了Strategy类,是填充多边形的绘制策略类。与BeginEndStrategy类具有类似的绘制策略,但由于输入的处理策略不同,不能继承自BeginEndStrategy类。

  • FillPolygonStrategy类源代码
package Rendering.strategy;

import Rendering.Editor;
import Rendering.element.FillPolygon;
import Rendering.element.ImageElement;
import Rendering.utils.RelativeMouseInput;
import Rendering.utils.SafeKeyboardInput;
import Rendering.utils.Vector2f;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

public class FillPolygonStrategy extends Strategy {
    protected FillPolygon polygon;

    public FillPolygonStrategy(Editor editor,
                               SafeKeyboardInput keyboard,
                               RelativeMouseInput mouse,
                               ArrayList<ImageElement> elements) {
        super(editor, keyboard, mouse, elements);
    }

    @Override
    public void setColor(Color color) {
        super.setColor(color);
        if (polygon != null) {
            polygon = new FillPolygon(polygon, this.color);
            updateElement();
        }
    }

    protected void addElement() {
        elements.add(polygon);
    }

    protected void removeElement() {
        elements.remove(elements.size() - 1);
    }

    protected void updateElement() {
        removeElement();
        addElement();
    }

    @Override
    public void finishDrawing() {
        super.finishDrawing();
        polygon = null;
    }

    @Override
    public void process() {
        if (mouse.buttonDownOnce(MouseEvent.BUTTON1) &&
                (polygon == null || (polygon != null && polygon.isClosed()))) {
            drawing = true;
            polygon = new FillPolygon(color);
            polygon.addPoint(editor.getWorldMousePosition());
            addElement();
            mouse.poll();
            keyboard.flush();
        }
        if (mouse.buttonDownOnce(MouseEvent.BUTTON1) && drawing) {
            polygon.addPoint(editor.getWorldMousePosition());
            updateElement();
            mouse.poll();
            keyboard.flush();
        }
        while (keyboard.processEvent()) {
            if (keyboard.keyDownOnce(KeyEvent.VK_C)) {
                updateElement();
                polygon.close();
                finishDrawing();
            }
        }
    }

    @Override
    protected void jump(Vector2f mousePos) {
        super.jump(mousePos);
        if (polygon == null || (polygon != null && polygon.isClosed())) {
            drawing = true;
            polygon = new FillPolygon(color);
            polygon.addPoint(mousePos);
            addElement();
            mouse.poll();
            keyboard.flush();
        } else {
            polygon.addPoint(mousePos);
            updateElement();
            mouse.poll();
            keyboard.flush();
        }
    }
}

##StringStrategy类

StringStrategy类继承了Strategy类,是字符串的绘制策略类。和FillPolygonStrategy类一样,虽然与BeginEndStrategy类具有类似的绘制策略,但由于输入的处理策略不同,不能继承自BeginEndStrategy类。

  • StringStrategy类源代码
package Rendering.strategy;

import Rendering.Editor;
import Rendering.element.ImageElement;
import Rendering.element.StringElement;
import Rendering.utils.RelativeMouseInput;
import Rendering.utils.SafeKeyboardInput;
import Rendering.utils.Vector2f;

import java.awt.event.MouseEvent;
import java.util.ArrayList;

public class StringStrategy extends Strategy {
    protected Vector2f position;
    protected StringBuffer message;

    public StringStrategy(Editor editor,
                          SafeKeyboardInput keyboard,
                          RelativeMouseInput mouse,
                          ArrayList<ImageElement> elements) {
        super(editor, keyboard, mouse, elements);
    }

    @Override
    public void finishDrawing() {
        super.finishDrawing();
        position = null;
        message = null;
    }

    protected void addElement() {
        elements.add(new StringElement(position, message.toString(), color));
    }

    protected void removeElement() {
        elements.remove(elements.size() - 1);
    }

    protected void updateElement() {
        removeElement();
        addElement();
    }

    @Override
    public void process() {
        if (mouse.buttonDownOnce(MouseEvent.BUTTON1) && position == null) {
            drawing = true;
            position = editor.getWorldMousePosition();
            message = new StringBuffer().append("");
            addElement();
            mouse.poll();
            keyboard.flush();
        }
        if (drawing) {
            while (keyboard.processEvent()) {
                Character tmpChar = keyboard.getKeyTyped();
                if (tmpChar != null) {
                    if (Character.isISOControl(tmpChar)) {
                        if (KeyEvent.VK_BACK_SPACE == tmpChar 
                                && message.length() > 0) {
                                message.deleteCharAt(message.length() - 1);
                        }
                    } else {
                        message.append(tmpChar);
                    }
                }
            }
            updateElement();
        }
        if (mouse.buttonDownOnce(MouseEvent.BUTTON1) && position != null) {
            finishDrawing();
        }
    }

    @Override
    protected void jump(Vector2f mousePos) {
        super.jump(mousePos);
        if (position == null) {
            drawing = true;
            position = mousePos;
            message = new StringBuffer().append("");
            addElement();
            mouse.poll();
            keyboard.flush();
        }
    }
}


#主程序

以上所有的铺垫工作都已经完毕,接下来主程序的绘制异常简单,如果有不明白的地方可以在返回之前的示例中查看。实际上,最后的测试阶段会发现Bug大都出现在前面的铺垫中,主程序只是将它们做一个有机的相加的过程。下面我将对主程序的每一个部分做详细解释。

##字段声明

  • 主程序中的字段一览表
    |修饰符|类型|名称|说明|
    |—|---|—|---|
    |protected|ArrayList< ImageElement>|elements|存储图形元素的列表|
    |protected|HashMap< String, Strategy>|strategy|存储所有策略的字典|
    |protected|Strategy|currentStrategy|当前所采用的的绘图策略|
    |protected|Color|currentColor|当前的绘图颜色|
    |protected|boolean|similar|是否开启快捷选择操作|
    |protected|JButton|backgroundButton|修改画板背景色的按钮|
    |protected|JButton|colorButton|修改当前绘图颜色的按钮|
    |protected|JButton|similarButton|开启/关闭快捷选择操作的按钮|
    |protected|BufferedImage|lastImage|打开的以保存图像|

##构造函数

由于主程序继承自SwingFramework类,所以构造函数只需要对一些继承自父类的元素稍作修改即可。

  • 主程序构造函数源代码
public Editor() {
        appBorder = new Color(0xFFEBCD);
        appBackground = Color.WHITE;
        appFont = new Font("Courier New", Font.PLAIN, 20);
        appWidth = 1080;
        appHeight = 720;
        appWorldWidth = 16.0f;
        appWorldHeight = 9.0f;
        appSleep = 10L;
        appMaintainRatio = true;
        appBorderScale = 0.95f;
        appTitle = "Editor";
        currentColor = Color.BLACK;
        similar = false;
    }

##GUI设计

在父类中提供了onCreateAndShowGUI方法供子类继承来设计程序界面,这是一个很长的方法,但是很好理解。它在主面板里增加了菜单栏和工具栏,在菜单栏中增加新建,保存,打开,关闭,说明,关于等功能,在工具栏中为每一个策略和一些其他小功能增添了按钮,在改变策略时改变currentStrategy字段即可。

  • onCreateAndShowGUI方法源代码
@Override
    protected void onCreateAndShowGUI() {
        JMenuBar menuBar = new JMenuBar();
        JMenu menu = new JMenu("File");
        JMenuItem item = new JMenuItem(new AbstractAction("New") {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (saved()||empty()) {
                    onNew();
                } else {
                    int isSave = JOptionPane.showConfirmDialog(Editor.this, "是否保存文件?",
                            "保存", JOptionPane.YES_NO_OPTION);
                    if (isSave == JOptionPane.NO_OPTION) {
                        onNew();
                    } else {
                        save();
                    }
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Open") {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (saved()||empty()) {
                    onOpen();
                } else {
                    int isSave = JOptionPane.showConfirmDialog(Editor.this, "是否保存文件?",
                            "保存", JOptionPane.YES_NO_OPTION);
                    if (isSave == JOptionPane.NO_OPTION) {
                        onOpen();
                    } else {
                        save();
                    }
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Save") {
            @Override
            public void actionPerformed(ActionEvent e) {
                while (!saved()) {
                    save();
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Exit") {
            @Override
            public void actionPerformed(ActionEvent e) {
                Editor.this.dispatchEvent(new WindowEvent(
                        Editor.this, WindowEvent.WINDOW_CLOSING
                ));
            }
        });
        menu.add(item);
        menuBar.add(menu);
        menu = new JMenu("Help");
        item = new JMenuItem(new AbstractAction("Instruction") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                        Editor.this, "Instruction of this app!!!",
                        "Instruction", JOptionPane.INFORMATION_MESSAGE
                );
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("About") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                        Editor.this, "About this app!!!",
                        "About", JOptionPane.INFORMATION_MESSAGE
                );
            }
        });
        menu.add(item);
        menuBar.add(menu);
        setJMenuBar(menuBar);

        JToolBar bar = new JToolBar();
        bar.setFloatable(false);
        backgroundButton = new JButton("■");
        backgroundButton.setForeground(appBackground);
        backgroundButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JColorChooser chooser=new JColorChooser();
                Color color = chooser.showDialog(
                        Editor.this, "选取颜色", Color.BLACK);
                if (color != null) {
                    appBackground = color;
                }
                backgroundButton.setForeground(appBackground);
                canvas.setBackground(appBackground);
            }
        });
        bar.add(backgroundButton);
        colorButton = new JButton("■");
        colorButton.setForeground(currentColor);
        colorButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JColorChooser chooser=new JColorChooser();
                Color color = chooser.showDialog(
                        Editor.this, "选取颜色", Color.BLACK);
                if (color != null) {
                    currentColor = color;
                }
                colorButton.setForeground(currentColor);
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(colorButton);
        JButton b = new JButton("•");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("points strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("-");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("line strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("△");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("triangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("□");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("rectangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("○");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("circle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("O");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("oval strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("◆");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill polygon strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("▲");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill triangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("■");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill rectangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("●");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill circle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("Θ");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill oval strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("abc");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("string strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        similarButton = new JButton("×");
        similarButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                similar = !similar;
                if (similar) {
                    similarButton.setForeground(Color.RED);
                } else {
                    similarButton.setForeground(Color.BLACK);
                }
            }
        });
        bar.add(similarButton);
        b = new JButton("⊙");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("null strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        getMainPanel().add(bar, BorderLayout.NORTH);
    }

##菜单操作方法

在onCreateAndShowGUI方法的菜单操作中只是调用了执行操作的方法,而没有对具体的文件操作做具体实现,下面是对这些方法的详解。

  • 菜单操作方法一览表
修饰符返回值函数名参数说明
protectedbooleanempty()判断当前画板是否为空
protectedbooleansaved()判断当前图像是否保存
protectedvoidsave()保存当前绘制图像
protectedvoidonNew()新建画板并还原默认设置
protectedvoidonOpen()新建画板并打开指定图像
  • 菜单操作方法源代码
protected boolean empty() {
        if (elements.size() == 0) {
            return true;
        } else {
            for (ImageElement element : elements) {
                if (!element.empty()) {
                    return false;
                }
            }
            return true;
        }
    }

    protected boolean saved() {
        if (empty()) {
            return true;
        }else {
            return elements.get(elements.size() - 1) instanceof SaveImageElement;
        }
    }

    protected void save() {
        BufferedImage image = new BufferedImage(getScreenWidth(), getScreenHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        Matrix3x3f view = getViewportTransform();
        g2d.setColor(appBackground);
        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
        if (lastImage != null) {
            g2d.drawImage(lastImage, 0, 0, image.getWidth(), image.getHeight(), null);
        }
        for (ImageElement element : elements) {
            element.draw(g2d, view);
        }
        g2d.dispose();
        String fileName = (String) JOptionPane.showInputDialog(Editor.this, "文件名:",
                "保存", JOptionPane.PLAIN_MESSAGE, null,null,"new");
        if (fileName == null) {
            return;
        }
        fileName = fileName + ".jpg";
        File file = new File(fileName);
        try {
            if (!ImageIO.write(image, "jpg", file)) {
                throw new IOException("No 'jpg' image writer found");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            elements.add(new SaveImageElement());
        }
    }

    protected void onNew() {
        elements.clear();
        currentColor = Color.BLACK;
        colorButton.setForeground(currentColor);
        currentStrategy.finishDrawing();
        currentStrategy = strategy.get("null strategy");
        currentStrategy.setColor(currentColor);
    }

    protected void onOpen() {
        onNew();
        String fileName = JOptionPane.showInputDialog(Editor.this, "文件名:",
                "打开", JOptionPane.PLAIN_MESSAGE);
        if (fileName == null) {
            return;
        }
        if (fileName.endsWith(".jpg") || fileName.endsWith(".png")) {
            try {
                lastImage = ImageIO.read(new File(fileName));
            } catch (IOException e1) {
                JOptionPane.showMessageDialog(
                        Editor.this, "No such image exists!!!",
                        "Warning", JOptionPane.INFORMATION_MESSAGE);
                e1.printStackTrace();
            }
        } else {
            JOptionPane.showMessageDialog(
                    Editor.this, "Can't solve image type!!!",
                    "Warning", JOptionPane.INFORMATION_MESSAGE);
        }
    }

##初始化操作

初始化操作调用继承自Framework类的initialize方法,其主要作用为向策略字典中添加所有的绘制策略,是本程序中最难理解的一个函数。

每个BeginEnd策略的初始化框架如下:

strategy.put("strategy name",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        //some thing to draw
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                       //some thing for similar
                    }
                });
            }
        });

在字典中加入匿名的策略,需要重写抽象策略的addElement方法,而addElement方法又需要一个匿名的图形元素,其中重写了draw方法和similar方法。实际上就是一种不同的draw方法和similar方法,决定了一个图形元素,同时也决定了一种绘图策略。

  • initialize方法源代码
@Override
    protected void initialize() {
        super.initialize();
        elements = new ArrayList<ImageElement>();
        strategy = new HashMap<>();
        strategy.put("null strategy",
                new NullStrategy(this, keyboard, mouse, elements));
        strategy.put("points strategy",
                new PointsStrategy(this, keyboard, mouse, elements));
        strategy.put("line strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawPolygon(g2d, new Vector2f[]{begin, end}, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        }else if(center.similar(mousePos,EPSILON)){
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("triangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawTriangle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f tmp = end.sub(begin);
                        Vector2f topTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        Vector2f bottomTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if (topTmp.similar(mousePos,EPSILON)) {
                            return topTmp;
                        } else if (bottomTmp.similar(mousePos,EPSILON)) {
                            return bottomTmp;
                        }else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("rectangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawRect(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f other = begin.add(new Vector2f(end.sub(begin).x, 0));
                        Vector2f another = end.sub(new Vector2f(end.sub(begin).x, 0));
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if(other.similar(mousePos,EPSILON)){
                            return other;
                        } else if (another.similar(mousePos, EPSILON)) {
                            return another;
                        } else if (center.similar(mousePos, EPSILON)) {
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("circle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawCircle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f circle = mousePos.sub(begin).norm();
                        circle = begin.add(circle.mul(end.sub(begin).len()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("oval strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawOval(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        Vector2f circle = mousePos.sub(center);
                        float length = Math.abs(end.x - begin.x);
                        float width = Math.abs(end.y - begin.y);
                        float radian = circle.angle();
                        circle = center.add(
                                new Vector2f(
                                        (float) (length / 2 * Math.cos(radian)),
                                        (float) (width / 2 * Math.sin(radian))));
                        if (center.similar(mousePos,EPSILON)) {
                            return center;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill triangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillTriangle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f tmp = end.sub(begin);
                        Vector2f topTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        Vector2f bottomTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if (topTmp.similar(mousePos,EPSILON)) {
                            return topTmp;
                        } else if (bottomTmp.similar(mousePos,EPSILON)) {
                            return bottomTmp;
                        }else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill rectangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillRect(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f other = begin.add(new Vector2f(end.sub(begin).x, 0));
                        Vector2f another = end.sub(new Vector2f(end.sub(begin).x, 0));
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if(other.similar(mousePos,EPSILON)){
                            return other;
                        } else if (another.similar(mousePos, EPSILON)) {
                            return another;
                        } else if (center.similar(mousePos, EPSILON)) {
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill circle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillCircle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f circle = mousePos.sub(begin).norm();
                        circle = begin.add(circle.mul(end.sub(begin).len()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill oval strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillOval(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        Vector2f circle = mousePos.sub(center);
                        float length = Math.abs(end.x - begin.x);
                        float width = Math.abs(end.y - begin.y);
                        float radian = circle.angle();
                        circle = center.add(
                                new Vector2f(
                                        (float) (length / 2 * Math.cos(radian)),
                                        (float) (width / 2 * Math.sin(radian))));
                        if (center.similar(mousePos,EPSILON)) {
                            return center;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill polygon strategy",
                new FillPolygonStrategy(this, keyboard, mouse, elements));
        strategy.put("string strategy",
                new StringStrategy(this, keyboard, mouse, elements));
        currentStrategy = strategy.get("null strategy");
    }

##主程序输入处理

由于主程序继承自SwingFramework类,那么就需要实现Framework类里的processInput方法来处理输入。因为之前已经把所有的输入处理都交给了不同的策略,所以这一步非常的简单,整个方法只有以下两行。

@Override
    protected void processInput(float delta) {
        super.processInput(delta);
        currentStrategy.processInput();
    }

##主程序渲染处理

由于主程序继承自SwingFramework类,那么就需要实现Framework类里的render方法来处理渲染。这里将渲染处理分为3部分,**打开的图像文件的渲染,图形元素列表的渲染,和快捷选择提示圆的渲染。**这一步也非常简单,因为具体的实现方法之前已经定义好了,所以这里只需要调用每一个图形元素的draw方法即可。具体代码如下。

protected void renderLastImage(Graphics g) {
        if (lastImage != null) {
            g.drawImage(lastImage, 0, 0, getScreenWidth(), getScreenHeight(),null);
        }
    }

    @Override
    protected void render(Graphics g) {
        super.render(g);
        renderLastImage(g);
        Matrix3x3f view = getViewportTransform();
        ArrayList<ImageElement> elementsTmp = new ArrayList<>(elements);
        for (ImageElement element : elementsTmp) {
            element.draw((Graphics2D)g, view);
        }
        if (similar) {
            renderSimilar(g);
        }
    }

    protected void renderSimilar(Graphics g) {
        Matrix3x3f view = getViewportTransform();
        Vector2f mousePos = getWorldMousePosition();
        ArrayList<ImageElement> elementsTmp = new ArrayList<>(elements);
        for (ImageElement element : elementsTmp) {
            if (element instanceof Similarable) {
                Vector2f tmpPos = ((Similarable) element).similar(mousePos);
                if (tmpPos != null && tmpPos.sub(mousePos).len() > Vector2f.EPSILON) {
                    Utility.fillSimilarCircle((Graphics2D) g, tmpPos, view);
                    if (mouse.buttonDownOnce(MouseEvent.BUTTON2)) {
                        currentStrategy.similar(tmpPos, getViewportTransform());
                    }
                }
            }
        }
    }

##主程序主函数

其实到这里,整个程序已经结束了,但是要有主函数,程序才能运行。其实应该可以想到,非常简单,主函数只有一行。

public static void main(String[] args) {
        launchApp(new Editor());
    }

launchApp即可,Now,You can enjoy it!


##附录:主程序源代码

import Rendering.element.BeginEndImageElement;
import Rendering.element.ImageElement;
import Rendering.element.SaveImageElement;
import Rendering.element.Similarable;
import Rendering.strategy.*;
import Rendering.utils.Matrix3x3f;
import Rendering.utils.SwingFramework;
import Rendering.utils.Utility;
import Rendering.utils.Vector2f;
import Rendering.strategy.FillPolygonStrategy;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;

public class Editor extends SwingFramework {
    protected ArrayList<ImageElement> elements;
    protected HashMap<String, Strategy> strategy;
    protected Strategy currentStrategy;
    protected Color currentColor;
    protected boolean similar;
    protected JButton backgroundButton;
    protected JButton colorButton;
    protected JButton similarButton;
    protected BufferedImage lastImage;


    public Editor() {
        appBorder = new Color(0xFFEBCD);
        appBackground = Color.WHITE;
        appFont = new Font("Courier New", Font.PLAIN, 20);
        appWidth = 1080;
        appHeight = 720;
        appWorldWidth = 16.0f;
        appWorldHeight = 9.0f;
        appSleep = 10L;
        appMaintainRatio = true;
        appBorderScale = 0.95f;
        appTitle = "Editor";
        currentColor = Color.BLACK;
        similar = false;
    }

    @Override
    protected void onCreateAndShowGUI() {
        JMenuBar menuBar = new JMenuBar();
        JMenu menu = new JMenu("File");
        JMenuItem item = new JMenuItem(new AbstractAction("New") {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (saved()) {
                    onNew();
                } else {
                    int isSave = JOptionPane.showConfirmDialog(Editor.this, "是否保存文件?",
                            "保存", JOptionPane.YES_NO_OPTION);
                    if (isSave == JOptionPane.NO_OPTION) {
                        onNew();
                    } else {
                        save();
                    }
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Open") {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (saved()) {
                    onOpen();
                } else {
                    int isSave = JOptionPane.showConfirmDialog(Editor.this, "是否保存文件?",
                            "保存", JOptionPane.YES_NO_OPTION);
                    if (isSave == JOptionPane.NO_OPTION) {
                        onOpen();
                    } else {
                        save();
                    }
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Save") {
            @Override
            public void actionPerformed(ActionEvent e) {
                while (!saved()) {
                    save();
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Exit") {
            @Override
            public void actionPerformed(ActionEvent e) {
                Editor.this.dispatchEvent(new WindowEvent(
                        Editor.this, WindowEvent.WINDOW_CLOSING
                ));
            }
        });
        menu.add(item);
        menuBar.add(menu);
        menu = new JMenu("Help");
        item = new JMenuItem(new AbstractAction("Instruction") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                        Editor.this, "Instruction of this app!!!",
                        "Instruction", JOptionPane.INFORMATION_MESSAGE
                );
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("About") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                        Editor.this, "About this app!!!",
                        "About", JOptionPane.INFORMATION_MESSAGE
                );
            }
        });
        menu.add(item);
        menuBar.add(menu);
        setJMenuBar(menuBar);

        JToolBar bar = new JToolBar();
        bar.setFloatable(false);
        backgroundButton = new JButton("■");
        backgroundButton.setForeground(appBackground);
        backgroundButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JColorChooser chooser=new JColorChooser();
                Color color = chooser.showDialog(
                        Editor.this, "选取颜色", Color.BLACK);
                if (color != null) {
                    appBackground = color;
                }
                backgroundButton.setForeground(appBackground);
                canvas.setBackground(appBackground);
            }
        });
        bar.add(backgroundButton);
        colorButton = new JButton("■");
        colorButton.setForeground(currentColor);
        colorButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JColorChooser chooser=new JColorChooser();
                Color color = chooser.showDialog(
                        Editor.this, "选取颜色", Color.BLACK);
                if (color != null) {
                    currentColor = color;
                }
                colorButton.setForeground(currentColor);
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(colorButton);
        JButton b = new JButton("•");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("points strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("-");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("line strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("△");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("triangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("□");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("rectangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("○");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("circle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("O");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("oval strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("◆");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill polygon strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("▲");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill triangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("■");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill rectangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("●");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill circle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("Θ");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill oval strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("abc");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("string strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        similarButton = new JButton("×");
        similarButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                similar = !similar;
                if (similar) {
                    similarButton.setForeground(Color.RED);
                } else {
                    similarButton.setForeground(Color.BLACK);
                }
            }
        });
        bar.add(similarButton);
        b = new JButton("⊙");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("null strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        getMainPanel().add(bar, BorderLayout.NORTH);
    }

    protected boolean empty() {
        if (elements.size() == 0) {
            return true;
        } else {
            for (ImageElement element : elements) {
                if (!element.empty()) {
                    return false;
                }
            }
            return true;
        }
    }

    protected boolean saved() {
        if (empty()) {
            return true;
        }else {
            return elements.get(elements.size() - 1) instanceof SaveImageElement;
        }
    }

    protected void save() {
        BufferedImage image = new BufferedImage(getScreenWidth(), getScreenHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        Matrix3x3f view = getViewportTransform();
        g2d.setColor(appBackground);
        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
        if (lastImage != null) {
            g2d.drawImage(lastImage, 0, 0, image.getWidth(), image.getHeight(), null);
        }
        for (ImageElement element : elements) {
            element.draw(g2d, view);
        }
        g2d.dispose();
        String fileName = (String) JOptionPane.showInputDialog(Editor.this, "文件名:",
                "保存", JOptionPane.PLAIN_MESSAGE, null,null,"new");
        if (fileName == null) {
            return;
        }
        fileName = fileName + ".jpg";
        File file = new File(fileName);
        try {
            if (!ImageIO.write(image, "jpg", file)) {
                throw new IOException("No 'jpg' image writer found");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            elements.add(new SaveImageElement());
        }
    }

    protected void onNew() {
        elements.clear();
        currentColor = Color.BLACK;
        colorButton.setForeground(currentColor);
        currentStrategy.finishDrawing();
        currentStrategy = strategy.get("null strategy");
        currentStrategy.setColor(currentColor);
    }

    protected void onOpen() {
        onNew();
        String fileName = JOptionPane.showInputDialog(Editor.this, "文件名:",
                "打开", JOptionPane.PLAIN_MESSAGE);
        if (fileName == null) {
            return;
        }
        if (fileName.endsWith(".jpg") || fileName.endsWith(".png")) {
            try {
                lastImage = ImageIO.read(new File(fileName));
            } catch (IOException e1) {
                JOptionPane.showMessageDialog(
                        Editor.this, "No such image exists!!!",
                        "Warning", JOptionPane.INFORMATION_MESSAGE);
                e1.printStackTrace();
            }
        } else {
            JOptionPane.showMessageDialog(
                    Editor.this, "Can't solve image type!!!",
                    "Warning", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    @Override
    protected void initialize() {
        super.initialize();
        elements = new ArrayList<ImageElement>();
        strategy = new HashMap<>();
        strategy.put("null strategy",
                new NullStrategy(this, keyboard, mouse, elements));
        strategy.put("points strategy",
                new PointsStrategy(this, keyboard, mouse, elements));
        strategy.put("line strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawPolygon(g2d, new Vector2f[]{begin, end}, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        }else if(center.similar(mousePos,EPSILON)){
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("triangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawTriangle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f tmp = end.sub(begin);
                        Vector2f topTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        Vector2f bottomTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if (topTmp.similar(mousePos,EPSILON)) {
                            return topTmp;
                        } else if (bottomTmp.similar(mousePos,EPSILON)) {
                            return bottomTmp;
                        }else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("rectangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawRect(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f other = begin.add(new Vector2f(end.sub(begin).x, 0));
                        Vector2f another = end.sub(new Vector2f(end.sub(begin).x, 0));
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if(other.similar(mousePos,EPSILON)){
                            return other;
                        } else if (another.similar(mousePos, EPSILON)) {
                            return another;
                        } else if (center.similar(mousePos, EPSILON)) {
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("circle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawCircle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f circle = mousePos.sub(begin).norm();
                        circle = begin.add(circle.mul(end.sub(begin).len()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("oval strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawOval(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        Vector2f circle = mousePos.sub(center);
                        float length = Math.abs(end.x - begin.x);
                        float width = Math.abs(end.y - begin.y);
                        float radian = circle.angle();
                        circle = center.add(
                                new Vector2f(
                                        (float) (length / 2 * Math.cos(radian)),
                                        (float) (width / 2 * Math.sin(radian))));
                        if (center.similar(mousePos,EPSILON)) {
                            return center;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill triangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillTriangle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f tmp = end.sub(begin);
                        Vector2f topTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        Vector2f bottomTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if (topTmp.similar(mousePos,EPSILON)) {
                            return topTmp;
                        } else if (bottomTmp.similar(mousePos,EPSILON)) {
                            return bottomTmp;
                        }else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill rectangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillRect(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f other = begin.add(new Vector2f(end.sub(begin).x, 0));
                        Vector2f another = end.sub(new Vector2f(end.sub(begin).x, 0));
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if(other.similar(mousePos,EPSILON)){
                            return other;
                        } else if (another.similar(mousePos, EPSILON)) {
                            return another;
                        } else if (center.similar(mousePos, EPSILON)) {
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill circle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillCircle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f circle = mousePos.sub(begin).norm();
                        circle = begin.add(circle.mul(end.sub(begin).len()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill oval strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillOval(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        Vector2f circle = mousePos.sub(center);
                        float length = Math.abs(end.x - begin.x);
                        float width = Math.abs(end.y - begin.y);
                        float radian = circle.angle();
                        circle = center.add(
                                new Vector2f(
                                        (float) (length / 2 * Math.cos(radian)),
                                        (float) (width / 2 * Math.sin(radian))));
                        if (center.similar(mousePos,EPSILON)) {
                            return center;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill polygon strategy",
                new FillPolygonStrategy(this, keyboard, mouse, elements));
        strategy.put("string strategy",
                new StringStrategy(this, keyboard, mouse, elements));
        currentStrategy = strategy.get("null strategy");
    }

    @Override
    protected void processInput(float delta) {
        super.processInput(delta);
        currentStrategy.processInput();
    }

    protected void renderLastImage(Graphics g) {
        if (lastImage != null) {
            g.drawImage(lastImage, 0, 0, getScreenWidth(), getScreenHeight(),null);
        }
    }

    @Override
    protected void render(Graphics g) {
        super.render(g);
        renderLastImage(g);
        Matrix3x3f view = getViewportTransform();
        ArrayList<ImageElement> elementsTmp = new ArrayList<>(elements);
        for (ImageElement element : elementsTmp) {
            element.draw((Graphics2D)g, view);
        }
        if (similar) {
            renderSimilar(g);
        }
    }

    protected void renderSimilar(Graphics g) {
        Matrix3x3f view = getViewportTransform();
        Vector2f mousePos = getWorldMousePosition();
        ArrayList<ImageElement> elementsTmp = new ArrayList<>(elements);
        for (ImageElement element : elementsTmp) {
            if (element instanceof Similarable) {
                Vector2f tmpPos = ((Similarable) element).similar(mousePos);
                if (tmpPos != null && tmpPos.sub(mousePos).len() > Vector2f.EPSILON) {
                    Utility.fillSimilarCircle((Graphics2D) g, tmpPos, view);
                    if (mouse.buttonDownOnce(MouseEvent.BUTTON2)) {
                        currentStrategy.similar(tmpPos, getViewportTransform());
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        launchApp(new Editor());
    }
}

#源代码下载链接

源代码

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值