前面介绍了如何使用画笔工具Graphics绘制各种图案,然而Graphics并不完美,它的遗憾之处包括但不限于:
1、不能设置背景颜色;
2、虽然提供了平移功能,却未提供旋转功能与缩放功能;
3、只能在控件上作画,无法将整幅画保存为图片;
有鉴于此,AWT提供了Graphics的升级版名叫Graphics2D,这个二维画笔不但继承了画笔的所有方法,而且拓展了好几个实用的方法,包括设置背景色的setBackground方法,旋转画布的rotate方法,缩放画布的scale方法等。尤为关键的是,Graphics2D允许在图像缓存BufferedImage上作画,意味着二维画笔的绘图成果能够保存为图片文件。这可是重大的功能改进,因为一旦保存为图片,以后就能随时拿出来用,不必每次都重新绘画了。
那么要怎样获得二维画笔呢?这还得从缓存图像BufferedImage说起。之前获取缓存图像的时候,是通过ImageIO工具把图片文件读到BufferedImage中,完全按照已有的图片构建缓存图像。其实直接调用BufferedImage的构造方法,也能创建一个空的缓存图像对象,接着调用该对象的createGraphics方法,即可创建并获取新图像的二维画笔,然后使用二维画笔就能在缓存图像上作画了。譬如要旋转某个缓存图像,则利用二维画笔Graphics2D实现的方法代码定义如下:

	// 旋转图像。输入参数依次为:原图像、旋转角度
public static BufferedImage rotateImage(BufferedImage origin, int rotateDegree) {
int width = origin.getWidth(); // 获取原图像的宽度
int height = origin.getHeight(); // 获取原图像的高度
int imageType = origin.getType(); // 获取原图像的颜色类型
// 创建与原图像同样尺寸的新图像
BufferedImage newImage = new BufferedImage(width, height, imageType);
// 创建并获取新图像的画笔
Graphics2D graphics2d = newImage.createGraphics();
// 以原图像的中点为圆心,将画布按逆时针旋转若干角度
graphics2d.rotate(Math.toRadians(rotateDegree), width / 2, height / 2);
// 使用新图像的画笔绘制原图像,也就是把原图像画到新图像上
graphics2d.drawImage(origin, 0, 0, null);
return newImage; // 返回加工后的新图像
}

注意到上述代码调用BufferedImage的构造方法传入了三个参数,分别是新图像的宽度、高度和颜色类型。其中颜色类型常见的有两种:一种为BufferedImage.TYPE_4BYTE_ABGR,它表示四个字节的颜色模型,有三个字节分别表示蓝色、绿色和红色,还有一个字节表示透明度,这样总共有四个字节共32位,该类型等同于Windows平台上的32位真彩色;另一种颜色类型为BufferedImage.TYPE_3BYTE_BGR,它只有三个字节分别表示蓝色、绿色和红色,与TYPE_4BYTE_ABGR相比少了一个字节的透明度,这样加起来才24位,由于少了透明度信息,因此该类型接近于不透明的JPG图片格式。
接下来回到主界面的代码中,先在窗口上添加一个演示用的图像视图,并从本地图片构建一个原始的缓存图像,此时的控件初始化代码示例如下:

		ImageView imageView= new ImageView(); // 创建一个图像视图
// 把输入流中的图片数据读到缓存图像
BufferedImage origin = ImageIO.read(TestChange.class.getResourceAsStream("apple.png"));
// 设置图像视图的宽高
imageView.setSize(origin.getWidth(), origin.getHeight());
imageView.setImage(origin); // 设置图像视图的缓存图像
Panel panelCenter = new Panel(); // 创建中央面板
panelCenter.add(imageView); // 在中央面板上添加图像视图
frame.add(panelCenter, BorderLayout.CENTER); // 把中央面板添加到窗口的中间位置

然后在窗口上放置一个旋转按钮,单击该按钮时将命令图像往顺时针方向旋转90度,于是在按钮的单击事件中添加以下的旋转处理代码:

				// 将图像视图的尺寸设置为原图像的宽高
imageView.setSize(origin.getWidth(), origin.getHeight());
// 获得顺时针旋转90度后的新图像
BufferedImage newImage = ImageUtil.rotateImage(origin, 90);
imageView.setImage(newImage); // 设置图像视图的缓存图像

运行以上的主界面测试代码,在弹出的窗口界面中,单击旋转按钮前后的效果参见下列两图所示。


从两张效果图的对比可知,界面展示的图像成功旋转过来了。
实现图像的旋转功能之后,缩放图像、平移图像也可分别通过scale方法和translate方法来实现,相应的方法代码如下所示:

	// 缩放图像。输入参数依次为:原图像、缩放的比率
