Java图形图像处理【基础篇】【二】
Java图形图像处理【基础篇】
- 18.1.4 Java 2D图形类库**
- 18.1.5 文本绘制与Font相关类
18.1.4 Java 2D图形类库**
自从Java SE 2以来,Java还引入了Java2D图形类库。Shape是Java 2D图形库中表示几何图形的接口。它定义了一个几何图形的轮廓,并提供了用于检测坐标中的Point(点)是否在几何图形内、计算图形的边界框等方法。
Shape类的常用方法:
(1)boolean contains(double x, double y):判断点是否在几何图形内。
(2)Rectangle getBounds():返回几何图形的边界矩形。
(3)PathIterator getPathIterator(AffineTransform at):返回几何图形的路径迭代器,用于遍历几何图形的轮廓。
Java2D图形类库的几何图形都是Shape接口实现类。都在java.awt.geom包中定义,常用的几何图形类有:
- Line2D类:直线类
- Ellipse2D类:椭圆类
- RoundRectangle2D类:圆角矩形类
- Rectangle2D类:矩形类
- Arc2D类:圆弧类
- QuadCurve2D类:二次曲线类
- CubicCurve2D类:三次曲线类
- GeneralPath:自定义几何图形路径。
自从Java SE 2版本以来,Java引入了Graphics2D类和Java2D图形类库,可提供几何学、坐标变换、颜色管理以及文本排列等更高级的控制。
Java2D图形类库中的所有图形类都实现了Shape接口,继承关系如下:
说明:在Java 1.0版本中,图形类中的坐标使用整型(int)坐标,但在Java 2D图形库中的坐标采用浮点类型(float, double)坐标。类库的设计者为每个2D基本图形类设计了两个版本,但为了避免变成两个独立的图形类,使用静态内部类的方式来实现。
图中的Point和Rectangle类是Java 1.0版本中的遗留类,也被整合到2D图形类的继承层次中,作为对应的2D图形类的子类。Point2D和Rectangle2D都是抽象类,坐标点Point2D类内部定义了两个静态内部类Point2D.Float和Point2D.Double,都可作为实现类使用;类似地Rectangle2D也是如此。下面是创建Point2D实例的示例:
Point2D point2d = new Point2D.Double(16.5, 28.2);Point2D point2d2 = new Point2D.Float(5.3f, 56.5f);
从示例可看出,由于Java中浮点数的默认类型是double,使用Float内部类时参数要通过强制类型转换为float类型。但是float类型占用的空间是double类型的一半,当定义大批量2D图形时,使用Float静态内部类可节约内存空间。
GeneralPath是Shape接口的一个实现类,用于创建自定义的几何图形。它通过一系列的点来定义几何图形的轮廓,支持直线、二次曲线和三次曲线的绘制。
当要绘制比基本图形更复杂的图形时,例如多边形,星形,你就要使用GeneralPath类。几何路径GeneralPath可由线段、二次曲线或三次曲线一起组成复杂的几何路径图形。
GeneralPath类的两个构造器方法:
(1)Public GeneralPath() 以默认的弯曲规则构造一个空的几何路径对象。
(2)Public GeneralPath(int rule,int n) 参数rule是弯曲规则,使用GeneralPath类常量,有两个常量值WIND_NON_ZERO(默认值)和WIND_EVEN_ODD。弯曲规则决定内部路径。参数n,是图形顶点数量,又称路径的容量。
GeneralPath类的常用方法:
(1)moveTo(float x,float y) 移动到坐标点(x,y),在路径上添加此点。
(2)lineTo(float x,float y) 从当前坐标点到(x,y)坐标点画一条直线,将此点添加到路径上。
(3)quadTo(float x1,float y1,float x2,float y2) 以(x1,y1)和(x2,y2)坐标点之间插入二次曲线片断。
(4)curveTo(float x1,float y1,float x2,float y2,float x3,float y3) 以(x1,y1)和(x2,y2)为控制点,在当前坐标点和(x3,y3)之间插入三次曲线片断。
(5)closePath() 闭合路径。这个方法将路径的当前点和起始点连接形成封闭图形。
另有方法fill(Shape s) 绘制实心几何图形。
若要将特定的路径加到GeneralPath对象中,可以使用append()方法。
Graphics2D类继承于Graphics类,Java 2D绘图需要获得一个Graphics2D类对象,在paintComponent方法中,可把Graphics对象强制类型转换为Graphics2D类对象。利用Graphics2D类,Java有两种绘制图形方法:
(一)public abstract void draw(Shape s) 直接绘制基本图形,如下表。
圆弧Arc2D类可绘制圆弧(椭圆的一部分曲线)。所有的基本图形类都实现了Shape接口。除了Line2D以外的基本图形类都属于矩形类图形,继承自RectangularShape。GeneralPath(几何路径)可用于绘制复杂图形,也实现了Shape接口。
(二)先用GeneralPath类绘制任意图形,生成Shape实例,然后再调用draw()方法。当要绘制比基本图形更复杂的图形时,例如多边形,星形,你就要使用GeneralPath类。
【例程18-4】Graphics2D绘图例程GeneralPathTest
下面的例程演示用Graphics2D绘制几何图形,以及设置笔画属性。Java中没有在一个坐标上画点的方法,用直线图形演示绘制了一个圆点。
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.*;
import javax.swing.*;
import static java.awt.BasicStroke.*;
/**** @author QiuGen* @description Graphics2D绘图例程GeneralPathTest* @date 2023/7/15* ***/
public class GeneralPathTest extends JComponent {GeneralPath polygon =null; //几何路径public GeneralPathTest() {setPreferredSize(new Dimension(220,100)); //设置面板大小crtGeneralPath(); //创建五角星的几何路径}private void crtGeneralPath(){ //创建五角星的几何路径int x_Points[] = {12,88,26,50,74};int y_Points[] = {43,43,87,15,87};int rule = GeneralPath.WIND_EVEN_ODD;polygon = new GeneralPath(rule, x_Points.length);polygon.moveTo(x_Points[0], y_Points[0]);for (int i = 1; i < x_Points.length; i++) {polygon.lineTo(x_Points[i], y_Points[i]);}polygon.closePath();}@Overrideprotected void paintComponent(Graphics g) {Graphics2D g2d = (Graphics2D)g; //画笔转换g2d.draw( polygon ); //画任意的几何路径图形Shape ellipse2d= new Ellipse2D.Double(140,40,60,40);g2d.draw( ellipse2d ); //画椭圆g2d.setColor(Color.BLUE); //设置画笔颜色Stroke stroke= new BasicStroke(20,CAP_ROUND,JOIN_ROUND);g2d.setStroke(stroke); //设置画笔的笔画宽度Shape line = new Line2D.Double(116,60,116,60); //圆点g2d.draw( line ); //画直线(特例:画圆点)g2d.fill(polygon); //填充图形
g2d.dispose(); //释放g2d对象,避免内存泄漏}public static void main(String[] args) {JFrame frame = new JFrame("Graphics2D绘图");GeneralPathTest c = new GeneralPathTest();frame.add(c);frame.pack();frame.setVisible(true);}
} //Graphics2D绘图例程,源代码GeneralPathTest.java结束。
例程运行效果图:
18.1.5 文本绘制与Font相关类
在Java中,Font类用于表示字体。通过Font类,你可以设置文本的字体名称、样式(如粗体、斜体)和大小。Font类通常与Graphics2D类一起使用,用于在图形上下文中绘制文本。
Font类封装了字体相关的属性。字体的属性包含字体家族名称、字体样式和字号大小。字体家族名称与我们在字处理软件WPS中所用到的字体名是相同的。
创建Font字体对象。Font类的构造方法如下:
Font(String familyName, int style, int size)
参数说明:
- familyName 字体名称表示一种字体,例如:宋体、仿宋体、Arial等。
- style 字体样式,它需要使用Font类的常量来指定。字体样式的常量值:
Font.PLAIN 普通字体;Font.BOLD 粗体;Font.ITALIC 斜体;Font.BOLD+Font.ITALIC 或 Font.BOLD|Font.ITALIC 粗斜体。
- size 字号大小,其默认单位是磅(pt),数字越大,字体越大。
说明:英文的字体家族名称只对英文有效(如Arial,Times New Roman等),对中文无效。但中文的字体家族名都可兼容英文文本显示。
下面是创建字体对象的例子:
Font font1 = new Font("Arial Black" ,Font.PLAIN, 36);Font font2 = new Font("宋体" ,Font.BOLD+Font.ITALIC, 24);Font font = new Font("宋体", Font.BOLD | Font.ITALIC, 24);
AWT定义了5种逻辑字体名称:Dialog、DialogInput、Monospaced、Serif和SansSerif。这些逻辑字体是跨平台的,它们会映射到不同操作系统上的实际字体。使用逻辑字体可以确保Java程序在不同平台上具有一致的字体显示效果。
逻辑字体的映射
逻辑字体的实际显示效果取决于操作系统和Java运行时环境(JRE)。Java会将逻辑字体映射到系统上可用的物理字体。例如:
(一)在 Windows 上:
Serif 可能映射到 Times New Roman。SansSerif 可能映射到 Arial。Monospaced 可能映射到 Courier New。
(二)在 macOS 上:
Serif 可能映射到 Times。SansSerif 可能映射到 Helvetica。Monospaced 可能映射到 Courier。
Font 类提供了一些方法来获取字体的属性:
(1)getFontName(): 获取字体名称。
(2)getFamily(): 获取字体的家族名称。
(3)getSize(): 获取字体大小。
(4)getStyle(): 获取字体样式(PLAIN、BOLD、ITALIC 或其组合)。
(5)isBold(): 判断字体是否为粗体。
(6)isItalic(): 判断字体是否为斜体。
(7)deriveFont(int style, float size): 基于当前字体创建一个新字体,可以修改样式和大小。
【例程18-5】显示电脑系统可用字体的例程ListFonts
源代码文件ListFonts.java如下:
import java.awt.GraphicsEnvironment;
public class ListFonts {public static void main(String[] args) {GraphicsEnvironment env=null;env=GraphicsEnvironment.getLocalGraphicsEnvironment();String[] fontNames =env.getAvailableFontFamilyNames();for (String fontName : fontNames) { System.out.println(fontName); }}
} //源代码文件ListFonts.java结束。
GraphicsEnvironment类描述了电脑系统的图形环境,上面的程序演示了如何查看电脑系统中所安装的可用字体。
【例程18-6】预览字体的列表框例程FontCellRender
这个例程是预览字体的列表框的第三个版本,自定义单元格的绘制。前两个版本的例程列表的数据类型都使用了String,本例则使用Font类型。
import java.awt.*;
import javax.swing.*;
/**** @author QiuGen* @description 预览字体的列表框例程FontCellRender* @date 2023/7/18* ***/
public class FontCellRender extends JComponent implements ListCellRenderer<Font> {private Font font;private Color backGround;private Color foreGround;@Overridepublic Component getListCellRendererComponent(JList<? extends Font> list, Font font, int index, boolean isSelected,boolean cellHasFocus) {backGround = isSelected ? list.getSelectionBackground() : list.getBackground();foreGround = isSelected ? list.getSelectionForeground() : list.getForeground();this.font = font; return this;}@Overrideprotected void paintComponent(Graphics g) {String txt = font.getFamily();FontMetrics fm = g.getFontMetrics(font);g.setColor(backGround);g.fillRect(0, 0, getWidth(), getHeight());//单元格大小g.setColor(foreGround);g.setFont(font);g.drawString(txt, 0, fm.getAscent());}@Overridepublic Dimension getPreferredSize() { //设置单元格首选大小String txt = font.getFamily();Graphics g = getGraphics();FontMetrics fm = g.getFontMetrics(font);return new Dimension( fm.stringWidth(txt), fm.getHeight());}public static void main(String[] args) {GraphicsEnvironment env=null; //工具类:图形环境对象env=GraphicsEnvironment.getLocalGraphicsEnvironment();String[] fontNames =env.getAvailableFontFamilyNames();DefaultListModel<Font> fontModel = new DefaultListModel<>();for (String fName : fontNames) //准备数据{Font font = new Font(fName, Font.BOLD, 24);fontModel.addElement(font);}JList<Font> fontList = new JList<>(fontModel);ListCellRenderer<Font> render = new FontCellRender();fontList.setCellRenderer(render);JFrame frame = new JFrame("预览字体列表");frame.add(new JScrollPane(fontList), BorderLayout.CENTER);frame.setSize(300, 200);frame.setVisible(true);}
} //预览字体列表框例程第三个版本
测试效果图:
在Java中,FontMetrics类用于获取字体的度量信息,例如字符的宽度、高度、行间距等。通过FontMetrics,可以更精准地控制文本的绘制位置,实现居中、对齐等效果。结合Graphics或Graphics2D,可以灵活地绘制文本。
FontMetrics对象可以通过Graphics或Graphics2D的getFontMetrics()方法获取。你需要先设置字体,然后获取对应的FontMetrics。
FontMetrics的常用方法:
- int getAscent():获取字体的上坡度(从基线到字符顶部的距离)。
- int getDescent():获取字体的下坡度(从基线到字符底部的距离)。
- int getHeight():获取字体的总高度(ascent + descent + leading)。
- int stringWidth(String str):获取字符串的总宽度。
- int charWidth(char ch):获取单个字符的宽度。
- int getLeading():获取行间距(上一行底部到下一行顶部的距离)。
文本绘制时,若要精准控制绘制的位置,就要做一些额外的工作。精准绘制文本需要清楚几个排版术语:
基线(baseline)是一条虚构的线,像英文字母“e”的底线。字母的上坡度(ascent)是从基线到字符坡顶的距离;字母的下坡度(descent)是从基线到文本底部的距离。行间距(leading)是二行之间的间距。字体的高度是连续两个基线之间的距离,它等于上坡度+下坡度+间距。
【例程18-7】利用文本度量自动换行绘制例程ChineseTextWrap
这是一个文本度量绘制自动换行的例程ChineseTextWrap.java。
程序源代码如下所示:
import javax.swing.*;
import java.awt.*;
/**** @author QiuGen* @description 利用文本度量自动换行绘制例程ChineseTextWrap* @date 2023/7/22* ***/
public class ChineseTextWrap extends JComponent {String text = "春城万户瓮醪香,闻道明州是醉乡。莫怪儿童笑山简,明州风物胜襄阳。——《明州歌选》明 李濂";@Overrideprotected void paintComponent(Graphics g) {Graphics2D g2d = (Graphics2D) g;// 设置字体Font font = new Font("华文彩云", Font.BOLD, 24);g2d.setFont(font);g2d.setColor(Color.BLUE);// 获取 FontMetricsFontMetrics metrics = g2d.getFontMetrics();int margin = 20; // 面板的左右边距// 每行的最大宽度int maxWidth = getWidth() - 2 * margin;// 起始坐标int x = margin;int y = margin + metrics.getAscent();// 分割文本为字符数组char[] chars = text.toCharArray();// 当前行的宽度int currentLineWidth = 0;for (char ch : chars) { // 遍历每个字符// 获取字符的宽度int charWidth = metrics.charWidth(ch);// 如果当前行宽度加上字符宽度超过最大宽度,则换行if (currentLineWidth + charWidth > maxWidth) {y += metrics.getHeight(); // 移动到下一行x = margin; // 重置 x 坐标currentLineWidth = 0; // 重置当前行宽度}// 绘制字符g2d.drawString(String.valueOf(ch), x, y);// 更新当前行宽度和 x 坐标x += charWidth;currentLineWidth += charWidth;}}public static void main(String[] args) {JFrame frame = new JFrame("文本的度量绘制测试");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setSize(460, 170);frame.add(new ChineseTextWrap());frame.setVisible(true);}
} //文本度量绘制自动换行例程ChineseTextWrap.java源代码结束。
测试效果图:
通过FontMetrics实现中文文本的自动换行,可以精确控制文本的布局和显示效果。这种方法适用于需要动态调整文本显示的场景,例如文本编辑器、聊天窗口等。
屏幕设备字体绘制渲染属性还可通过Graphics2D类按如下获得。
Graphics2D类的getFontRenderContext()方法用于获取当前字体渲染上下文FontRenderContext对象。它包含了字体渲染时所需的信息,例如抗锯齿设置、文本缩放、变换等。
FontRenderContext对象描述了当前Graphics2D上下文的字体渲染环境。它包含以下功能:
- 抗锯齿设置:是否启用抗锯齿。
- 信息的仿射变换:当前的仿射变换(如缩放、旋转等)。
- 文本缩放:文本的缩放比例。
FontRenderContext有一个返回当前对象的仿射变换的方法:
AffineTransform getTransform()
在下面的例子中,对文本信息显示位置进行了精确定位,用到了字体绘制渲染器对象FontRenderContext,这个对象包含了字体排版属性:
FontRenderContext context = g2d.getFontRenderContext();
把文本信息message和句柄context传递给字体的getStringBounds方法可得到文本信息所占的屏幕空间范围:
Rectangle2D bounds=font.getStringBounds(message, context);
getStringBounds方法返回的矩形空间,其宽度是文本的水平方向宽度;其高度是文本字体的高度。
如果要获取字母的下坡度和行间距,可调用Font类的getLineMetrics方法:
LineMetrics metrics = font.getLineMetrics(message, context);float descent = metrics.getDescent();float leading = metrics.getLeading();
特别说明:在用“g2d.drawString(message, (int)x, (int)baseY);”绘制文本时,文本是绘制在基线坐标的上方。这与图形图像绘制方式不同,图形图像绘制是绘制在下方:
g.drawImage(image, x, y, null); //绘制在坐标(x,y)的下方
【例程18-8】文本度量精准定位绘制例程DrawTextPane
例程DrawTextPane.java的paintComponent方法,把画笔原来的默认颜色(黑色)设置为蓝色,把文本信息显示在组件中间位置。
在组件中央绘制文本,源代码文件DrawTextPane.java开始:
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.*;
import javax.swing.*;
/**** @author QiuGen* @description 文本度量精准定位绘制例程DrawTextPane* @date 2023/7/22* ***/
public class DrawTextPane extends JComponent {private static final int DEFAULT_WIDTH = 440;private static final int DEFAULT_HEIGHT = 100;private String message = "吉祥如意Good luck happiness";//文本信息public DrawTextPane() { //在构造器中设置面板的首选大小setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));}@Overrideprotected void paintComponent(Graphics g) { //g.drawString(message, 100, 50);Graphics2D g2d=(Graphics2D)g; //转换画笔//创建字体实例Font font = new Font("华文隶书", Font.BOLD, 32);g2d.setFont(font); //设置画笔的字体//度量文本信息的大小。FontRenderContext是字体渲染器对象FontRenderContext context=g2d.getFontRenderContext();//获取文本信息所占空间的范围Rectangle2D bnds=font.getStringBounds(message, context);double x=(getWidth() - bnds.getWidth())/2;double y=(getHeight() - bnds.getHeight())/2; double ascent = -bnds.getY(); //计算上坡度(ascent)double baseY = y + ascent; //基线的y坐标g2d.setPaint(Color.BLUE); //设置画笔颜色g2d.drawString(message, (int)x, (int)baseY); //绘文本g2d.setPaint(Color.LIGHT_GRAY); //设置画笔颜色,画基线和外框g2d.draw(new Line2D.Double(x, baseY, x+bnds.getWidth(), baseY));g2d.draw(new Rectangle2D.Double(x,y,bnds.getWidth(),bnds.getHeight()));}public static void main(String[] args) {JFrame frame = new JFrame("绘制文本测试");frame.add(new DrawTextPane());frame.pack(); //按组件的首选大小显示frame.setVisible(true);}
} //在组件中央绘制文本,源代码文件DrawTextPane.java结束。
在此例程中,用“g2d.setColor(Color.BLUE);”设置画笔颜色,效果也相同。
测试效果图:
说明:如果在paintComponent方法外面,要精确定位文本位置就不能从Graphics2D句柄得到字体绘制渲染器对象FontRenderContext,可通过调用组件超类JComponent中继承的getFontMetrics方法,通过FontMetrics实例得到字体渲染器对象:
FontRenderContext context=getFontMetrics(font).getFontRenderContext();
博客导航:
上一篇博客:Java图形图像处理【基础篇】【一】
下一篇博客:[Java图形图像处理【基础篇】【三】