1、第十一章 图形图像处理 在前边的章节的图形用户界面的处理中,已经看到了图像的应用。本章将简要介绍如何在用户屏幕上绘制图形以及如何显示图像。 11.1 图形 在前边我们已经介绍了用户屏幕和容器的概念,也看到了如何在容器中添加组件。一般来说,在用户屏幕上绘制图形其实就是在容器组件上绘制图形。因此需要注意以下两点: 1)组件中的坐标系统 容器组件的坐标系统类似于屏幕的坐标系统,坐标原点(0,0)在容器的左上角,正x轴方向水平自左向右,正y轴方向垂直自上向下。 在java中,不同的图形输出设备拥有自己的设备坐标系统,该系统具有与默认用户坐标系统相同的方向。坐标单位取决于设备,比如,显示的
2、分辨率不同,设备坐标系统就不同。一般来说,在显示屏幕上的计量单位是像素(每英寸大约90个像素),在打印机上是点(每英寸大约600个点)。Java系统自动将用户坐标转换成输出设备专有的设备坐标系统。 2)图形环境(graphics context) 由于在组件上绘制图形使用的用户坐标系统被封装在Graphics2D类的对象中,所以Graphics2D被称之为图形环境。它提供了丰富的绘图方法,包括绘制直线、矩形、圆、多边形等。 下边我们先介绍与绘制图形相关的类,再介绍绘制图形的方法和步骤。 11.1.1 绘制图形的类 与绘制图形有关的类的层次结构如下: |- java.awt.Grap
3、hics |-java.awt.Graphics2D |- java.awt.GraphicsConfigTemplate |- java.awt.GraphicsConfiguration |- java.awt.GraphicsDevice |- java.awt.GraphicsEnvironment Graphics 类是所有图形类的抽象基类,它允许应用程序可以在组件(已经在各种设备上实现)上进行图形图像的绘制。Graphics 对象封装了 Java 支持的基本绘制操作所需的状态信息,其中包括组件对象、绘制和剪贴坐标的转换原点、当前剪贴区、当前颜色、当前字体、当前的
4、逻辑像素操作方法(XOR 或 Paint)等等。 Graphics2D类是从早期版本(JDK1.0)中定义设备环境的Graphics类派生而来的,它提供了对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制。它是用于在Java(tm)平台上绘制二维图形、文本和图像的基础类。 GraphicsDevice类定义了屏幕和打印机这类可用于绘制图形的设备。 GraphicsEnvironment类定义了所有可使用的图形设备和字体设备。 GraphicsConfiguration类定义了屏幕或打印机这类设备的特征。在图形绘制过程中,每个 Graphics2D 对象都与一个定义了绘制位置的目
5、标相关联。GraphicsConfiguration 对象定义绘制目标的特征(如像素格式和分辨率等)。在Graphics2D对象的整个生命周期中都使用相同的绘制标准。 Griphics和Graphics2D类都是抽象类,我们无法直接创建这两个类的对象,表示图形环境的对象完全取决于与之相关的组件,因此获得图形环境总是与特定的组件相关。 创建 Graphics2D 对象时,GraphicsConfiguration 将为 Graphics2D 的目标(Component 或 Image)指定默认转换,所有 Graphics2D 方法都采用用户空间坐标。 一般来说,图形的绘制过程分为四个阶段:
6、确定绘制内容、在指定的区域绘制、确定绘制的颜色、将颜色应用于绘图面。有三种绘制操作:几何图形、文本和图像。 绘制过程中,Graphics2D对象的6个重要属性如下: Paint 颜料属性决定线条绘制的颜色。它也定义填充图形的颜色和模式,系统默认的颜料属性是组件的颜色。 Font 字体属性定义了绘制文本时所使用的字体,系统默认的字体是组件的字体设置。 Stroke 画笔属性确定线型,如实线、虚线或点划线等。该属性也决定线段端点的形状。系统默认的画笔是方形画笔,绘制线宽为1的实线,线的末端为方形,斜角线段接口为45度斜面。 Transform 转换属性定义渲染过程中应用的转换方法。
7、可以使绘制的图形平移、旋转和缩放。 Composite 合成属性决定如何在组件上绘制叠放图形。 Clip 剪切属性定义了组件上的一个区域边界。图形绘制只能在该区域内进行。 一般情况下,我们使用Griphics2D对象的方法进行图形的绘制工作,Griphics2D对象的常用方法如下: 1) abstract void clip(Shape s) 将当前Clip与指定Shape的内部区域相交,并将Clip设置为所得的交集。 2) abstract void draw(Shape s) 使用当前Graphics2D上下文的设置勾画Shape的轮廓。 3) abstract vo
8、id drawImage(BufferedImage img, BufferedImageOp op, int x, int y) 呈现使用 BufferedImageOp 过滤的 BufferedImage 应用的呈现属性包括 Clip、Transform 和 Composite 属性。 4) abstract boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) 呈现一个图像,在绘制前进行从图像空间到用户空间的转换。 5) abstract void drawString(String
9、s,float x,float y) 使用 Graphics2D上下文中当前文本属性状态呈现由指定 String 指定的文本。 6) abstract void drawString(String str, int x, int y) 使用Graphics2D上下文中的当前文本属性状态呈现指定的 String 的文本。 7) abstract void fill(Shape s) 使用Graphics2D上下文的设置,填充Shape的内部区域。 8) abstract Color getBackground() 返回用于清除区域的背景色。 9) abstract
10、Composite getComposite() 返回 Graphics2D 上下文中的当前 Composite 10) abstract Paint getPaint() 返回 Graphics2D 上下文中的当前 Paint 11) abstract Stroke getStroke() 返回 Graphics2D 上下文中的当前 Stroke 12) abstract boolean hit(Rectangle rect, Shape s, boolean onStroke) 检查指定的 Shape 是否与设备空间中的指定 Rectangle 相交。 13)
11、 abstract void rotate(double theta) 将当前的 Graphics2D Transform 与旋转转换连接。 14) abstract void rotate(double theta, double x, double y) 将当前的 Graphics2D Transform 与平移后的旋转转换连接。 15) abstract void scale(double sx, double sy) 将当前Graphics2D Transform与可缩放转换连接。 16) abstract void setBackground(Color co
12、lor) 设置Graphics2D上下文的背景色。 17) abstract void setComposite(Composite comp) 为 Graphics2D上下文设置 Composite Composite 用于所有绘制方法中,如 drawImage、drawString、draw 和 fill 它指定新的像素如何在呈现过程中与图形设备上的现有像素组合。 18) abstract void setPaint(Paint paint) 为 Graphics2D 上下文设置 Paint 属性。 19) abstract void setStroke(Stroke s)
13、 为 Graphics2D 上下文设置 Stroke 20) abstract void setTransform(AffineTransform Tx) 重写Graphics2D上下文中的 Transform。 21) abstract void shear(double shx, double shy) 将当前Graphics2D Transform与剪裁转换连接。 22) abstract void translate(double tx, double ty) 将当前的Graphics2D Transform与平移转换连接。 23) abstract v
14、oid translate(int x, int y) 将Graphics2D上下文的原点平移到当前坐标系统中的点 (x, y)。 11.1.2 简单几何图形类的层次结构 在java.awt.geom包中定义了几何图形类,包括点、直线、矩形、圆、椭圆、多边形等等。该包中各类的层次结构如下: |- java.lang.Object |- java.awt.geom.AffineTransform |- java.awt.geom.Area |- java.awt.geom.CubicCurve2D |- java.awt.geom.CubicCurve2D.Double
15、 |- java.awt.geom.CubicCurve2D.Float |- java.awt.geom.Dimension2D |- java.awt.geom.FlatteningPathIterator |- java.awt.geom.Line2D |- java.awt.geom.Line2D.Double |- java.awt.geom.Line2D.Float |- java.awt.geom.Path2D |- java.awt.geom.Path2D.Double |- java.awt.geom.Path2D.Float
16、 |- java.awt.geom.GeneralPath |- java.awt.geom.Point2D |- java.awt.geom.Point2D.Double |- java.awt.geom.Point2D.Float |- java.awt.geom.QuadCurve2D |- java.awt.geom.QuadCurve2D.Double |- java.awt.geom.QuadCurve2D.Float |- java.awt.geom.RectangularShape |- java.awt.geom.Arc2D |- java.a
17、wt.geom.Arc2D.Double |- java.awt.geom.Arc2D.Float |- java.awt.geom.Ellipse2D |- java.awt.geom.Ellipse2D.Double |- java.awt.geom.Ellipse2D.Float |- java.awt.geom.Rectangle2D |- java.awt.geom.Rectangle2D.Double |- java.awt.geom.Rectangle2D.Float |- java.awt.geom.RoundRectangle2D |- java.
18、awt.geom.RoundRectangle2D.Double |- java.awt.geom.RoundRectangle2D.Float 下边我们简要介绍一下绘制几何图形类的功能及应用。 11.1.3 路径类 路径类用于构造直线、二次曲线和三次曲线的几何路径。它可以包含多个子路径。如上类层次结构所描述,Path2D是基类(它是一个抽象类);Path2D.Double和Path2D.Float 是其子类,它们分别以不同的精度的坐标定义几何路径;GeneralPath在1.5及其以前的版本中,它是一个独立的最终类。在1.6版本中进行了调整与划分,其功能由Path2D替代,为了其兼
19、容性,把它划为Path2D.Float派生的最终类。下边以GeneralPath类为例介绍一下路径类的功能与应用。 1. 构造方法 构造路径对象的方法如下: 1) GeneralPath(int rule) 以rule指定缠绕规则构建对象。缠绕规则确定路径内部的方式。有两种方式的缠绕规则:Path2D.WIND_EVEN_ODD用于确定路径内部的奇偶 (even-odd) 缠绕规则;Path2D.WIND_NON_ZERO用于确定路径内部的非零 (non-zero) 缠绕规则。 2) GeneralPath() 以默认的缠绕规则Path2D.WIND_NON_ZERO构建对象。
20、3) GeneralPath(int rule, int initialCapacity) 以rule指定缠绕规则和initialCapacity指定的容量(以存储路径坐标)构建对象。 4) GeneralPath(Shape s) 以Shape对象s构建对象。 2. 常用方法 路径对象常用的方法如下: 1) void append(Shape s, boolean connect) 将指定Shape对象的几何形状追加到路径中,也许使用一条线段将新几何形状连接到现有的路径段。如果connect为true并且路径非空,则被追加的Shape几何形状的初始moveTo操作将被转换为li
21、neTo操作。 2) void closePath() 回到初始点使之形成闭合的路径。 3) boolean contains(double x, double y) 测试指定坐标是否在当前绘制边界内。 4) void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) 将三个新点定义的曲线段添加到路径中。 5) Rectangle2D getBounds2D() 获得路径的边界框。 6) Point2D getCurrentPoint() 获得当前添加到路径的坐标。 7) int
22、 getWindingRule() 获得缠绕规则。 8) void lineTo(float x, float y) 绘制一条从当前坐标到(x,y)指定坐标的直线,将(x,y)坐标添加到路径中。 9) void moveTo(float x, float y) 从当前坐标位置移动到(x,y)指定位置,将(x,y)添加到路径中。 10) void quadTo(float x1, float y1, float x2, float y2) 将两个新点定义的曲线段添加到路径中。 11) void reset() 将路径重置为空。 12) void setWindin
23、gRule(int rule) 设置缠绕规则。 13) void transform(AffineTransform at) 使用指定的AffineTransform 变换此路径的几何形状。 以上只是列出了一些常用的方法,若需要了解更多的信息,请参阅JDK文档。 3. 应用举例 如前所述,我们不能直接创建Graphics和Graphics2D绘图对象,要在组件上绘图,需要使用组件的方法先获得绘图环境对象的引用。 一般情况下,我们采用重写paint()方法的方式实现绘图,该方法在组件重建的时候被调用。当然我们也可以使用组件的getGriphics()方法获得绘图对象。 图11
24、1 折线图 例11.1 在屏幕上画出如图11.1的折线图。 程序的基本设计思想如下:建立JFrame的派生类,重写paint()方法,在该方法中实现折线图的绘制。程序参考代码如下: /* 绘制折线程序 DrawLine.java */ import java.awt.*; import java.awt.geom.*; import javax.swing.*; public class DrawLine extends JFrame { public DrawLine() { super("Drawing 2D Paint"); se
25、tSize(425,160); setVisible(true); setDefaultCloseOperation(EXIT_ON_CLOSE); } public void paint(Graphics g) //重写绘图方法paint() { super.paint(g); Graphics2D g2d = (Graphics2D)g; // 强制转换为Graphics2D引用 int xPoints[]={50,75,100,125,150,175,200}; int yPoints[]=
26、{100,50,100,50,100,50,100};
GeneralPath line = new GeneralPath(); // 构建GeneralPath类对象
line.moveTo(xPoints[0],yPoints[0]); // 将起始点加入路径
for(int i=1; i 27、ic static void main(String args[]) //主方法main()
{
new DrawLine();
} //主方法main()结束
}
我们可以使用路径存储多边形、二次曲线、三次曲线的坐标点,实现对这些几何图形的绘制。
11.1.4 点与线段类
1. 点
在java中有3个定义点的类:Point2D.Float、Point2D.Double和Point。前两个是Point2D的静态内部类,它们使用浮点型数计算点的坐标;最后一个是Point2D的子类,它使用整型数计算点的坐标。Point2D是一个抽象类,虽然不能直接创建 28、对象进行操作,但我们可以使用其内部类或子类对象进行操作。我们可以使用如下的构造方法创建对象:
1) Point2D.Float() 创建具有坐标(0,0)的点对象。
2) Point2D.Float(float x, float y) 创建具有坐标(x ,y )的点对象。
3) Point2D.Double() 创建具有坐标(0,0)的点对象。
4) Point2D.Double(double x,double y) 创建具有坐标(x ,y )的点对象。
两种不同类型的构造方法构造不同精度的坐标点。
下边的两个语句分别构造两个不同的坐标点:
Point2D.Float 29、p1 = new Point2D.Float(); //构造的坐标点在用户坐标原点(0,0)
Point2D.Float p2 = new Point2D.Float(100,100); //构造的坐标点在用户坐标(100,100)
它们也提供一些获得坐标值、计算两点之间的距离、设置新点的位置等方法。需要时可可查阅JDK相关的文档。
2. 线段
在java中提供了处理线段的类Line2D.Float、Line2D.Double和Line2D。其中Line2D类是所有存储2D线段对象的惟一抽象超类;Line2D.Float、Line2D.Doubl是其子类,分别用于float型和do 30、uble型用户坐标的定义。我们可以使用如下的构造方法创建对象:
1) Line2D.Float() 创建一个从坐标 (0, 0) 到(0, 0)的对象。
2) Line2D.Float(float X1, float Y1, float X2, float Y2) 根据指定坐标(X1,Y1)和(X2,Y2)构造对象。
3) Line2D.Float(Point2D p1, Point2D p2) 根据p1和p2指定的两点构造对象。
4) Line2D.Double() 创建一个从坐标 (0, 0) 到(0, 0)的对象。
5) Line2D.Double(double X1 31、 double Y1, double X2, double Y2) 根据指定坐标(X1,Y1)和(X2,Y2)构造对象。
6) Line2D. Double (Point2D p1, Point2D p2) 根据p1和p2指定的两点构造对象。
例如,下列语句:
Point2D.Float p1 = new Point2D.Float();
Point2D.Float p2 = new Point2D.Float(100,100);
Line2D.Float line1 = new Line2D.Float(p1,p2);
Line2D.Float line2 = new 32、 Line2D.Float(0,0,100,100);
将构建两条(line1和line2)相同的从用户坐标(0,0)到(100,100)的线段。
线段类也提供一些获取线段的相关信息(如坐标值)、计算点到直线之间的距离、测试线段(是否在指定的边界内、是否与另一条线段相交等)、设置线段等方法。这些方法不再列出,使用时再作简要介绍。读者需要了解更多的信息时,可参阅JDK相关的文档。
3. 应用举例
在前边的示例中,我们已经看到了绘制折线的方法和步骤。有多种绘制线段的方法。当绘制多个连续的线段(如上例的折线),一般来说,会采用路径的方式,先将各坐标点存入路径中,然后再进行绘制。当绘制单个简单 33、的线段时,会采用Graphics对象的drawLine()方法,需要指定直线的起点和终点坐标。方法的格式如下:
图11.2
void drawLine(int X1,int Y1,int X2,int Y2)
当定位坐标要求精度较高时,采用浮点数坐标,使用Graphics2D对象绘制图形。
下边我们举例说明其应用。
例11.2 在屏幕上绘制如图11.2的图形。
程序的基本设计思想如下:建立JFrame的派生类,重写paint()方法,在该方法中实现线段的绘制。程序参考代码如下:
/* 程序名DrawLineDemo.java*/
import java.awt.*;
34、import javax.swing.*;
import java.awt.geom.*;
public class DrawLineDemo extends JFrame
{
public DrawLineDemo()
{
super("Drawing Line");
setSize(400,300);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void paint(Graphics g)
{
super.pain 35、t(g);
g.drawLine(0, 0, 50, 100); //绘制单个线段
g.drawLine(50, 100, 50, 200); //绘制单个线段
g.drawLine(50, 100, 300, 100); //绘制单个线段
Graphics2D g2d = (Graphics2D)g; // 强制转换为Graphics2D引用
Line2D.Float p1 = new Line2D.Float(100f,100f,125f,50f); //定义线段1
Line2D.Float p2 = new 36、 Line2D.Float(125f,50f,150f,100f); //定义线段2
Line2D.Float p3 = new Line2D.Float(150f,100f,125f,150f);//定义线段3
Line2D.Float p4 = new Line2D.Float(125f,150f,100f,100f);//定义线段4
g2d.draw(p1); //绘制线段1
g2d.draw(p2); //绘制线段2
g2d.draw(p3); //绘制线段3
g2d.draw(p4); //绘制线段4
}
37、 public static void main(String args[])
{
new DrawLineDemo();
}
}
在该程序中,绘制的菱形是由4条连续的线段组成的,我们先以浮点类型坐标定义了4条线段,然后使用Graphics2D对象的draw()方法进行绘制。当然也可以象上例那样使用路径的方式绘制菱形。这一任务作为作业留给读者,以加深对绘图方式的理解。
11.1.5 矩形和圆角矩形
在java中,矩形包括直角和圆角两种形状,绘制矩形也有多种方法。既可绘制矩形的轮廓,也可对矩形区域内部进行填充
1. 直角矩形
与点、线段类的定义相似,也有三个类定义 38、直角矩形:Rectangle2D、Rectangle2D.Double和Rectangle2D.Float。我们可以使用下边的构造方法创建直角矩形。
1) Rectangle2D.Float()
2) Rectangle2D.Float(float x, float y, float w, float h)
3) Rectangle2D.Double()
4) Rectangle2D.Double (double x , double y, double w, double h)
其中,没有参数的构造方法将构造一个左上角的坐标为(0,0),高度和宽度为0的矩形。参数x, y 39、指定矩形的左上角坐标,w指定矩形的宽度,h指定矩形的高度。
直角矩形类也提供了相关的方法,不再一一列出,用到时再作介绍,下边举一个例子说明直角矩形的绘制。
例11.3 绘制如图11.3的5个直角矩形。
程序的基本设计思想如下:建立JFrame的派生类,重写paint()方法,在该方法中实现
图11.3 绘制直角矩形
直角矩形的绘制。先定义第一个矩形,然后在此矩形的基础上,使用setRect()方法改变对象的位置。setRect()方法说明如下:
void setRect(float x, float y, float w, float h)
四个参数与对象构造方法中所说明 40、的相一致。
程序参考代码如下:
/* 程序名DrawRectangleDemo.java */
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
public class DrawRectangleDemo extends JFrame
{
public DrawRectangleDemo()
{
super("Drawing Rectangle");
setSize(400,300);
setVisible(true);
setDefaultClo 41、seOperation(EXIT_ON_CLOSE);
}
public void paint(Graphics g)
{
super.paint(g);
Graphics2D g2d=(Graphics2D)g;
float x=50f,y=50f; //定义初始坐标
Rectangle2D.Float rectangle1=new Rectangle2D.Float(x,y,50f,50f);//定义矩形
for(int i=1; i<=5; i++)
{
g2d..draw(rectangl 42、e1); //绘制矩形
rectangle1.setRect(x+=10f,y+=10f,50f,50f);//改变(x,y)坐标移动对象位置
}
}
public static void main(String args[])
{
new DrawRectangleDemo();
}
}
在程序中,采用了先定义矩形对象,再使用Graphics2D对象的draw()方法绘制矩形。当然,也可以直接使用图形对象的如下方法绘制直角矩形:
void drawRect(int x, int y, int w, int h)
vo 43、id fillRect(int x, int y, int w, int h)
void draw3DRect(int x, int y, int w, int h , boolean raised)
void fill3DRect(int x, int y, int w , int h ,boolean raised)
其中第二个方法用于填充直角矩形;第三个方法用于画3维直角矩形,第四个方法用于填充3维直角矩形,raised确定是否凸起。读者可以修改上边的示例程序,使用上边的方法绘制,看一下图形有何变化。
2. 圆角矩形
定义圆角矩形相关的类是RoundRe 44、ctangle2D、geom.RoundRectangle2D.Double和RoundRectangle2D.Float。其层次结构与上述介绍的类相似。可用的对象构造方法如下:
1) RoundRectangle2D.Float( )
2) RoundRectangle2D.Float(double x,double y,double w,double h,double arcw, double
arch)
3) RoundRectangle2D.Double( )
4) RoundRectangle2D.Double(double x,double y,double w,dou 45、ble h,double arcw, double
arch)
和直角矩形不同的是增加了两个参数arcw和arch,圆角弧的宽度和高度。
与绘制直角矩形相同,也可以多种方式绘制,除了采用了先定义圆角矩形对象,再使用Graphics2D对象的draw()方法绘制外,也可以直接使用图形对象的如下方法绘制圆角矩形:
1) void drawRoundRect(int x,int y,int w, int h,int arcW,int arcH)
2) void fillRoundRect(int x,int y,int w,int h,int arcW, int arcH)
3. 46、 应用举例
下边举一个例子,说明矩形的几种绘制方法。
例11.4 分别以两种方法绘制直角和圆角矩形。
程序代码如下:
/* 程序名DrawRectangleSummary.java */
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
public class DrawRectangleSummary extends JFrame
{
public DrawRectangleSummary()
{
super("Drawing Rectangle");
se 47、tSize(400,300);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void paint(Graphics g)
{
super.paint(g);
Graphics2D g2d=(Graphics2D)g;
float x=10f,y=50f;
Rectangle2D.Float r1= new Rectangle2D.Float(x,y,50f,50f);//定义直角矩形
RoundRectangle2 48、D.Float rr1=new RoundRectangle2D.Float(x,y+70,50f,50f, 10f,10f); //定义圆角矩形
g2d.draw(r1); //绘制直角矩形
g2d.draw(rr1); //绘制圆角矩形
r1.setRect(x+=70f,y,50f,50f); //重设直角矩形
rr1.setRoundRect(x,y+70f,50f,50f,10f,10f); 重设圆角矩形
g2d.fill(r1); //以填充方式绘制直角矩形
g2d.fill(rr1); //以填充方式绘 49、制圆角矩形
//以下直接使用Graphics2D对象的方法以int新坐标的形式绘制及填充两种矩形
g2d.draw3DRect((int)(x+=70),(int)y,50,50,true);
g2d.drawRoundRect((int)x,(int)y+70,50,50,10,10);
g2d.fill3DRect((int)(x+=70),(int)y,50,50,true);
g2d.fillRoundRect((int)x,(int)y+70,50,50,10,10);
图11.4
}
public static 50、void main(String args[])
{
new DrawRectangleSummary();
}
}
编译运行该程序,运行结果如图11.4所示。读者可以对照程序分析一下运行结果,以加深对绘图方法的理解。
11.1.6圆和椭圆
绘制圆和椭圆用的是同一种对象。定义圆和椭圆相关的类是:Ellipse2D、Ellipse2D.Float和Ellipse2D.Double。可用于构造对象的构造方法如下:
1) Ellipse2D.Float( )
2) Ellipse2D.Float(float x, float y, float w, float h)