public static BufferedImage resizeImage(BufferedImage origin, double ratio) {
int width = origin.getWidth(); // 获取原图像的宽度
int height = origin.getHeight(); // 获取原图像的高度
int imageType = origin.getType(); // 获取原图像的颜色类型
// 创建尺寸大小为缩放宽高的新图像
BufferedImage newImage = new BufferedImage((int)(width*ratio), (int)(height*ratio), imageType);
// 创建并获取新图像的画笔
Graphics2D graphics2d = newImage.createGraphics();
graphics2d.scale(ratio, ratio); // 把画布的宽高分别缩放到指定比例
// 使用新图像的画笔绘制原图像,也就是把原图像画到新图像上
graphics2d.drawImage(origin, 0, 0, null);
return newImage; // 返回加工后的新图像
} // 平移图像。输入参数依次为:原图像、水平方向上的平移距离、垂直方向上的平移距离
public static BufferedImage translateImage(BufferedImage origin, int translateX, int translateY) {
int width = origin.getWidth(); // 获取原图像的宽度
int height = origin.getHeight(); // 获取原图像的高度
int imageType = origin.getType(); // 获取原图像的颜色类型
// 创建与原图像同样尺寸的新图像
BufferedImage newImage = new BufferedImage(width, height, imageType);
// 创建并获取新图像的画笔
Graphics2D graphics2d = newImage.createGraphics();
// 把画笔移动到指定的坐标点
graphics2d.translate(translateX, translateY);
// 使用新图像的画笔绘制原图像,也就是把原图像画到新图像上
graphics2d.drawImage(origin, 0, 0, null);
return newImage; // 返回加工后的新图像
}

缩放图像和平移图像的演示界面效果分别如下列两图所示。


除了旋转、缩放、平移这三种常见的图像变换操作,还有裁剪与翻转两种处理动作,其中参见用到了clipRect方法,而翻转用到了带十个参数的drawImage方法。下面是裁剪图像和翻转图像的方法定义:

	// 裁剪图像。输入参数依次为:原图像、裁剪的比率
public static BufferedImage clipImage(BufferedImage origin, double ratio) {
int width = origin.getWidth(); // 获取原图像的宽度
int height = origin.getHeight(); // 获取原图像的高度
int imageType = origin.getType(); // 获取原图像的颜色类型
// 创建尺寸大小为裁剪比例的新图像
BufferedImage newImage = new BufferedImage((int)(width*ratio), (int)(height*ratio), imageType);
// 创建并获取新图像的画笔
Graphics2D graphics2d = newImage.createGraphics();
// 把画笔的绘图范围裁剪到从左上角到右下角的指定区域,
// 其中左上角的坐标为(0,0),右下角的坐标为(width*ratio,height*ratio)
graphics2d.clipRect(0, 0, (int)(width*ratio), (int)(height*ratio));
// 使用新图像的画笔绘制原图像,也就是把原图像画到新图像上
graphics2d.drawImage(origin, 0, 0, null);
return newImage; // 返回加工后的新图像
} // 水平翻转图像。输入参数依次为:原图像
public static BufferedImage flipImage(BufferedImage origin) {
int width = origin.getWidth(); // 获取原图像的宽度
int height = origin.getHeight(); // 获取原图像的高度
int imageType = origin.getType(); // 获取原图像的颜色类型
// 创建与原图像同样尺寸的新图像
BufferedImage newImage = new BufferedImage(width, height, imageType);
// 创建并获取新图像的画笔
Graphics2D graphics2d = newImage.createGraphics();
// 使用新图像的画笔在目标位置绘制指定尺寸的原图像
// 其中目标区域的左上角坐标为(0,0),右下角坐标为(width,height)
// 对于水平翻转的情况,原图像的起始坐标为(width,0),终止坐标为(0,height)
graphics2d.drawImage(origin, 0, 0, width, height, width, 0, 0, height, null);
// 对于垂直翻转的情况,原图像的起始坐标为(0,height),终止坐标为(width,0)
//graphics2d.drawImage(origin, 0, 0, width, height, 0, height, width, 0, null);
return newImage; // 返回加工后的新图像
}

裁剪图像和翻转图像的演示界面效果分别如下列两图所示。


