Java开发笔记(一百二十五)AWT图像加工
前面介绍了如何使用画笔工具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图像加工的更多相关文章
- Java开发笔记(二十五)方法的输入参数
前面通过main方法介绍了方法的定义形式,对于方法的输入参数来说,还有几个值得注意的地方,接下来分别对输入参数的几种用法进行阐述.一个方法可以有输入参数,也可以没有输入参数,倘若无需输入参数,则方法定 ...
- Java开发笔记(六十五)集合:HashSet和TreeSet
对于相同类型的一组数据,虽然Java已经提供了数组加以表达,但是数组的结构实在太简单了,第一它无法直接添加新元素,第二它只能按照线性排列,故而数组用于基本的操作倒还凑合,若要用于复杂的处理就无法胜任了 ...
- Java开发笔记(八十五)通过字符流读写文件
前面介绍了文件的信息获取.管理操作,以及目录下的文件遍历,那么文件内部数据又是怎样读写的呢?这正是本文所要阐述的内容.File工具固然强大,但它并不能直接读写文件,而要借助于其它工具方能开展读写操作. ...
- Java开发笔记(二十四)方法的组成形式
经过前面的学习,我们发现演示的Java代码越来越复杂,而且每个例子的代码都堆在入口方法main内部,这会导致如下问题:1.一个方法内部堆砌了太多的代码行,看着费神,维护起来也吃力:2.部分代码描述的是 ...
- Java开发笔记(二十六)方法的输出参数
前面介绍了方法的输入参数,与输入参数相对应的则为输出参数,输出参数也被称作方法的返回值,意思是经过方法的处理最终得到的运算数值.这个返回值可能是整型数,也可能是双精度数,也可能是数组等其它类型,甚至允 ...
- Java开发笔记(二十八)布尔包装类型
前面介绍了数值包装类型,因为不管是整数还是小数,它们的运算操作都是类似的,所以只要学会了Integer的用法,其它数值包装类型即可一并掌握.但是对于布尔类型boolean来说,该类型定义的是“true ...
- Java开发笔记(二十九)大整数BigInteger
早期的编程语言为了节约计算机的内存,给数字变量定义了各种存储规格的数值类型,比如字节型byte只占用一个字节大小,短整型short占用两个字节大小,整型int占用四个字节大小,长整型long占用八个字 ...
- Java开发笔记(三十五)字符串格式化
前面介绍了字符串变量的四种赋值方式,对于简单的赋值来说完全够用了,即便是两个字符串拼接,也只需通过加号把两个目标串连起来即可.但对于复杂的赋值来说就麻烦了,假设现在需要拼接一个很长的字符串,字符串内部 ...
- Java开发笔记(四十五)成员属性与成员方法
前面介绍了许多数据类型,除了基本类型如整型int.双精度型double.布尔型boolean之外,还有高级一些的如包装整型Integer.字符串类型String.本地日期类型LocalDate等等,那 ...
- Java开发笔记(七十五)异常的处理:扔出与捕捉
前面介绍的几种异常(不包含错误),编码的时候没认真看还发现不了,直到程序运行到特定的代码跑不下去了,程序员才会恍然大悟:原来这里的代码逻辑有问题.像这些在运行的时候才暴露出来的异常,又被称作“运行时异 ...
随机推荐
- C# 基础回顾: volatile 关键字
有些人可能从来没看到过这个关键字,这也难怪,因为这个关键字并不常用.那这个关键字到底有什么用呢? 我在网上搜索这个关键字的时候,发现很多朋友都有一个错误的认识 ------ 认为这个关键字可以防止并发 ...
- maven的目录
maven目录主要分为: src/main/java:项目主体源代码目录 src/main/resources:项目主体源代码所需资源目录 src/test/java:测试代码目录(测试代码不会被打包 ...
- C++ EH Exception(0xe06d7363)---捕获过程
书接上文<C++ EH Exception(0xe06d7363)----抛出过程>,下面我们讲下,VC++是如何catch到异常且处理的. 我们知道,在VC++里,C++异常实现的底层机 ...
- 关于java项目跑着跑着就挂掉的问题
部署项目后,安装redis,从redis中获取数据,或一些数据库查询操作,服务器cpu和内存占用率突增.
- windowns server 2008 r2 AD桌面文件重定向设置
1.创建将要进行重定向的组(此处为chongdingxiangzu) 2.选择要重定向的用户,并将此用户加入到要重定向的组里 3.打开组策略管理,右击刚才用户所属的组织单位(OU)进行新建GPO(此处 ...
- 【loj3123】【CTS2019】重复
题目 给出一个长度为\(n\)的串\(s\),询问有多少个长度为\(m\)的串\(t\) 满足 \(t\) 的无限循环串存在一个长度为\(n\)且比\(s\)字典序严格小的子串 $ n , m \le ...
- Java串口通信--------基于RXTX (附带资源地址)
最近帮老师做了一个小项目,一个牧场公司想用传感器收集一些环境信息,记录到数据库里去,然后加以分析查看.这里面和传感器通信用到了串口通信,我也是接触了一下,把用到的东西分享出来. 准备工作: RXTX: ...
- Hadoop版本升级(2.7.6 => 3.1.2)
自己的主机上的Hadoop版本是2.7.6,是测试用的伪分布式Hadoop,在前段时间部署了Hive on Spark,但由于没有做好功课,导致了Hive无法正常启动,原因在于Hive 3.x版本不适 ...
- CS224n学习笔记(二)
Global Vectors for Word Representation (GloVe) GloVe 模型包含一个训练在单词-单词的共同出现次数上的加权的最小二乘模型. 什么是Co-occurre ...
- Java自己实现HTTP服务器来理解GET和POST区别
GET请求和POST请求有什么区别?GET请求的参数在URL的问号后面显示,而POST参数不在URL上:POST可以比GET请求更大的数据…一般的回答都是这样. 但是作为一个高端大气上档次的程序员 ...