坐标
Java 2D API 维护两个坐标空间:
-
用户空间 – 指定图形基元的空间
-
设备空间 – 输出设备(如屏幕、窗口或打印机)的坐标系统
用户空间是一个设备无关的逻辑坐标系统,即您的程序使用的坐标空间。传递给 Java 2D 渲染例程的所有几何图形都是使用用户空间坐标指定的。
当使用从用户空间到设备空间的默认转换时,用户空间的原点是组件绘图区域的左上角。x坐标向右增加,y坐标向下增加,如下图所示。窗口的左上角是 0,0。所有坐标都使用整数指定,通常足够。但是,有些情况需要浮点数甚至双精度,这也是支持的。
设备空间是一个依赖设备的坐标系统,根据目标渲染设备而变化。虽然窗口或屏幕的坐标系统可能与打印机的坐标系统非常不同,但这些差异对 Java 程序是不可见的。在渲染过程中,用户空间和设备空间之间的必要转换是自动执行的。
Java 2D 渲染
Java 2D API 在不同类型设备上提供统一的渲染模型。在应用程序级别,无论目标渲染设备是屏幕还是打印机,渲染过程都是相同的。当需要显示组件时,其paint
或update
方法会自动使用适当的Graphics
上下文调用。
Java 2D API 包括java.awt.Graphics2D
类,该类扩展了Graphics
类,以提供对 Java 2D API 增强图形和渲染功能的访问。这些功能包括:
-
渲染任何几何原语的轮廓,使用笔画和填充属性(
draw
方法)。 -
通过使用颜色或图案指定的填充属性(
fill
方法)来填充任何几何原语的内部。 -
渲染任何文本字符串(
drawString
方法)。字体属性用于将字符串转换为字形,然后用颜色或图案指定的填充。 -
渲染指定的图像(
drawImage
方法)。
此外,Graphics2D
类支持特定形状的drawOval
和fillRect
等Graphics
渲染方法。上面列出的所有方法可以分为两组:
-
绘制形状的方法
-
影响渲染的方法
第二组方法使用形成Graphics2D
上下文的状态属性,用于以下目的:
-
变化笔画宽度
-
更改如何连接笔画
-
设置裁剪路径以限制渲染区域
-
在渲染对象时平移、旋转、缩放或倾斜对象
-
定义颜色和图案以填充形状
-
指定如何组合多个图形对象
在应用程序中使用 Java 2D API 功能,将传递给组件渲染方法的Graphics
对象转换为Graphics2D
对象。例如:
public void paint (Graphics g) {
Graphics2D g2 = (Graphics2D) g;
...
}
如下图所示,Graphics2D
类的渲染上下文包含几个属性。
笔属性应用于形状的轮廓。此笔画属性使您能够用任何点大小和虚线模式绘制线条,并对线条应用端点和连接装饰。 | |
---|---|
填充属性应用于形状的内部。此填充属性使您能够用纯色、渐变和图案填充形状。 | |
compositing attribute 在渲染对象重叠现有对象时使用。 | |
transform 属性在渲染过程中应用,将渲染对象从用户空间转换为设备空间坐标。通过该属性还可以应用可选的平移、旋转、缩放或剪切变换。 | |
clip 类型将渲染限制在用于定义剪切路径的 Shape 对象轮廓内的区域。任何用于定义剪切的 Shape 对象。 | |
font 属性用于将文本字符串转换为字形。 | |
Rendering hints 指定在速度和质量之间的权衡偏好。例如,您可以指定是否应使用抗锯齿,如果该功能可用的话。另请参阅 控制渲染质量。 |
要了解更多关于变换和合成的内容,请参阅 Java2D 中的高级主题。
当设置属性时,会传递适当的属性对象。如下例所示,要将绘画属性更改为蓝绿色渐变填充,您需要构造一个 GradientPaint
对象,然后调用 setPaint
方法。
gp = new GradientPaint(0f,0f,blue,0f,30f,green);
g2.setPaint(gp);
几何基元
Java 2D API 提供了一组有用的标准形状,如点、线、矩形、弧、椭圆和曲线。定义常见几何基元的最重要的包是java.awt.geom
包。任意形状可以由直线几何基元的组合表示。
Shape
接口表示具有轮廓和内部的几何形状。此接口提供了一组用于描述和检查二维几何对象的常见方法,并支持曲线线段和多个子形状。Graphics
类仅支持直线段。Shape
接口可以支持曲线段。
关于如何绘制和填充形状的更多详细信息,请参阅几何处理课程。
点
Point2D
类定义了表示(x, y)坐标空间中位置的点。Java 2D API 中的术语“点”与像素不同。点没有面积,不包含颜色,也不能呈现。
点用于创建其他形状。Point2D
类还包括一种计算两点之间距离的方法。
线
Line2D
类是表示一条线的抽象类。线的坐标可以作为双精度数检索。Line2D
类包括几种设置线端点的方法。
您还可以使用下面描述的GeneralPath
类创建直线段。
矩形形状
Rectangle2D
、RoundRectangle2D
、Arc2D
和Ellipse2D
基元都是从RectangularShape
类派生的。此类定义了可以由矩形边界框描述的Shape
对象的方法。RectangularShape
对象的几何形状可以从完全包围Shape
轮廓的矩形中推断出来。
二次和三次曲线
QuadCurve2D
使您能够创建二次参数曲线段。二次曲线由两个端点和一个控制点定义。
CubicCurve2D
类使您能够创建三次参数曲线段。三次曲线由两个端点和两个控制点定义。以下是二次和三次曲线的示例。请参阅描边和填充图形基元以获取三次和二次曲线的实现。
此图表示一个二次曲线。
此图表示一个三次曲线。
任意形状
GeneralPath
类使您能够通过指定沿着形状边界的一系列位置来构造任意形状。这些位置可以通过线段、二次曲线或三次(贝塞尔)曲线连接。以下形状可以通过三条线段和一条三次曲线创建。有关此形状实现的更多信息,请参见描边和填充图形基元。
区域
使用Area
类,您可以对任意两个Shape
对象执行布尔运算,如并集、交集和差集。这种技术通常被称为构造性区域几何,使您能够快速创建复杂的Shape
对象,而无需描述每条线段或曲线。
文本
Java 2D API 具有各种文本渲染功能,包括用于呈现字符串的方法以及用于设置字体属性和执行文本布局的整个类。
如果您只想绘制静态文本字符串,最直接的方法是通过Graphics
类使用drawString
方法直接呈现它。要指定字体,您可以使用Graphics
类的setFont
方法。
如果您想要实现自己的文本编辑例程或需要比文本组件提供的更多对文本布局的控制,您可以使用java.awt.font
包中的 Java 2D 文本布局类。
字体
字体用于表示字符串中字符的形状称为字形。特定字符或字符组合可能表示为一个或多个字形。例如,á可能由两个字形表示,而连字fi可能由一个字形表示。
字体可以被视为一组字形。单个字体可能有许多面孔,如斜体和常规。字体中的所有面孔具有类似的印刷特征,并且可以被识别为同一字体系列的成员。换句话说,具有特定样式的一组字形形成一个字体面孔。一组字体面孔形成一个字体系列。字体系列的集合形成了系统中可用的字体集。
当您使用 Java 2D API 时,您可以通过使用Font
的实例来指定字体。您可以通过调用静态方法GraphicsEnvironment.getLocalGraphicsEnvironment
并查询返回的GraphicsEnvironment
来确定可用的字体。getAllFonts
方法返回一个包含系统上所有可用字体的Font
实例数组。getAvailableFontFamilyNames
方法返回可用字体系列的列表。
文本布局
在文本可以显示之前,必须对其进行布局,以便字符以适当的位置表示为适当的字形。以下是两种用于管理文本布局的 Java 2D 机制:
-
TextLayout
类管理文本布局、高亮显示和命中检测。TextLayout
提供的功能处理最常见的情况,包括具有混合字体、混合语言和双向文本的字符串。 -
您可以通过使用
Font
类创建自己的GlyphVector
对象,然后通过Graphics2D
类渲染每个GlyphVector
对象。因此,您可以完全控制文本的形状和位置。
文本的渲染提示
Java 2D API 使您能够通过使用渲染提示来控制形状和文本渲染的质量。渲染提示由java.awt.RenderingHints
类封装。
当应用于文本时,这种能力用于抗锯齿(也称为平滑边缘)。例如,KEY_TEXT_ANTIALIASING
提示可以让您单独控制文本的抗锯齿效果,而不影响其他形状的抗锯齿效果。要了解更多关于渲染提示的信息,请参阅控制渲染质量课程。
图像
在 Java 2D API 中,图像通常是一个矩形的二维像素数组,其中每个像素表示图像该位置的颜色,而维度表示图像显示时的水平范围(宽度)和垂直范围(高度)。
表示此类图像最重要的图像类是java.awt.image.BufferedImage
类。Java 2D API 将这些图像的内容存储在内存中,以便可以直接访问。
应用程序可以直接创建一个BufferedImage
对象,或从外部图像格式(如 PNG 或 GIF)获取图像。
在任一情况下,应用程序可以通过使用 Java 2D API 图形调用在图像上绘制。因此,图像不仅限于显示照片类型的图像。不同的对象,如线条艺术、文本和其他图形,甚至其他图像都可以绘制到图像上(如下图所示)。
Java 2D API 允许您对BufferedImage
应用图像过滤操作,并包括几个内置过滤器。例如,ConvolveOp
过滤器可用于模糊或锐化图像。
然后生成的图像可以绘制到屏幕上,发送到打印机上,或保存为 PNG、GIF 等图形格式。要了解更多关于图像的信息,请参阅使用图像课程。
打印
所有的 Swing 和 Java 2D 图形,包括合成图形和图像,都可以通过使用 Java 2D Printing API 渲染到打印机上。该 API 还提供文档组合功能,使您能够执行诸如更改打印页面顺序之类的操作。
渲染到打印机就像渲染到屏幕一样。打印系统控制何时渲染页面,就像绘图系统控制组件何时在屏幕上绘制一样。
Java 2D Printing API 基于回调模型,其中打印系统而不是应用程序控制打印页面的时间。应用程序向打印系统提供要打印的文档信息,打印系统确定何时需要成像每一页。
以下两个功能对支持打印很重要:
-
作业控制 – 启动和管理打印作业,包括显示标准打印和设置对话框
-
分页 – 在打印系统请求时呈现每一页
当需要成像页面时,打印系统使用适当的Graphics
上下文调用应用程序的print
方法。要在打印时使用 Java 2D API 功能,您将Graphics
对象转换为Graphics2D
类,就像在渲染到屏幕时一样。
教程:开始使用图形
Java 2D API 强大而复杂。然而,Java 2D API 的绝大多数用途利用了java.awt.Graphics
类中封装的其功能的一个小子集。本课程涵盖了应用程序开发人员最常见的需求。较少常见的需求稍后在 Java 2D API 的高级主题中描述。
Graphics
类的大多数方法可以分为两个基本组:
-
绘制和填充方法,使您能够渲染基本形状、文本和图像
-
设置属性的方法,影响绘制和填充的外观
诸如setFont
和setColor
之类的方法定义了如何渲染绘制和填充方法。
本图说明了这些方法与图形对象的关系:
)
绘制方法包括:
-
drawString
– 用于绘制文本g.drawString("Hello", 10, 10);
-
drawImage
– 用于绘制图像g.drawImage(img, 0, 0, width, height, 0, 0, imageWidth, imageHeight, null);
-
drawLine
,drawArc
,drawRect
,drawOval
,drawPolygon
– 用于绘制几何形状g2.draw(new Line2D.Double(0, 0, 30, 40));
根据您当前的需求,您可以根据以下标准在Graphics
类中选择几种方法之一:
-
无论您想要在指定位置以原始大小渲染图像还是将其缩放以适应给定矩形
-
无论您喜欢用颜色填充图像的透明区域还是保持其透明
填充方法适用于几何形状,包括fillArc
,fillRect
,fillOval
,fillPolygon
。
无论您绘制文本还是图像,记住在 2D 图形中,每个点由其 x 和 y 坐标确定。所有绘制和填充方法都需要这些信息,这些信息确定了文本或图像应该渲染在哪里。
例如,要绘制一条线,应用程序调用以下代码:
java.awt.Graphics.drawLine(int x1, int y1, int x2, int y2)
在此代码中*(x1, y1)是线的起点,(x2, y2)*是线的终点。
因此,绘制水平线的代码如下:
Graphics.drawLine(20, 100, 120, 100);
下面的演示累积了所有提到的技术。移动滑块以显示各种天气类型。
注意: 如果您看不到小程序运行,您需要安装至少Java SE Development Kit (JDK) 7版本。
WeatherWizard
演示使用 JSlider
组件以及各种图形功能来生成和显示指定的天气类型。有关 JSlider
类的更多信息,请参阅 Swing 教程的 How to Use Sliders 部分。
WeatherPainter
类的 paint
方法实现了图形特性。以下代码使用 setupWeatherReport()
方法绘制了一个图像。
...
origComposite = g2.getComposite();
if (alpha0 != null) g2.setComposite(alpha0);
g2.drawImage(img0,
0, 0, size.width, size.height,
0, 0, img0.getWidth(null),
img0.getHeight(null), null);
if (img1 != null) {
if (alpha1 != null) g2.setComposite(alpha1);
g2.drawImage(img1,
0, 0, size.width, size.height,
0, 0, img1.getWidth(null),
img1.getHeight(null), null);
}
...
setFont
和 drawString
方法渲染温度和天气状况。
...
// Freezing, Cold, Cool, Warm, Hot,
// Blue, Green, Yellow, Orange, Red
Font font = new Font("Serif", Font.PLAIN, 36);
g.setFont(font);
String tempString = feels + " " + temperature+"F";
FontRenderContext frc =
((Graphics2D)g).getFontRenderContext();
...
g.setColor(textColor);
int xTextTemp = rX-(int)boundsTemp.getX();
int yTextTemp = rY-(int)boundsTemp.getY();
g.drawString(tempString, xTextTemp, yTextTemp);
int xTextCond = rX-(int)boundsCond.getX();
int yTextCond = rY-(int)boundsCond.getY()
+ (int)boundsTemp.getHeight();
g.drawString(condStr, xTextCond, yTextCond);
fillRect
方法允许您绘制一个填充了指定颜色的矩形。
...
Rectangle2D boundsTemp
= font.getStringBounds(tempString, frc);
Rectangle2D boundsCond
= font.getStringBounds(condStr, frc);
int wText = Math.max((int)boundsTemp.getWidth(),
(int)boundsCond.getWidth());
int hText = (int)boundsTemp.getHeight()
+ (int)boundsCond.getHeight();
int rX = (size.width-wText)/2;
int rY = (size.height-hText)/2;
g.setColor(Color.LIGHT_GRAY);
g2.fillRect(rX, rY, wText, hText);
...
尝试修改 WeatherWizard
演示以更改图形内容。例如,使用 fillRoundRect
方法代替 fillRect
或在 setFont
方法中应用另一个字体大小。在 WeatherWizard.java
文件中找到此小程序的完整代码。演示还需要以下图像:weather-cloud.png
, weather-rain.png
, weather-snow.png
和 weather-sun.png
,这些图像位于 images
目录中。
教程:与几何形状一起工作
在之前的课程中,您已经学习了关于图形概念的知识,包括关于坐标系和图形对象创建的基本信息。现在,您将进一步学习有关 2D 图形类的详细课程。本课程将向您展示如何使用Graphics2D
类绘制图形基元以及任意形状,以及如何使用花哨的轮廓和填充样式显示图形。这些主题将在以下部分讨论。
绘制几何基元
这一部分解释了如何创建标准形状,如点、线、曲线、弧线、矩形和椭圆。
绘制任意形状
这一部分解释了如何使用GeneralPath
类绘制由直线几何基元组合表示的形状。
填充和描边
这一部分解释了如何设置描边和填充属性,以控制应用于Shape
对象和文本的轮廓和填充样式。
绘制几何图元
Java 2D API 提供了几个定义常见几何对象(如点、线、曲线和矩形)的类。这些几何类是java.awt.geom
包的一部分。
PathIterator
接口定义了从路径中检索元素的方法。
Shape
接口提供了一组描述和检查几何路径对象的方法。该接口由GeneralPath
类和其他几何类实现。
本节中的所有示例都是通过使用java.awt.geom
包创建几何图形,然后通过使用Graphics2D
类来呈现它们。首先,您需要获取一个Graphics2D
对象,例如通过将paint()
方法的Graphics
参数进行强制转换。
public void paint (Graphics g) {
Graphics2D g2 = (Graphics2D) g;
...
}
点
Point
类创建一个表示(x,y)坐标空间中位置的点。子类Point2D.Float
和Point2D.Double
分别提供存储点坐标的浮点和双精度。
//Create Point2D.Double
Point2D.Double point = new Point2D.Double(x, y);
要创建坐标为 0,0 的点,您可以使用默认构造函数Point2D.Double()
。
您可以使用setLocation
方法设置点的位置如下:
-
setLocation(double x, double y)
– 以双精度值设置点的位置-定义坐标。 -
setLocation(Point2D p)
– 使用另一个点的坐标设置点的位置。
此外,Point2D
类有方法来计算当前点与给定坐标点之间的距离,或者两点之间的距离。
线
Line2D
类表示(x, y)坐标空间中的线段。Line2D.Float
和Line2D.Double
子类指定了浮点和双精度的线。例如:
// draw Line2D.Double
g2.draw(new Line2D.Double(x1, y1, x2, y2));
该类包括几个setLine()
方法来定义线的端点。
或者,可以使用Line2D.Float
类的构造函数指定线的端点如下:
-
Line2D.Float(float X1, float Y1, float X2, float Y2)
-
Line2D.Float(Point2D p1, Point2D p2)
在Graphics2D
类中使用 Stroke 对象定义线路径的笔画。
曲线
java.awt.geom
包使您能够创建二次或三次曲线段。
二次曲线段
QuadCurve2D
类实现了Shape
接口。该类表示(x, y)坐标空间中的二次参数曲线段。QuadCurve2D.Float
和QuadCurve2D.Double
子类指定了浮点和双精度的二次曲线。
几个setCurve
方法用于指定曲线的两个端点和控制点,其坐标可以直接定义,通过其他点的坐标定义,或使用给定数组。
一个非常有用的方法,setCurve(QuadCurve2D)
,设置具有与提供的曲线相同端点和控制点的二次曲线。例如:
// create new QuadCurve2D.Float
QuadCurve2D q = new QuadCurve2D.Float();
// draw QuadCurve2D.Float with set coordinates
q.setCurve(x1, y1, ctrlx, ctrly, x2, y2);
g2.draw(q);
三次曲线段
CubicCurve2D
类还实现了Shape
接口。该类表示(x, y)坐标空间中的三次参数曲线段。CubicCurve2D.Float
和CubicCurve2D.Double
子类指定了浮点和双精度的三次曲线。
CubicCurve2D
类具有设置曲线的类似方法,与QuadraticCurve2D
类相同,只是有第二个控制点。例如:
// create new CubicCurve2D.Double
CubicCurve2D c = new CubicCurve2D.Double();
// draw CubicCurve2D.Double with set coordinates
c.setCurve(x1, y1, ctrlx1,
ctrly1, ctrlx2, ctrly2, x2, y2);
g2.draw(c);
矩形
在下面的示例中表示的原语的类扩展了RectangularShape
类,该类实现了Shape
接口并添加了一些自己的方法。
这些方法使您能够获取有关形状位置和大小的信息,检查矩形的中心点,并设置形状的边界。
Rectangle2D
类表示由位置(x, y)和尺寸(w x h)定义的矩形。Rectangle2D.Float
和Rectangle2D.Double
子类指定了浮点和双精度的矩形。例如:
// draw Rectangle2D.Double
g2.draw(new Rectangle2D.Double(x, y,
rectwidth,
rectheight));
RoundRectangle2D
类表示具有圆角的矩形,由位置(x, y)、尺寸(w x h)和圆角的宽度和高度定义。RoundRectangle2D.Float
和RoundRectangle2D.Double
子类指定了浮点和双精度的圆角矩形。
圆角矩形由以下参数指定:
-
位置
-
宽度
-
高度
-
圆角弧的宽度
-
圆角弧的高度
要设置RoundRectangle2D
对象的位置、大小和弧度,请使用方法setRoundRect(double a, double y, double w, double h, double arcWidth, double arcHeight)
。例如:
// draw RoundRectangle2D.Double
g2.draw(new RoundRectangle2D.Double(x, y,
rectwidth,
rectheight,
10, 10));
椭圆
Ellipse2D
类表示由边界矩形定义的椭圆。Ellipse2D.Float
和Ellipse2D.Double
子类指定了以浮点和双精度表示的椭圆。
椭圆由位置、宽度和高度完全定义。例如:
// draw Ellipse2D.Double
g2.draw(new Ellipse2D.Double(x, y,
rectwidth,
rectheight));
弧
要绘制椭圆的一部分,您可以使用Arc2D
类。这个类表示由边界矩形、起始角度、角度范围和闭合类型定义的弧。Arc2D.Float
和Arc2D.Double
子类指定了以浮点和双精度表示的弧。
Arc2D
类定义了这个类中对应常量表示的三种弧形:OPEN、PIE 和 CHORD。
有几种方法可以设置弧的大小和参数:
-
直接,通过坐标
-
通过提供的
Point2D
和Dimension2D
-
通过复制现有的
Arc2D
此外,您可以使用setArcByCenter
方法来指定从中心点开始的弧,给定其坐标和半径。
// draw Arc2D.Double
g2.draw(new Arc2D.Double(x, y,
rectwidth,
rectheight,
90, 135,
Arc2D.OPEN));
ShapesDemo2D.java
代码示例包含了所有描述的几何原语的实现。有关本节中所代表的类和方法的更多信息,请参阅java.awt.geom
规范。
绘制任意形状
您已经学会了如何绘制java.awt.geom
包中表示的大多数形状。要创建更复杂的几何图形,如多边形、折线或星形,您可以使用此包中的另一个类GeneralPath
。
此类实现了Shape
接口,表示由线段、二次曲线和三次曲线构成的几何路径。此类中的三个构造函数可以使用默认绕组规则(WIND_NON_ZERO
)、给定的绕组规则(WIND_NON_ZERO
或WIND_EVEN_ODD
)或指定的初始坐标容量创建GeneralPath
对象。绕组规则指定了如何确定路径的内部。
public void paint (Graphics g) {
Graphics2D g2 = (Graphics2D) g;
...
}
要创建一个空的GeneralPath
实例,请调用new GeneralPath()
,然后使用以下方法向形状添加线段:
-
moveTo(float x, float y)
– 将路径的当前点移动到给定点 -
lineTo(float x, float y)
– 向当前路径添加一个直线段 -
quadTo(float ctrlx, float ctrly, float x2, floaty2)
– 向当前路径添加一个二次曲线段 -
curveTo(float ctrlx1, float ctrly1, float ctrlx2, float ctrly2, float x3, floaty3)
– 向当前路径添加一个三次曲线段 -
closePath()
– 关闭当前路径
以下示例说明了如何使用GeneralPath
绘制折线:
|
// draw GeneralPath (polyline)
int x2Points[] = {0, 100, 0, 100};
int y2Points[] = {0, 50, 50, 0};
GeneralPath polyline =
new GeneralPath(GeneralPath.WIND_EVEN_ODD, x2Points.length);
polyline.moveTo (x2Points[0], y2Points[0]);
for (int index = 1; index < x2Points.length; index++) {
polyline.lineTo(x2Points[index], y2Points[index]);
};
g2.draw(polyline);
此示例说明了如何使用GeneralPath
绘制多边形:
|
// draw GeneralPath (polygon)
int x1Points[] = {0, 100, 0, 100};
int y1Points[] = {0, 50, 50, 0};
GeneralPath polygon =
new GeneralPath(GeneralPath.WIND_EVEN_ODD,
x1Points.length);
polygon.moveTo(x1Points[0], y1Points[0]);
for (int index = 1; index < x1Points.length; index++) {
polygon.lineTo(x1Points[index], y1Points[index]);
};
polygon.closePath();
g2.draw(polygon);
请注意,最后两个代码示例之间唯一的区别是closePath()
方法。此方法通过向上一次moveTo
的坐标绘制一条直线,从而将折线变成多边形。
要将特定路径添加到您的GeneralPath
对象的末尾,您可以使用append()
方法之一。ShapesDemo2D.java
代码示例包含了任意形状的额外实现。
描边和填充图形原语
您已经知道如何创建不同的几何原语和更复杂的形状。本课程教授如何为图形添加一些颜色和花哨的轮廓,并表示填充和描边:
-
填充 - 是用纯色、颜色渐变或纹理图案绘制形状内部的过程
-
描边 - 是绘制形状轮廓的过程,应用描边宽度、线条样式和颜色属性
要将花哨的线条样式和填充图案应用于几何原语,需在呈现之前更改Graphics2D
上下文中的描边和绘制属性。例如,通过创建适当的Stroke
对象来绘制虚线。在呈现线条之前,将此描边添加到Graphics2D
上下文中,调用setStroke
方法。同样,通过创建GradientPaint
对象并将其添加到Graphics2D
上下文中,可以将渐变填充应用于Shape
对象。
以下代码行丰富了几何原语的填充和描边上下文:
// draw RoundRectangle2D.Double
final static float dash1[] = {10.0f};
final static BasicStroke dashed =
new BasicStroke(1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, dash1, 0.0f);
g2.setStroke(dashed);
g2.draw(new RoundRectangle2D.Double(x, y,
rectWidth,
rectHeight,
10, 10));
// fill Ellipse2D.Double
redtowhite = new GradientPaint(0,0,color.RED,100, 0,color.WHITE);
g2.setPaint(redtowhite);
g2.fill (new Ellipse2D.Double(0, 0, 100, 50));
ShapesDemo2D.java
代码示例代表了描边和填充的额外实现。
定义花哨的线条样式和填充图案
使用 Java 2D 的Stroke
和Paint
类,可以定义花哨的线条样式和填充图案。
线条样式
线条样式由Graphics2D
呈现上下文中的描边属性定义。要设置描边属性,需创建一个BasicStroke
对象并将其传递给Graphics2D
的setStroke
方法。
一个BasicStroke
对象保存有关线宽、连接样式、端点样式和虚线样式的信息。当使用draw
方法呈现Shape
时,将使用此信息。
线宽是线条垂直于其轨迹的厚度。线宽以用户坐标单位的float
值指定,当使用默认变换时,这些单位大致相当于 1/72 英寸。
连接样式是应用在两条线段相遇处的装饰。BasicStroke
支持以下三种连接样式:
JOIN_BEVEL
JOIN_MITER
JOIN_ROUND
端点样式是应用在线段结束处的装饰。BasicStroke
支持以下三种端点样式:
CAP_BUTT
CAP_ROUND
CAP_SQUARE
虚线样式定义了沿着线长度应用的不透明和透明部分的模式。虚线样式由一个虚线数组和一个虚线相位定义。虚线数组定义了虚线模式。数组中的交替元素表示用户坐标单位中的虚线长度和虚线之间的空间长度。元素 0 表示第一个虚线,元素 1 表示第一个空格,依此类推。虚线相位是虚线模式中的偏移量,也以用户坐标单位指定。虚线相位指示应用于线的开头的虚线模式的哪个部分。
填充图案
填充图案由Graphics2D
渲染上下文中的paint
属性定义。要设置paint
属性,您需要创建一个实现Paint
接口的对象实例,并将其传递给Graphics2D
的setPaint
方法。
以下三个类实现了Paint
接口:Color
、GradientPaint
和TexturePaint
。
要创建一个GradientPaint
,您需要指定起始位置和颜色以及结束位置和颜色。渐变沿着连接两个位置的线从一种颜色变化到另一种颜色。例如:
TexturePaint
类的图案由BufferedImage
类定义。要创建一个TexturePaint
对象,您需要指定包含图案的图像和用于复制和锚定图案的矩形。以下图像表示了这个特性:
课程:使用文本 APIs
本课程向您介绍了使用文本 API 的概念,以应用文本渲染功能。在本教程中,您已经使用了基本的 Java 2D 文本 API,并知道如何设置字体和位置,以及如何绘制文本。
本课程扩展了该材料,帮助您了解如何使用这些 API,并进一步了解 Java 2D 文本显示的功能。
这些主题将在以下部分中讨论。
物理和逻辑字体
这一部分解释了如何使用Font
类的方法来确定系统上可用的字体,创建Font
对象,并获取有关字体系列的信息。
测量文本
这一部分解释了如何通过使用FontMetrics
类的实例来正确测量文本。
高级文本显示
这一部分解释了如何定位和渲染一段样式文本,显示抗锯齿文本,使用文本属性来设置文本样式,并处理双向文本。
字体概念
本节介绍了Font
类,支持详细字体信息的规范和复杂排版功能的使用。
一个Font
对象表示系统上可用的字体集合中的字体面实例。常见字体面的示例包括 Helvetica Bold 和 Courier Bold Italic。一个Font
对象关联三个名称:其逻辑名称、族名称和字体面名称:
-
一个
Font
对象的逻辑名称是映射到系统上可用的特定字体之一的名称。在 Java 中指定Font
时,请使用字体面名称而不是逻辑名称。您可以通过调用getName
方法从Font
中获取逻辑名称。要获取映射到系统上可用的特定字体的逻辑名称列表,请调用java.awt.GraphicsEnvironment.getAvailableFontFamilyNames方法。查看物理和逻辑字体以获取更多信息。
-
一个
Font
对象的族名称是确定跨多个字体面的排版设计的字体族名称,如 Helvetica。通过getFamily
方法检索族名称。 -
一个
Font
对象的字体面名称指的是系统上安装的实际字体。这是您在指定字体时应该使用的名称。通常被称为字体名称。通过调用getFontName
检索字体名称。要确定系统上可用的字体面,请调用java.awt.GraphicsEnvironment.getAllFonts
方法。
您可以通过getAttributes
方法访问有关Font
的信息。Font
对象的属性包括其名称、大小、变换和字体特征,如粗细和姿势。
一个LineMetrics
对象封装了与Font
相关的测量信息,如其上升、下降和行间距:
-
上升是基线到上升线的距离。这个距离代表大写字母的典型高度,但有些字符可能会延伸到上升线以上。
-
下降是基线到下行线的距离。大多数字符的最低点将落在下降线内,但有些字符可能会延伸到下行线以下。
-
行间距是推荐的从下行线底部到下一行顶部的距离。
以下图显示了上升线、基线和下行线的位置:
这些信息用于正确定位字符沿着一行,以及相对于彼此定位行。您可以通过getAscent
、getDescent
和getLeading
方法访问这些行度量。您还可以通过LineMetrics
类访问有关Font
对象的高度、基线以及下划线和删除线特性的信息。
文本布局概念
在显示一段文本之前,必须使用适当的字形和连字对其进行正确的形状和定位。这个过程被称为文本布局。文本布局过程涉及以下内容:
-
使用适当的字形和连字来形状文本
-
正确排序文本
-
测量和定位文本
用于排列文本的信息也对执行文本操作(如插入符定位、点击检测和高亮显示)是必要的。查看处理双向文本以获取有关这些文本操作的更多信息。
要开发可以部署在国际市场的软件,文本必须以符合适当书写系统规则的方式排列在不同语言中。
本节涵盖以下主题:
-
形状文本
-
排序文本
-
测量和定位文本
形状文本
字形是一个或多个字符的视觉表示。字形的形状、大小和位置取决于其上下文。根据字体和样式,可以使用许多不同的字形来表示单个字符或字符组合。
例如,在手写草书文本中,特定字符的形状可能会因其与相邻字符的连接方式而异。
在某些书写系统中,特别是阿拉伯文中,必须始终考虑字形的上下文。与英语不同,阿拉伯文中的草书形式是强制性的;在阿拉伯文中,不能不使用草书形式呈现文本。
根据上下文,这些草书形式在形状上可能有很大差异。例如,阿拉伯字母heh有以下图中显示的四种草书形式:
尽管这四种形式彼此非常不同,但这种草书形式的变化与英语中的草书写作并无根本区别。
在某些情况下,两个字形的形状甚至可以发生更大变化,合并成一个单一的字形。这种合并的字形称为连字。例如,大多数英文字体包含以下图中显示的连字fi:
合并的字形考虑了字母f的悬挑,并以一种自然的方式组合字符,而不是简单地让字母相撞。
阿拉伯文中也使用连字,有些连字的使用是强制性的;在不使用适当的连字的情况下呈现某些字符组合是不可接受的。当从阿拉伯字符形成连字时,形状甚至比在英文中更根本地改变。例如,下图说明了当两个阿拉伯字符在一起时如何组合成单个连字。
文本排序
在 Java 编程语言中,文本使用 Unicode 字符编码进行编码。使用 Unicode 字符编码的文本以逻辑顺序存储在内存中。逻辑顺序是字符和单词被读取和写入的顺序。逻辑顺序不一定与视觉顺序相同,即对应字形显示的顺序。
特定书写系统(脚本)中字形的视觉顺序称为脚本顺序。例如,罗马文本的脚本顺序是从左到右,而阿拉伯文和希伯来文的脚本顺序是从右到左。
一些书写系统除了脚本顺序外,还有规则来排列文本行上的字形和单词。例如,阿拉伯文和希伯来文的数字是从左到右排列的,即使字母是从右到左排列的。这意味着阿拉伯文和希伯来文,即使没有嵌入英文文本,也是真正的双向文本。更多信息请参见处理双向文本。
测量和定位文本
除非您使用等宽字体,否则字体中的不同字符具有不同的宽度。这意味着所有文本的定位和测量都必须考虑到确切使用了哪些字符,而不仅仅是数量。例如,要在比例字体中右对齐显示的数字列,您不能简单地使用额外的空格来定位文本。为了正确对齐列,您需要知道每个数字的确切宽度,以便相应调整。
文本通常使用多种字体和样式显示,如粗体或斜体。在这种情况下,即使是相同的字符也可能有不同的形状和宽度,这取决于其样式。为了正确定位、测量和呈现文本,您需要跟踪每个单独的字符和应用于该字符的样式。幸运的是,TextLayout
类可以为您完成这些工作。
要正确显示希伯来文和阿拉伯文等语言的文本,需要测量和定位每个单独的字符,并将其放置在相邻字符的上下文中。由于字符的形状和位置可能会根据上下文而变化,因此在不考虑上下文的情况下测量和定位此类文本会产生不可接受的结果。
此外,Java SE 为您提供了FontMetrics
类,它使您能够获取由Font
对象渲染的文本的测量值,比如字体中一行文本的高度。您可以利用这些信息在 Java 图形应用程序中精确定位文本。更多信息请参见测量文本。
物理和逻辑字体
有两种字体:物理字体和逻辑字体。物理字体是实际的字体库,包括 TrueType 或 PostScript Type 1 字体。物理字体可以是 Time、Helvetica、Courier 或任何其他字体,包括国际字体。逻辑字体是以下五个字体系列:Serif、SansSerif、Monospaced、Dialog 和 DialogInput。这些逻辑字体不是实际的字体库。相反,Java 运行时环境通过逻辑字体名称将其映射到物理字体。
本节帮助您确定在应用程序中使用哪种字体。它涵盖以下主题:
-
物理字体
-
Lucidia 字体
-
将物理字体与您的应用程序捆绑
-
-
逻辑字体
-
使用物理和逻辑字体的优缺点
-
字体配置文件
物理字体
物理字体是包含字形数据和表的实际字体库,使用 TrueType 或 PostScript Type 1 等字体技术,以将字符序列映射到字形序列。要获取系统中安装的所有可用字体系列的名称,请调用以下内容:
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
String []fontFamilies = ge.getAvailableFontFamilyNames();
FontSelector 示例程序(在 FontSelector.java
中可用)演示了如何定位和选择这些字体。
注意: 应用程序不应假设任何特定的物理字体存在。然而,逻辑字体是一个安全的选择,因为它们始终存在。有关更多信息,请参阅逻辑字体。
注意: 如果您看不到 applet 运行,请至少安装Java SE Development Kit (JDK) 7 版本。
Lucidia 字体
Oracle 的 JRE 包含这一系列物理字体,也被许可用于其他 Java 平台的实现。这些字体是物理字体,但不依赖于主机操作系统。
使用这些字体的应用程序可以在这些字体可用的任何地方实现相同的外观。此外,这些字体涵盖了大量的语言(特别是欧洲和中东地区),因此您可以为支持的语言创建完全多语言的应用程序。然而,这些字体可能不在所有 JRE 中可用。此外,它们目前不涵盖完整的 Unicode 字符集;特别是,不支持中文、日文和韩文。
将物理字体与您的应用程序捆绑
有时,应用程序不能依赖于系统上安装的字体,通常是因为该字体是一种不可用的自定义字体。在这种情况下,必须将字体文件与应用程序捆绑在一起。
使用以下方法之一从现有物理字体创建 Font
对象:
Font java.awt.Font.createFont(int fontFormat, InputStream in);
Font java.awt.Font.createFont(int fontFormat, File fontFile);
要从 TrueType 字体创建 Font
对象,形式参数 fontFormat
必须是常量 Font.TRUETYPE_FONT
。以下示例从 TrueType 字体文件 A.ttf
创建 Font
对象:
Font font = Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf"));
直接从文件访问字体更简单、更方便。但是,如果您的代码无法访问文件系统资源,或者字体与应用程序或小程序的其余部分一起打包在 Java 存档(JAR)文件中,则可能需要一个 InputStream
对象。
createFont
方法创建一个具有点大小为 1 和样式 PLAIN
的新 Font
对象。然后可以使用 Font.deriveFont
方法将此基础字体用于派生具有不同大小、样式、变换和字体特性的新 Font
对象。例如:
try {
//Returned font is of pt size 1
Font font = Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf"));
//Derive and return a 12 pt version:
//Need to use float otherwise
//it would be interpreted as style
return font.deriveFont(12f);
} catch (IOException|FontFormatException e) {
// Handle exception
}
使用 deriveFont
方法很重要,因为应用程序创建的字体不属于底层字体系统所知的字体集。由于 deriveFont
方法是从最初创建的字体工作的,因此它没有这种限制。
解决此问题的方法是将创建的字体注册到图形环境中。例如:
try {
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf"));
} catch (IOException|FontFormatException e) {
//Handle exception
}
在将字体注册到图形环境后,该字体可以在调用 getAvailableFontFamilyNames()
时使用,并且可以在字体构造函数中使用。
逻辑字体
Java SE 定义了以下五个逻辑字体系列:
-
Dialog
-
DialogInput
-
Monospaced
-
Serif
-
SansSerif
这些字体在任何 Java 平台上都可用,并且可以被视为某些具有其名称所暗示属性的基础字体的别名。Serif 字体类似于 Times New Roman,通常用于印刷。Sans Serif 字体更适合屏幕使用。
这些字体可以根据用户的语言环境进行定制。此外,这些字体支持最广泛的代码点(Unicode 字符)范围。
除了字体系列,字体还具有其他属性,其中最重要的是 样式 和 大小。样式有 Bold 和 Italic。
Java 2D API 使用的默认字体是 12 磅的 Dialog。这种字体是在普通 72-120 DPI 显示设备上阅读文本的典型字号。应用程序可以通过指定以下内容直接创建此字体的实例:
Font font = new Font("Dialog", Font.PLAIN, 12);
使用物理和逻辑字体的优缺点
物理字体使应用程序能够充分利用所有可用字体,实现不同的文本外观和最大的语言覆盖范围。然而,创建使用物理字体的应用程序要困难得多。
将物理字体与您的应用程序捆绑在一起,可以使您创建的应用程序在任何地方看起来都一样,并且可以完全控制您想要支持的应用程序。然而,捆绑的字体可能会很大,特别是如果您希望您的应用程序支持中文、日文和韩文。此外,您可能需要解决许可问题。
逻辑字体名称保证在任何地方都能正常工作,并且它们至少能够在主机操作系统本地化的语言中进行文本呈现(通常支持更广泛的语言范围)。然而,用于呈现文本的物理字体在不同的实现、主机操作系统和区域设置之间会有所不同,因此应用程序无法在任何地方实现相同的外观。此外,映射机制有时会限制可以呈现的字符范围。这在 JRE 版本 5.0 之前曾经是一个大问题:例如,日文文本只能在日本本地化的主机操作系统上呈现,而在其他本地化系统上即使安装了日文字体也无法呈现。对于使用 2D 字体呈现的应用程序,在 JRE 版本 5.0 及更高版本中,这个问题要少得多,因为映射机制现在通常会识别并使用所有支持的书写系统的字体(如果已安装)。
字体配置文件
Java SE 运行时环境使用字体配置文件将逻辑字体名称映射到物理字体。根据主机操作系统版本的不同映射,有几个文件支持不同的映射。这些文件位于 JRE 安装的 lib
目录中。您可以编辑或创建自己的字体配置文件,以调整映射到您特定系统设置的映射。有关更多信息,请参阅字体配置文件。
测量文本
要正确测量文本,您需要学习一些方法和一些要避免的错误。字体度量是由Font
对象呈现的文本的测量,例如字体中一行文本的高度。测量文本最常见的方法是使用封装了这些度量信息的FontMetrics
实例。例如:
// get metrics from the graphics
FontMetrics metrics = graphics.getFontMetrics(font);
// get the height of a line of text in this
// font and render context
int hgt = metrics.getHeight();
// get the advance of my text in this font
// and render context
int adv = metrics.stringWidth(text);
// calculate the size of a box to hold the
// text with some padding.
Dimension size = new Dimension(adv+2, hgt+2);
对于许多应用程序来说,这种方式足以均匀间隔文本行或调整 Swing 组件的大小。
注意以下内容:
-
这些度量是从
Graphics
类中获取的,因为这个类封装了FontRenderContext
,这是准确测量文本所需的。在屏幕分辨率下,字体会根据易读性进行调整。随着文本大小的增加,这种调整并不是线性缩放的。因此,在 20 pt 时,字体显示的文本长度不会正好是在 10 pt 时的两倍。除了文本本身和字体之外,用于测量文本的另一个重要信息是FontRenderContext
。该方法包括从用户空间到设备像素的变换,用于测量文本。 -
高度报告时没有参考任何特定文本字符串。例如,在文本编辑器中,您希望每行文本之间具有相同的行间距时,这是有用的。
-
stringWidth()
返回文本的前进宽度。前进宽度是从文本原点到随后呈现的字符串位置的距离。
在使用这些方法测量文本时,请注意文本可以向字体高度和字符串前进的矩形定义之外的任何方向延伸。
通常,最简单的解决方案是确保文本不被裁剪,例如,由围绕文本的组件。在可能导致文本被裁剪的情况下添加填充。
如果此解决方案不足够,Java 2D 软件中的其他文本测量 API 可以返回矩形边界框。这些框考虑了要测量的特定文本的高度和像素化效果。
高级文本显示
Java 2D API 提供了支持复杂文本布局的机制。本节描述了高级文本显示的以下特性。
使用渲染提示显示抗锯齿文本
本节介绍如何通过使用渲染提示来控制渲染质量。
使用文本属性来设置文本样式
本节解释了如何使用TextAttribute
类来给文本添加下划线或删除线。
绘制多行文本
本节解释了如何使用TextLayout
和LineBreakMeasurer
类来定位和渲染一段样式化文本。
处理双向文本
本节讨论如何使用java.awt
和java.awt.font
包中的类处理双向文本。
使用渲染提示显示抗锯齿文本
Java 2D 文本渲染可能受渲染提示的影响。
请记住最重要的文本绘制方法如下:
Graphics.drawString(String s, int x, int y);
通常,该方法会使用纯色绘制文本字符串中的每个字形,而该字形中的每个“开启”像素都会设置为该颜色。这种绘制方式产生了最高对比度的文本,但有时会出现锯齿状(锯齿状)边缘。
文本抗锯齿是一种用于平滑屏幕上文本边缘的技术。Java 2D API 使应用程序能够指定是否应使用此技术以及通过将文本渲染提示应用于Graphics
来使用哪种算法。
最常见的渲染提示会将前景(文本)颜色与文本边缘的屏幕背景像素混合。要请求此提示,应用程序必须调用以下方法:
graphics2D.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
以下图示说明了抗锯齿功能。
如果不当使用,此方法可能使文本显得过于模糊。在这种情况下,更好的提示是以下提示:
graphics2D.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
这种方法会自动使用字体本身的信息来决定是使用抗锯齿还是使用纯色。
LCD 显示器具有 Java 2D API 可以利用的属性,以产生不像典型抗锯齿那样模糊的文本,但在小尺寸下更易读的文本。要求使用典型 LCD 显示器的子像素 LCD 文本模式绘制文本,应用程序必须调用以下方法:
graphics2D.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
下面所示的代码示例说明了抗锯齿功能的顺序:
-
抗锯齿已关闭。
-
抗锯齿已开启。
-
使用
TEXT_ANTIALIAS_GASP
提示进行抗锯齿。
注意: 因此,GASP 表指定仅在这些大小上使用提示,而不是“平滑”。因此,在许多情况下,结果文本显示等同于
VALUE_TEXT_ANTIALIAS_OFF
。
-
使用
TEXT_ANTIALIAS_LCD_HRGB
提示进行抗锯齿。
注意: 如果看不到 applet 运行,请至少安装Java SE Development Kit (JDK) 7版本。
此 applet 的完整代码在AntialiasedText.java
中。
使用文本属性来设置文本样式
应用程序通常需要能够应用以下文本属性:
-
下划线 – 在文本下方绘制的线
-
删除线 – 通过文本绘制的水平线
-
上标或下标 – 文本或字母略高于一行或相应地低于一行
-
字间距 – 调整字符之间的空间
这些以及其他文本属性可以通过使用 Java 2D TextAttribute
类来应用。
要应用这些文本属性,请将它们添加到 Font
对象中。例如:
Map<TextAttribute, Object> map =
new Hashtable<TextAttribute, Object>();
map.put(TextAttribute.KERNING,
TextAttribute.KERNING_ON);
font = font.deriveFont(map);
graphics.setFont(font);
下面的代码示例显示了按以下顺序应用文本属性:
-
示例字符串(未应用文本属性)
-
字间距
-
字间距和下划线
-
字间距、下划线和删除线
-
字间距、下划线、删除线和颜色
注意: 如果您看不到 applet 运行,您需要安装至少 Java SE Development Kit (JDK) 7 版本。
此 applet 的完整代码在 AttributedText.java
中。
绘制多行文本
如果您有一段带样式的文本,希望将其适应特定宽度,可以使用 LineBreakMeasurer
类。这个类使得带样式的文本可以被分成行,以便它们适应特定的视觉前进。每一行作为一个 TextLayout
对象返回,代表不可改变的、带样式的字符数据。然而,这个类也使得可以访问布局信息。TextLayout
的 getAscent
和 getDescent
方法返回有关用于在组件中定位行的字体的信息。文本被存储为 AttributedCharacterIterator
对象,以便字体和点大小属性可以与文本一起存储。
以下小程序使用 LineBreakMeasurer
、TextLayout
和 AttributedCharacterIterator
在组件中定位一段带样式的文本。
注意: 如果您看不到小程序运行,您需要安装至少 Java SE Development Kit (JDK) 7 版本。
这个小程序的完整代码在 LineBreakSample.java
中。
以下代码创建一个包含字符串 vanGogh
的迭代器。检索迭代器的开始和结束,并从迭代器创建一个新的 LineBreakMeasurer
。
AttributedCharacterIterator paragraph = vanGogh.getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g2d.getFontRenderContext();
lineMeasurer = new LineBreakMeasurer(paragraph, frc);
窗口的大小用于确定何处应该断行。同时为段落中的每一行创建一个 TextLayout
对象。
// Set break width to width of Component.
float breakWidth = (float)getSize().width;
float drawPosY = 0;
// Set position to the index of the first
// character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines from until the entire paragraph
// has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph
// is right-to-left we will align the
// TextLayouts to the right edge of the panel.
float drawPosX = layout.isLeftToRight()
? 0 : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the
// layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX,drawPosY).
layout.draw(g2d, drawPosX, drawPosY);
// Move y-coordinate in preparation for next
// layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
TextLayout
类通常不会被应用程序直接创建。然而,当应用程序需要直接处理在文本中特定位置应用了样式(文本属性)的文本时,这个类就非常有用。例如,要在段落中画一个单词斜体,应用程序需要为每个子字符串执行测量并设置字体。如果文本是双向的,这个任务就不那么容易正确完成。通过从 AttributedString
对象创建一个 TextLayout
对象来处理这个问题。请参考 Java SE 规范了解更多关于 TextLayout
的信息。
处理双向文本
这一部分讨论如何使用java.awt
和java.awt.font
包中的类处理双向文本。这些类允许您以任何语言或脚本绘制样式化文本,这些语言或脚本受到 Unicode 标准的支持:这是一个全球字符编码系统,用于处理各种现代、古典和历史语言。在绘制文本时,必须考虑文本的阅读方向,以便字符串中的所有单词都能正确显示。这些类维护文本的方向,并正确绘制它,无论字符串是从左到右、从右到左还是双向运行。双向文本对于正确定位插入符、准确定位选择以及正确显示多行文本提出了有趣的问题。另外,双向和从右到左的文本对于根据右箭头和左箭头键的按压正确移动插入符也存在类似问题。
下列主题包括:
-
文本排序
-
操作双向文本
-
显示插入符
-
移动插入符
-
点击测试
-
突出显示选择
-
-
在 Java 应用程序中执行文本布局
-
使用 TextLayout 类管理文本布局
-
使用 TextLayout 类布局文本
-
使用 TextLayout 类显示双插入符
-
使用 TextLayout 类移动插入符
-
使用 TextLayout 类进行点击测试
-
使用 TextLayout 类突出显示选择
-
如果您计划使用 Swing 组件,请参阅使用 JTextComponent 类处理双向文本和使用文本组件获取更多信息。
文本排序
Java SE 在内存中以逻辑顺序存储文本,这是字符和单词读取和写入的顺序。逻辑顺序不一定与视觉顺序相同,后者是显示相应字形的顺序。
即使混合使用多种语言,双向文本中必须保持书写系统的视觉顺序。下图展示了一个嵌入在英语句子中的阿拉伯短语。
注意: 在这个和后续的示例中,阿拉伯语和希伯来语文本由大写字母表示,空格由下划线表示。每个示例包含两部分:存储在内存中的字符表示(逻辑顺序的字符)后跟这些字符如何显示的表示(视觉顺序的字符)。字符框下面的数字表示插入偏移量。
尽管它们是英语句子的一部分,但阿拉伯语单词以阿拉伯语书写顺序从右到左显示。因为斜体的阿拉伯语单词在逻辑上位于普通文本的阿拉伯语单词之后,所以在视觉上位于普通文本的左侧。
当显示混合左到右和右到左文本的行时,基本方向很重要。基本方向是主要书写系统的书写顺序。例如,如果文本主要是英语并带有一些嵌入的阿拉伯语,则基本方向是从左到右。如果文本主要是阿拉伯语并带有一些嵌入的英语或数字,则基本方向是从右到左。
基本方向确定具有共同方向的文本段的显示顺序。在前面图中显示的示例中,基本方向是从左到右。在这个示例中有三个方向性运行:句子开头的英语文本从左到右运行,阿拉伯文本从右到左运行,句号从左到右运行。
图形通常嵌入在文本流中。这些内联图形在影响文本流和换行方式方面类似于字形。这样的内联图形需要使用相同的双向布局算法定位,以便它们出现在字符流的适当位置。
Java SE 使用Unicode 双向算法,这是一种用于在一行内对字形进行排序的算法,从而确定双向文本的方向性。在大多数情况下,您无需包含任何额外信息,以便该算法获取正确的显示顺序。
操纵双向文本
为了允许用户编辑双向文本,您必须能够执行以下操作:
-
显示插入符
-
移动插入符
-
点击测试
-
突出显示选择
显示插入符
在可编辑文本中,插入符用于图形化表示当前插入点,即文本中新字符将被插入的位置。通常,插入符显示为两个字形之间闪烁的垂直条。新字符被插入并显示在插入符的位置。
计算插入符位置可能会很复杂,特别是对于双向文本。在方向边界上的插入偏移量有两个可能的插入符位置,因为对应于字符偏移量的两个字形不会相邻显示。如下图所示。在此图中,插入符显示为方括号,表示插入符对应的字形。
字符偏移量 8 对应于下划线后和A之前的位置。如果用户输入阿拉伯字符,其字形将显示在A的右侧;如果用户输入英文字符,其字形将显示在下划线的右侧。
为了处理这种情况,一些系统显示双插入符,一个强(主要)插入符和一个弱(次要)插入符。强插入符指示插入的字符的方向与文本基础方向相同时将显示在何处。弱插入符显示插入的字符的方向与基础方向相反时将显示在何处TextLayout
自动支持双插入符。
当处理双向文本时,不能简单地将字符偏移量之前的字形宽度相加以计算插入符位置。如果这样做,插入符将被绘制在错误的位置,如下图所示:
为了正确定位插入符,需要将偏移量左侧的字形宽度相加,并考虑当前上下文。除非考虑上下文,否则字形度量可能不会与显示匹配。(上下文可能会影响使用哪些字形。)
移动插入符
所有文本编辑器都允许用户使用箭头键移动插入符。用户期望插入符沿按下的箭头键方向移动。在从左到右的文本中,移动插入偏移量很简单:右箭头键将插入偏移量增加一,左箭头键将其减少一。在双向文本或带有连字的文本中,此行为会导致插入符跨越方向边界的字形并在不同方向运行内部反向移动。
要在双向文本中平滑移动插入符,需要考虑文本运行的方向。当按下右箭头键时不能简单地增加插入偏移量,当按下左箭头键时减少它。如果当前插入偏移量位于从右到左字符的运行内,右箭头键应减少插入偏移量,左箭头键应增加它。
在跨越方向边界时移动插入符更加复杂。下图说明了当用户使用箭头键导航时,当穿越方向边界时会发生什么。在显示的文本中向右移动三个位置对应于移动到字符偏移 7、19,然后是 18。
某些字形之间永远不应该有插入符;相反,插入符应该移动得像这些字形代表一个单一字符一样。例如,如果一个 o 和一个变音符由两个单独的字符表示,那么它们之间永远不应该有插入符。
TextLayout
类提供了方法(getNextRightHit
和 getNextLeftHit
),使您能够轻松地在双向文本中平滑地移动插入符。
命中测试
通常,设备空间中的位置必须转换为文本偏移量。例如,当用户在可选择文本上单击鼠标时,鼠标位置将转换为文本偏移量,并用作选择范围的一端。从逻辑上讲,这是放置插入符的逆过程。
当处理双向文本时,显示中的单个视觉位置可以对应源文本中的两个不同偏移量,如下图所示:
因为单个视觉位置可以对应两个不同的偏移量,所以命中测试双向文本不仅仅是测量字形宽度直到找到正确位置的字形,然后将该位置映射回字符偏移量。检测命中位置所在的一侧有助于区分这两种选择。
您可以使用 TextLayout.hitTestChar
进行命中测试。命中信息封装在 TextHitInfo
对象中,并包括有关命中位置所在一侧的信息。
突出显示选择
一段选定的字符范围通过一个高亮区域图形化表示,其中字形以反色或不同背景颜色显示。
高亮区域,就像插入符一样,在双向文本中比单向文本更复杂。在双向文本中,一段连续的字符范围在显示时可能没有连续的高亮区域。相反,显示为视觉上连续的一系列字形的高亮区域可能不对应单一、连续的字符范围。
这导致双向文本中突出选择的两种策略:
-
逻辑高亮:使用逻辑高亮,所选字符在文本模型中始终是连续的,而高亮区域允许是不连续的。以下是逻辑高亮的示例:
-
可视高亮:使用可视高亮,可能会有多个选定字符范围,但高亮区域始终是连续的。以下是可视高亮的示例:
逻辑高亮更容易实现,因为所选字符在文本中始终是连续的。
示例SelectionSample.java
演示了逻辑高亮:
在 Java 应用程序中执行文本布局
根据您使用的 Java API,您可以根据需要对文本布局进行精细或粗略的控制:
-
如果您只想显示一块文本或需要一个可编辑的文本控件,您可以使用
JTextComponent
,它将为您执行文本布局。JTextComponent
旨在处理大多数国际应用程序的需求,并支持双向文本。有关JTextComponent
的更多信息,请参见使用 JTextComponent 类处理双向文本和使用文本组件。 -
如果您想显示一个简单的文本字符串,您可以调用方法
Graphics2D.drawString
,让 Java 2D 为您布局字符串。您还可以使用Graphics2D.drawString
来呈现带样式的字符串和包含双向文本的字符串。有关通过Graphics2D
呈现文本的更多信息,请参见使用文本 API。 -
如果您想实现自己的文本编辑例程,可以使用
TextLayout
来管理文本布局、高亮显示和点击检测。TextLayout
提供的功能处理了大多数常见情况,包括具有混合字体、混合语言和双向文本的文本字符串。有关使用 TextLayout 的更多信息,请参见管理文本布局。 -
如果您想完全控制文本的形状和位置,可以使用
Font
类构建自己的GlyphVector
实例,然后通过Graphics2D
类进行渲染。
通常,您不需要自己执行文本布局操作。对于大多数应用程序,JTextComponent
是显示静态和可编辑文本的最佳解决方案。但是,JTextComponent
不支持双插入符或双向文本中的不连续选择的显示。如果您的应用程序需要这些功能,或者您更喜欢实现自己的文本编辑例程,可以使用 Java 2D 文本布局 API。
使用 TextLayout 类管理文本布局
TextLayout
类支持包含多种样式和来自不同书写系统(包括阿拉伯文和希伯来文)的字符的文本。(阿拉伯文和希伯来文特别难以显示,因为您必须重新排列和重新排序文本以获得可接受的表示。)
即使您只处理英文文本,TextLayout
也简化了显示和测量文本的过程。通过使用 TextLayout
,您可以在不费额外努力的情况下实现高质量的排版。
TextLayout
被设计为在显示简单的单向文本时不会产生显著的性能影响。当使用 TextLayout
显示阿拉伯文或希伯来文时,会有一些额外的处理开销。但是,这通常是每个字符的微秒数量级,并且被正常绘图代码的执行所主导。
TextLayout
类为您管理字形的定位和排序。您可以使用TextLayout
来执行以下操作:
-
使用 TextLayout 类进行文本布局
-
使用 TextLayout 类显示双光标
-
使用 TextLayout 类移动光标
-
使用 TextLayout 类进行点击测试
-
使用 TextLayout 类突出显示选择内容
使用 TextLayout 类进行文本布局
TextLayout
自动布局文本,包括双向文本,具有正确的形状和顺序。为了正确形状和排序表示一行文本的字形,TextLayout
必须了解文本的完整上下文:
-
如果文本适合单行,例如按钮的单词标签或对话框中的一行文本,您可以直接从文本构造一个
TextLayout
。 -
如果您有更多文本无法适合单行或想要在单行文本上分隔制表符段,您不能直接构造一个
TextLayout
。您必须使用LineBreakMeasurer
提供足够的上下文。有关更多信息,请参见绘制多行文本。
文本的基本方向通常由文本上的属性(样式)设置。如果该属性缺失,TextLayout
将遵循 Unicode 双向算法,并从段落中的初始字符推导基本方向。
使用 TextLayout 类显示双光标
TextLayout
保留光标信息,如光标Shape
、位置和角度。您可以使用此信息轻松地在单向和双向文本中显示光标。在为双向文本绘制光标时,使用TextLayout
可确保光标位置正确。
TextLayout
提供默认插入符Shapes
并自动支持双插入符。对于斜体和倾斜字形,TextLayout
会产生倾斜插入符,如下图所示。这些插入符位置也用作高亮和命中测试之间的字形边界,有助于产生一致的用户体验。
给定插入偏移量,getCaretShapes
方法返回一个包含两个Shape
对象的数组:元素 0 包含强插入符,元素 1 包含弱插入符(如果存在)。要显示双插入符,只需绘制两个插入符Shape
对象;插入符将自动呈现在正确的位置。
如果您想使用自定义插入符,可以从TextLayout
中检索插入符的位置和角度,并自行绘制它们。
示例HitTestSample.java
演示了双插入符。
点击希伯来文本旁边的o会记录用户在o之后点击的位置,这部分属于英文文本。这将使弱(黑色)插入符位于o旁边,而强插入符(红色)位于H之前:
点击o右侧的空格会记录用户点击了空格,这部分属于希伯来文本。这将使强(红色)插入符位于o旁边,而弱插入符(黑色)位于H之前:
使用 TextLayout 类移动插入符
您还可以使用TextLayout
类确定用户按下左箭头或右箭头键时的插入偏移量。给定表示当前插入偏移量的TextHitInfo
对象,getNextRightHit
方法返回一个表示正确插入偏移量的TextHitInfo
对象,如果按下右箭头键。getNextLeftHit
方法为左箭头键提供相同的信息。
来自示例ArrowKeySample.java
的以下摘录演示了确定用户按下左箭头或右箭头键时的插入偏移量的方法:
public class ArrowKeySample extends JPanel implements KeyListener {
// ...
private static void createAndShowGUI() {
// Create and set up the window.
ArrowKey demo = new ArrowKey();
frame = new JFrame("Arrow Key Sample");
frame.addKeyListener(demo);
// ...
}
private void handleArrowKey(boolean rightArrow) {
TextHitInfo newPosition;
if (rightArrow) {
newPosition = textLayout.getNextRightHit(insertionIndex);
} else {
newPosition = textLayout.getNextLeftHit(insertionIndex);
}
// getNextRightHit() / getNextLeftHit() will return null if
// there is not a caret position to the right (left) of the
// current position.
if (newPosition != null) {
// Update insertionIndex.
insertionIndex = newPosition.getInsertionIndex();
// Repaint the Component so the new caret(s) will be displayed.
frame.repaint();
}
}
// ...
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT) {
handleArrowKey(keyCode == KeyEvent.VK_RIGHT);
}
}
}
使用 TextLayout 类进行命中测试
TextLayout
类提供了一个简单的文本命中测试机制。hitTextChar
方法以鼠标的x和y坐标作为参数,并返回一个TextHitInfo
对象。TextHitInfo
包含指定位置的插入偏移量和命中位置的侧面。插入偏移量是最接近命中位置的偏移量:如果命中位置超过行尾,将返回行尾的偏移量。
来自HitTestSample.java
的以下摘录从鼠标点击中检索偏移量:
private class HitTestMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
Point2D origin = computeLayoutOrigin();
// Compute the mouse click location relative to
// textLayout's origin.
float clickX = (float) (e.getX() - origin.getX());
float clickY = (float) (e.getY() - origin.getY());
// Get the character position of the mouse click.
TextHitInfo currentHit = textLayout.hitTestChar(clickX, clickY);
insertionIndex = currentHit.getInsertionIndex();
// Repaint the Component so the new caret(s) will be displayed.
repaint();
}
}
使用 TextLayout 类进行高亮选择
你可以从TextLayout
获取代表高亮区域的Shape
。在计算高亮区域的尺寸时,TextLayout
会自动考虑上下文。TextLayout
支持逻辑和视觉高亮。
来自SelectionSample.java
的以下摘录演示了显示高亮文本的一种方法:
public void paint(Graphics g) {
// ...
boolean haveCaret = anchorEnd == activeEnd;
if (!haveCaret) {
// Retrieve highlight region for selection range.
Shape highlight =
textLayout.getLogicalHighlightShape(anchorEnd, activeEnd);
// Fill the highlight region with the highlight color.
graphics2D.setColor(HIGHLIGHT_COLOR);
graphics2D.fill(highlight);
}
// ...
}
// ...
private class SelectionMouseMotionListener extends MouseMotionAdapter {
public void mouseDragged(MouseEvent e) {
Point2D origin = computeLayoutOrigin();
// Compute the mouse location relative to
// textLayout's origin.
float clickX = (float) (e.getX() - origin.getX());
float clickY = (float) (e.getY() - origin.getY());
// Get the character position of the mouse location.
TextHitInfo position = textLayout.hitTestChar(clickX, clickY);
int newActiveEnd = position.getInsertionIndex();
// If newActiveEnd is different from activeEnd, update activeEnd
// and repaint the Panel so the new selection will be displayed.
if (activeEnd != newActiveEnd) {
activeEnd = newActiveEnd;
frame.repaint();
}
}
}
private class SelectionMouseListener extends MouseAdapter {
public void mousePressed(MouseEvent e) {
Point2D origin = computeLayoutOrigin();
// Compute the mouse location relative to
// TextLayout's origin.
float clickX = (float) (e.getX() - origin.getX());
float clickY = (float) (e.getY() - origin.getY());
// Set the anchor and active ends of the selection
// to the character position of the mouse location.
TextHitInfo position = textLayout.hitTestChar(clickX, clickY);
anchorEnd = position.getInsertionIndex();
activeEnd = anchorEnd;
// Repaint the Panel so the new selection will be displayed.
frame.repaint();
}
}
方法SelectionMouseListener.mousePressed
指定了变量anchorEnd
,它是鼠标点击的文本位置。方法SelectionMouseMotionListener.mouseDragged
指定了变量activeEnd
,它是鼠标拖动到的文本位置。paint
方法检索一个代表所选文本的Shape
对象(即anchorEnd
和activeEnd
位置之间的文本)。然后paint
方法用高亮颜色填充Shape
对象。
教程:处理图像
正如您已经从 图像 教程中了解的那样,Image
由以像素为单位的宽度和高度描述,并且具有与绘图表面无关的坐标系。
处理图像时有许多常见任务。
-
将外部 GIF、PNG JPEG 图像格式文件加载到 Java 2D 使用的内部图像表示中。
-
直接创建 Java 2D 图像并对其进行渲染。
-
将 Java 2D 图像的内容绘制到绘图表面上。
-
将 Java 2D 图像的内容保存到外部 GIF、PNG 或 JPEG 图像文件中。
本课程教授如何加载、显示和保存图像的基础知识。
您必须了解的两个主要类来处理图像:
-
java.awt.Image
类是表示图形图像的像素矩形数组的超类。 -
java.awt.image.BufferedImage
类扩展了Image
类,允许应用程序直接操作图像数据(例如,检索或设置像素颜色)。应用程序可以直接构造此类的实例。
BufferedImage
类是 Java 2D 立即模式成像 API 的基石。它管理内存中的图像,并提供存储、解释和获取像素数据的方法。由于 BufferedImage
是 Image
的子类,因此可以通过接受 Image
参数的 Graphics
和 Graphics2D
方法来呈现它。
BufferedImage
本质上是具有可访问数据缓冲区的 Image
。因此,直接使用 BufferedImage
更有效。BufferedImage
具有 ColorModel 和图像数据的 Raster。ColorModel 提供图像像素数据的颜色解释。
Raster 执行以下功能:
-
表示图像的矩形坐标
-
在内存中维护图像数据
-
提供从单个图像数据缓冲区创建多个子图像的机制
-
提供访问图像内特定像素的方法
图像的基本操作在以下部分中表示:
读取/加载图像
本节解释了如何使用 Image I/O API 将外部图像格式的图像加载到 Java 应用程序中。
绘制图像
本节教授如何使用 Graphics
和 Graphics2D
类的 drawImage
方法显示图像。
创建和绘制到图像
本节描述了如何创建图像以及如何将图像本身用作绘图表面。
写入/保存图像
本节解释了如何以适当的格式保存创建的图像。
读取/加载图像
当你想到数字图像时,你可能会想到采样图像格式,比如数字摄影中使用的 JPEG 图像格式,或者网页常用的 GIF 图像。所有可以使用这些图像的程序都必须首先将它们从外部格式转换为内部格式。
Java 2D 支持使用其 Image I/O API 将这些外部图像格式加载到其 BufferedImage
格式中,该 API 位于 javax.imageio
包中。Image I/O 内置支持 GIF、PNG、JPEG、BMP 和 WBMP。Image I/O 也是可扩展的,因此开发人员或管理员可以为其他格式“插入”支持。例如,TIFF 和 JPEG 2000 的插件是单独可用的。
要从特定文件加载图像,请使用以下代码,这段代码来自 LoadImageApp.java
:
BufferedImage img = null;
try {
img = ImageIO.read(new File("strawberry.jpg"));
} catch (IOException e) {
}
Image I/O 将文件的内容识别为 JPEG 格式图像,并将其解码为可以被 Java 2D 直接使用的 BufferedImage
。
LoadImageApp.java
展示了如何显示这个图像。
如果代码在小程序中运行,那么从小程序代码库获取图像就像轻而易举一样。以下摘录来自 LoadImageApplet.java
:
try {
URL url = new URL(getCodeBase(), "examples/strawberry.jpg");
img = ImageIO.read(url);
} catch (IOException e) {
}
这个示例中使用的 getCodeBase
方法返回部署在 Web 服务器上时包含这个小程序的目录的 URL。如果小程序是本地部署的,getCodeBase
返回 null,小程序将无法运行。
以下示例展示了如何使用 getCodeBase
方法加载 strawberry.jpg
文件。
注意: 如果你看不到小程序运行,你需要至少安装 Java SE Development Kit (JDK) 7 版本。
LoadImageApplet.java
包含了这个示例的完整代码,这个小程序需要 strawberry.jpg
图像文件。
除了从文件或 URLS 读取外,Image I/O 还可以从其他来源读取,比如 InputStream
。ImageIO.read()
对于大多数应用程序来说是最直接的便利 API,但 javax.imageio.ImageIO
类提供了更多静态方法,用于更高级的 Image I/O API 的用法。这个类上的方法集仅代表了用于发现关于图像信息和控制图像解码(读取)过程的丰富 API 集合的一部分。
我们将在 写入/保存图像 部分中进一步探讨 Image I/O 的其他功能。
绘制图像
正如您已经了解的那样,Graphics.drawImage
方法在特定位置绘制图像:
boolean Graphics.drawImage(Image img,
int x, int y,
ImageObserver observer);
x,y
位置指定了图像左上角的位置。observer
参数通知应用程序异步加载的图像更新。observer
参数通常不直接使用,对于BufferedImage
类来说,通常为 null。
描述的方法仅适用于整个图像要绘制的情况,将图像像素映射到用户空间坐标 1:1。有时应用程序需要绘制图像的一部分(子图像),或者缩放图像以覆盖绘图表面的特定区域,或在绘制之前对图像进行变换或过滤。
drawImage()
方法的重载执行这些操作。例如,drawImage()
方法的以下重载使您可以绘制指定图像的指定区域的尽可能多的部分,将其缩放以适合目标可绘制表面的指定区域:
boolean Graphics.drawImage(Image img,
int dstx1, int dsty1, int dstx2, int dsty2,
int srcx1, int srcy1, int srcx2, int srcy2,
ImageObserver observer);
src
参数表示要复制和绘制的图像区域。dst
参数显示要由源区域覆盖的目标区域。dstx1, dsty1
坐标定义了绘制图像的位置。目标区域的宽度和高度维度由以下表达式计算:(dstx2-dstx1), (dsty2-dsty1)
。如果源区域和目标区域的尺寸不同,Java 2D API 将根据需要进行放大或缩小。
以下代码示例将图像分成四个象限,并随机将源图像的每个象限绘制到目标的不同象限。
注意: 如果您看不到小程序运行,您需要安装至少Java SE Development Kit (JDK) 7版本。
此小程序的完整代码在JumbledImageApplet.java
中。
此示例使用以下代码绘制混乱的duke_skateboard.jpg
图像。它迭代源图像的四个子图像,依次将每个子图像绘制到随机选择的目标象限中。
/* divide the image 'bi' into four rectangular
* areas and draw each of these areas in to a
* different part of the image, so as to jumble
* up the image. 'cells' is an array which has
* been populated with values which redirect
* drawing of one subarea to another subarea.
*/
int cellWidth = bi.getWidth(null)/2;
int cellHeight = bi.getHeight(null)/2;
for (int x=0; x<2; x++) {
int sx = x*cellWidth;
for (int y=0; y<2; y++) {
int sy = y*cellHeight;
int cell = cells[x*2+y];
int dx = (cell / 2) * cellWidth;
int dy = (cell % 2) * cellHeight;
g.drawImage(bi,
dx, dy,
x+cellWidth, dy+cellHeight,
sx, sy,
sx+cellWidth, sy+cellHeight,
null);
}
}
图像过滤
除了复制和缩放图像外,Java 2D API 还可以对图像进行滤镜处理。滤镜是通过将算法应用于源图像的像素来绘制或生成新图像。可以使用以下方法应用图像滤镜:
void Graphics2D.drawImage(BufferedImage img,
BufferedImageOp op,
int x, int y)
BufferedImageOp
参数实现了滤镜。以下小程序代表了一个在文本上方绘制的图像。拖动滑块以通过图像显示更多或更少的文本,并使图像更加透明。
注意: 如果您看不到小程序运行,请至少安装Java SE Development Kit (JDK) 7版本。
以下代码显示了如何通过使用RescaleOp
对象对带有alpha通道的BufferedImage
对象执行滤镜操作,并通过该对象重新调整 alpha 通道。alpha 通道确定每个像素的透明度。它还指定了此图像覆盖的程度。
/* Create an ARGB BufferedImage */
BufferedImage img = ImageIO.read(imageSrc);
int w = img.getWidth(null);
int h = img.getHeight(null);
BufferedImage bi = new
BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.getGraphics();
g.drawImage(img, 0, 0, null);
/*
* Create a rescale filter op that makes the image
* 50% opaque.
*/
float[] scales = { 1f, 1f, 1f, 0.5f };
float[] offsets = new float[4];
RescaleOp rop = new RescaleOp(scales, offsets, null);
/* Draw the image, applying the filter */
g2d.drawImage(bi, rop, 0, 0);
完整示例在SeeThroughImageApplet.java
中包含了使用滑块调整透明度从初始 50%的代码。此示例还需要 duke_skateboard.jpg 图像。
RescaleOp
对象只是可以创建的许多滤镜之一。Java 2D API 具有几种内置滤镜,包括以下内容:
-
ConvolveOp
。每个输出像素都是从源图像中周围像素计算出来的。可用于模糊或锐化图像。 -
AffineTransformOp
。此滤镜通过在像素位置上应用变换将源中的像素映射到目标中的不同位置。 -
LookupOp
。此滤镜使用应用提供的查找表重新映射像素颜色。 -
RescaleOp
。此滤镜将颜色乘以某个因子。可用于使图像变亮或变暗,增加或减少其不透明度等。
以下示例使用了描述的每个滤镜以及缩放:
注意: 如果您看不到小程序运行,请至少安装Java SE Development Kit (JDK) 7版本。
此小程序的完整代码在ImageDrawingApplet.java
中,此小程序需要 bld.jpg 图像。
使用下拉菜单选择图像缩放或滤镜操作。
创建和绘制图像
我们已经知道如何加载现有图像,该图像在您的系统中创建并存储,或者在任何网络位置。但是,您可能还想创建一个新图像作为像素数据缓冲区。
在这种情况下,您可以手动创建一个 BufferedImage
对象,使用该类的三个构造函数:
-
new BufferedImage(width, height, type)
- 构造一个预定义图像类型的BufferedImage
。 -
new BufferedImage(width, height, type, colorModel)
- 构造一个预定义图像类型的BufferedImage
:TYPE_BYTE_BINARY
或TYPE_BYTE_INDEXED
。 -
new BufferedImage(colorModel, raster, premultiplied, properties)
- 使用指定的ColorModel
和Raster
构造一个新的BufferedImage
。
另一方面,我们可以使用 Component
类的方法。这些方法可以分析给定 Component
或 GraphicsConfiguration
的显示分辨率,并创建一个适当类型的图像。
-
Component.createImage(width, height)
-
GraphicsConfiguration.createCompatibleImage(width, height)
-
GraphicsConfiguration.createCompatibleImage(width, height, transparency)
GraphicsConfiguration 返回一个 BufferedImage 类型的对象,但 Component 返回一个 Image 类型的对象,如果您需要一个 BufferedImage 对象,那么您可以在代码中执行 instanceof
并转换为 BufferedImage。
正如在之前的课程中已经提到的,我们不仅可以在屏幕上渲染图像。图像本身可以被视为一个绘图表面。您可以使用 BufferedImage
类的 createGraphics()
方法来实现这一目的:
...
BufferedImage off_Image =
new BufferedImage(100, 50,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = off_Image.createGraphics();
另一个有趣的离屏图像的用途是自动双缓冲。这个功能允许通过将图像绘制到后备缓冲区,然后将该缓冲区复制到屏幕上,而不是直接绘制到屏幕上,从而避免动画图像的闪烁。
Java 2D 还允许访问用于离屏图像的硬件加速,这可以提供更好的渲染性能以及从这些图像复制。您可以通过使用 Image
类的以下方法来获得此功能的好处:
-
getCapabilities
方法允许您确定图像当前是否加速。 -
setAccelerationPriority
方法允许您设置有关图像加速重要性的提示。 -
getAccelerationPriority
方法获取有关加速重要性的提示。
写入/保存图像
本课程从解释如何使用javax.imageio
包开始,将图像从外部图像格式加载到 Java 2D 使用的内部BufferedImage
格式。然后解释如何使用Graphics.drawImage()
来绘制该图像,可选择进行滤镜处理。
最后一阶段是将BufferedImage
对象保存为外部图像格式。这可能是最初由Image I/O
类从外部图像格式加载并可能使用 Java 2D API 修改的图像,也可能是由 Java 2D 创建的图像。
Image I/O
类提供了一种简单的方法,在以下示例中以各种图像格式保存图像:
static boolean ImageIO.write(RenderedImage im,
String formatName,
File output) throws IOException
注意: BufferedImage
类实现了RenderedImage
接口。
。
formatName
参数选择要保存BufferedImage
的图像格式。
try {
// retrieve image
BufferedImage bi = getMyImage();
File outputfile = new File("saved.png");
ImageIO.write(bi, "png", outputfile);
} catch (IOException e) {
...
}
ImageIO.write
方法调用实现 PNG 写入的代码,即“PNG 写入插件”。术语插件用于Image I/O
是可扩展的,可以支持各种格式。
但以下标准图像格式插件:JPEG、PNG、GIF、BMP 和 WBMP 始终存在。
每种图像格式都有其优点和缺点:
格式 | 优点 | 缺点 |
---|---|---|
GIF | 支持动画和透明像素 | 仅支持 256 种颜色和不支持半透明 |
PNG | 比 GIF 或 JPG 更好的选择,用于高色彩无损图像,支持半透明 | 不支持动画 |
JPG | 适用于摄影图像 | 压缩损失大,不适合文本、屏幕截图或需要完全保留原始图像的任何应用程序 |
对于大多数应用程序来说,使用这些标准插件就足够了。它们的优点是易于获取。Image I/O
类提供了一种插入对额外格式的支持的方式,可以使用许多这样的插件。如果您想知道系统中可加载或保存的文件格式,可以使用ImageIO
类的getReaderFormatNames
和getWriterFormatNames
方法。这些方法返回一个字符串数组,列出了此 JRE 支持的所有格式。
String writerNames[] = ImageIO.getWriterFormatNames();
返回的名称数组将包括任何已安装的附加插件,这些名称中的任何一个都可以用作格式名称来选择图像写入器。以下代码示例是一个简单版本的完整图像编辑/修饰程序,使用了修订版本的ImageDrawingApplet.java
示例程序,可以按以下方式使用:
-
图像首先通过 Image I/O 加载
-
用户从下拉列表中选择一个滤镜,然后绘制一个新的更新图像
-
用户从下拉列表中选择保存格式
-
接下来会出现文件选择器,用户选择保存图像的位置
-
修改后的图像现在可以被其他桌面应用程序查看。
这个示例的完整代码在SaveImage.java
中表示。
在本课程中,您仅学习了Image I/O
的基础知识,该知识提供了广泛的支持,包括直接使用ImageWriter
插件来实现对编码过程的更精细控制。 ImageIO 可以写入多个图像、图像元数据,并确定质量与大小之间的权衡。更多信息请参见Java Image I/O API 指南。
课程:打印
由于 Java 2D API 使您能够在任何表面上绘制,因此其自然扩展是能够打印 Java 2D 图形。打印机可以被视为与显示器一样的图形设备。
Java 2D 打印 API 不仅限于打印图形。它还使您能够打印应用程序用户界面的内容。内容可以通过将原始数据发送到打印机并在 Java 2D 打印 API 的格式控制下进行打印,或者使用 Java 2D 图形 API 来打印。
在这节课中,您将探索 Java 2D 打印 API 的打印机和作业控制功能,这些功能是对渲染元素的补充。您将学习如何查找系统或网络上配置的打印机,并发现有关这些打印机的信息,例如支持的纸张尺寸,并选择这些属性进行打印和用户对话框。
参与打印的主要类和接口在java.awt.print
和javax.print
包中(最后一个包允许您访问打印服务)。
基本的打印操作在以下部分中表示:
-
一个基本的打印程序 – 本节描述了
Printable
接口并呈现了一个基本的打印程序。 -
使用打印设置对话框– 本节解释了如何显示打印设置对话框。
-
打印多页文档 – 本节解释了如何使用分页打印多页文档。
-
使用打印服务和属性 – 本节教您有关打印服务的知识,如何指定打印数据格式,以及如何使用
javax.print
包创建打印作业。 -
打印用户界面的内容 – 本节解释了如何打印窗口或框架的内容。
-
Swing 组件中的打印支持 – 本节简要描述了
Swing
中相关打印功能,并引用了特定的Swing
类和接口。