更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(一百二十五)AWT图像加工的更多相关文章

  1. Java开发笔记(二十五)方法的输入参数

    前面通过main方法介绍了方法的定义形式,对于方法的输入参数来说,还有几个值得注意的地方,接下来分别对输入参数的几种用法进行阐述.一个方法可以有输入参数,也可以没有输入参数,倘若无需输入参数,则方法定 ...

  2. Java开发笔记(六十五)集合:HashSet和TreeSet

    对于相同类型的一组数据,虽然Java已经提供了数组加以表达,但是数组的结构实在太简单了,第一它无法直接添加新元素,第二它只能按照线性排列,故而数组用于基本的操作倒还凑合,若要用于复杂的处理就无法胜任了 ...

  3. Java开发笔记(八十五)通过字符流读写文件

    前面介绍了文件的信息获取.管理操作,以及目录下的文件遍历,那么文件内部数据又是怎样读写的呢?这正是本文所要阐述的内容.File工具固然强大,但它并不能直接读写文件,而要借助于其它工具方能开展读写操作. ...

  4. Java开发笔记(二十四)方法的组成形式

    经过前面的学习,我们发现演示的Java代码越来越复杂,而且每个例子的代码都堆在入口方法main内部,这会导致如下问题:1.一个方法内部堆砌了太多的代码行,看着费神,维护起来也吃力:2.部分代码描述的是 ...

  5. Java开发笔记(二十六)方法的输出参数

    前面介绍了方法的输入参数,与输入参数相对应的则为输出参数,输出参数也被称作方法的返回值,意思是经过方法的处理最终得到的运算数值.这个返回值可能是整型数,也可能是双精度数,也可能是数组等其它类型,甚至允 ...

  6. Java开发笔记(二十八)布尔包装类型

    前面介绍了数值包装类型,因为不管是整数还是小数,它们的运算操作都是类似的,所以只要学会了Integer的用法,其它数值包装类型即可一并掌握.但是对于布尔类型boolean来说,该类型定义的是“true ...

  7. Java开发笔记(二十九)大整数BigInteger

    早期的编程语言为了节约计算机的内存,给数字变量定义了各种存储规格的数值类型,比如字节型byte只占用一个字节大小,短整型short占用两个字节大小,整型int占用四个字节大小,长整型long占用八个字 ...

  8. Java开发笔记(三十五)字符串格式化

    前面介绍了字符串变量的四种赋值方式,对于简单的赋值来说完全够用了,即便是两个字符串拼接,也只需通过加号把两个目标串连起来即可.但对于复杂的赋值来说就麻烦了,假设现在需要拼接一个很长的字符串,字符串内部 ...

  9. Java开发笔记(四十五)成员属性与成员方法

    前面介绍了许多数据类型,除了基本类型如整型int.双精度型double.布尔型boolean之外,还有高级一些的如包装整型Integer.字符串类型String.本地日期类型LocalDate等等,那 ...

  10. Java开发笔记(七十五)异常的处理:扔出与捕捉

    前面介绍的几种异常(不包含错误),编码的时候没认真看还发现不了,直到程序运行到特定的代码跑不下去了,程序员才会恍然大悟:原来这里的代码逻辑有问题.像这些在运行的时候才暴露出来的异常,又被称作“运行时异 ...

随机推荐

  1. go 学习 (五):包管理

    一.设置环境变量 二.启用 go modules 功能 并设置代理 https://goproxy.io/zh/ 补充: GO111MODULE  有三个值:on.off.auto GO111MODU ...

  2. python基础知识总结大全(转载)

  3. 是Mscoreei.dll的正确版本吗?

    在安装.NET 4.0或更高版本之后,您可能会注意到.NET进程有点不寻常.下面是用.NET 2.0编译器编译的简单“Hello World”可执行文件的加载模块的部分列表. 开始-结束模块名称 60 ...

  4. ssh:no matching host key type found. Their offer: ssh-dss

    最近突然ssh 服务连接出现 no matching host key type found. Their offer: ssh-dss 以前一直没有问题 可能的原因 openssh 服务升级,加密算 ...

  5. Coffee Break

    题目链接:Coffee Break  Gym-101911A 题目大意:有一位员工想要利用喝咖啡来休息,他给了一个数组表示他想要喝咖啡的时间点(假设他喝咖啡用时1分钟),老板规定每次喝咖啡的时间间隔必 ...

  6. Spark在美团的实践

    https://tech.meituan.com/2016/03/31/spark-in-meituan.html 本文已发表在<程序员>杂志2016年4月期. 前言 美团是数据驱动的互联 ...

  7. A. The Fair Nut and Elevator (Codeforces Round #526 (Div. 2))

    A. The Fair Nut and Elevator 好笨啊QAQ. 暴力枚举的题,连分类都不用. 从电梯初始位置到第一层.人到第一层.间隔的层数,往返路程. #include <bits/ ...

  8. GoCN每日新闻(2019-10-26)

    GoCN每日新闻(2019-10-26) 1. GateKeeper:滴滴开源的使用Go编写的不依赖分布式数据库的API网关 https://mp.weixin.qq.com/s/gpQSPJ-uRp ...

  9. 平安寿险Java面试-社招-四面(2019/08)

    个人情况 2017年毕业,普通本科,计算机科学与技术专业,毕业后在一个二三线小城市从事Java开发,2年Java开发经验.做过分布式开发,没有高并发的处理经验,平时做To G的项目居多.写下面经是希望 ...

  10. 「ZJOI2019」语言

    传送门 Description 给定一棵\(n\)个点的树和\(m\)条链,两个点可以联会当且仅当它们同在某一条链上,求可以联会的点的方案数 \(n,m\leq10^5\) Solution  考虑计 ...