Java 中文官方教程 2022 版(三十一)(3)https://developer.aliyun.com/article/1487984
使用 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 图像。
使用下拉菜单选择图像缩放或滤镜操作。
创建和绘制图像
原文:
docs.oracle.com/javase/tutorial/2d/images/drawonimage.html
我们已经知道如何加载现有图像,该图像在您的系统中创建并存储,或者在任何网络位置。但是,您可能还想创建一个新图像作为像素数据缓冲区。
在这种情况下,您可以手动创建一个 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
类和接口。