文章目录
前言
在项目中,我们不可避免的会遇到对图片的处理,首先不考虑图片处理过程的细节,如何存储是个问题。如果图片很多的话,我们不可能把所有的图片都存储在数据库中,所以通常会在本机或者是服务器上单独创建一个目录来管理保存这些图片,在这个目录里,我们又会将其分为很多模块来存放不同类别的图片,而数据库里只需要保存图片的目录就可以了(为了便于项目的迁移,只保存相对路径)。
对于图片的处理,我使用了Thumbnailator这个类库。为了简化图片的处理,图片工具类自然是不可或缺的。接下来就来详细讲解图片处理的过程。
一、实现步骤
首先,以店铺缩略图的存储为例。每个店铺都会有图片,这个图片都保存在自己店铺的所属目录中。因为用户上传的图片路径大部分都不在同一目录,并且经常会重名,所以我们需要单独处理图片文件的路径,以及图片的重命名。
因此一个图片路径名应该是:项目存储图片的根目录/存储店铺缩略图的目录/店铺id/重命名后的图片名。
二、路径工具类(PathUtil.java)
思路:
- 设置图片存储的根目录
- 项目中使用的图片按照模块和功能,设置存储的相对路径。
- 图片的绝对路径为:根目录+子目录
为了更加方便的获取图片经过处理保存后的根目录和子目录以及其他目录,我们将这些方法写在PathUtil这个工具类里。
package com.yaya.o2o.util;
public class PathUtil {
private static String seperator = System.getProperty("file.separator");
//图片经过处理后的保存路径---根路径
public static String getImgBasePath() {
String os = System.getProperty("os.name");
String basePath = "";
if(os.toLowerCase().startsWith("win")) {
basePath = "D:/o2o/image";
} else {
basePath = "/home/duck/image";
}
return basePath.replace("/" , seperator);
}
//店铺图片经过处理后的保存路径---子路径
public static String getShopImagePath(long shopId) {
String imagePath = "/upload/item/shop/" + shopId + "/";
return imagePath.replace("/", seperator);
}
//个人信息头像经过处理后的保存路径---子路径
public static String getPersonInfoImagePath() {
String personInfoImagePath = "/upload/item/personinfo/";
return personInfoImagePath.replace("/", seperator);
}
}
考虑到项目将会迁移在不同的操作系统上,所以对不同操作系统上的目录分隔符做了替换处理。这里为了简单起见,我们直接将图片保存的根目录硬编码在代码中。
//获取当前操作系统的目录分隔符
private static String seperator = System.getProperty("file.separator");
在获取到目录后,用String的replace方法进行替换。这个类两个方法路径的结合得到了店铺图片应该存储的目录。但我们还不知道上传的图片的名字,并且没有给图片重命名来防止图片名称重复。下面通过ImageUtil来完成。
三、图片工具类(ImageUtil)
思路:
- 围绕添加水印输出目标图片这个功能展开
- 核心方法generateThumbnails,创建缩略图中3个关键参数 源图片 水印图 和 目标图片
- 围绕 源图片 水印图 和 目标图片,调用PathUtil中的方法拼接目标图片的存储路径以及目标图片的名称。
类似PathUtil工具类,对图片的处理我们也封装为一个类ImageUtil。
package com.yaya.o2o.util;
import com.yaya.o2o.dto.ImageHolder;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class ImageUtil {
private static String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
private static final Random r = new Random();
//生成缩略图
//参数:图片文件,保存路径
public static String generateThumbnail(ImageHolder thumbnail, String targetAddr) {
//1.目标目录,不存在创建,即/upload/item/shop/shopId
makeDirPath(targetAddr);
//2.为了防止图片重名,不采用用户上传的文件名,系统内部采用随机命名的方式
//随机文件名
String realFileName = getRandomFileName();
//3.获取用户上传的文件扩展名,用于拼接新的文件名
//文件扩展名
String extension = getFileExtension(thumbnail.getImageName());
//4.拼接新的文件名
//相对路径===目标路径+随机文件名+扩展名,即/upload/item/shop/shopId/****.***
String relativeAddr = targetAddr + realFileName + extension;
//目标文件===根路径+相对路径
File dest = new File(PathUtil.getImgBasePath() + relativeAddr);
//5.给源文件加水印后输出到目标文件
try {
Thumbnails.of(thumbnail.getImage()).scale(0.7f)//图片大小
//添加水印
.watermark(Positions.BOTTOM_RIGHT,//水印位置---右下
//水印路径,水印透明度
ImageIO.read(new File(basePath + "watermark.png")), 1.0f)
//输出到目标文件
.toFile(dest);
} catch (IOException e) {
e.printStackTrace();
}
//返回相对路径
return relativeAddr;
}
//生成随机文件名,当前年月日小时分钟秒+五位随机数
public static String getRandomFileName() {
int rn = r.nextInt(89999) + 10000; //10000-99999
String nowTimeStr = sDateFormat.format(new Date());
return nowTimeStr + rn;
}
//获取输入文件流的扩展名
public static String getFileExtension(String fileName) {
return fileName.substring(fileName.lastIndexOf("."));
}
//创建目录
public static void makeDirPath(String targetAddr) {
String realFileParentPath = PathUtil.getImgBasePath();
File dirPath = new File(realFileParentPath + targetAddr);
if(!dirPath.exists()) {
dirPath.mkdirs();
}
}
}
- 首先将水印图片储存在项目的resources目录下
那么在获取水印图片的路径的时候,同样将目录和水印图片名分开管理。
通过执行线程逆推到类加载器进而获取水印图片的绝对路径。private static String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
- 创建目录涉及到了文件类File,判断目录是否存在,不存在则创建。
- 以当前年月日小时分钟秒+五位随机数来作为新文件名,涉及到了日期时间格式化的SimpleDateFormat类和生成随机数的Random类。
- 用String的substring 方法截取下标为lastIndexOf(".")后的字符串。
- 拼接好了目标文件的绝对路径,开始进行图片处理。
四、用Thumbnailator处理图片
(1)简介
在博客开始我就提到了Thumbnailator这个类库。Thumbnailator是一个用来生成图像缩略图的Java类库,简化了缩略过程,两三行代码就能够从现有图片生成缩略图。支持根据一个目录批量生成缩略图,支持图片缩放,区域裁剪,水印,旋转,保持比例等等功能。
(2)主要功能
- 从原图创建高质量的缩略图
- 缩略图中嵌入水印(如logo)
- 嵌入的水印可以调整透明度从0%到100%
- 支持缩略图旋转
- 流畅的接口简化了生成缩略图的编程过程
- 多种缩略图质量模式选择
- 可以保存缩略图的长宽比
(3)在项目中使用
- 添加依赖
<dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.8</version> </dependency>
- 代码示例
Thumbnails.of() 这个方法可以传入一个文件也可以传入一个文件流。//图片路径 Thumbnails.of(thumbnail.getImage()) //图片大小 .scale(0.7f) //添加水印 .watermark(Positions.BOTTOM_RIGHT,//水印位置---右下 //水印路径,水印透明度 ImageIO.read(new File(basePath + "watermark.png")), 1.0f) //输出到目标文件 .toFile(dest);
scale() 方法是缩放的倍数。
watermark() 方法分别可以设置水印图的位置,水印图片的绝对路径,透明度。
toFile() 方法是目标文件路径。 - 效果展示
水印图:
添加水印图后的店铺缩略图:
(4)具体场景
以下参考自:https://rensanning.iteye.com/blog/1545708 侵删~
-
指定大小进行缩放
//size(宽度, 高度) /* * 若图片横比200小,高比300小,不变 * 若图片横比200小,高比300大,高缩小到300,图片比例不变 * 若图片横比200大,高比300小,横缩小到200,图片比例不变 * 若图片横比200大,高比300大,图片按比例缩小,横为200或高为300 */ Thumbnails.of("image.jpg") .size(200, 300) .toFile("newImage.jpg");
-
按照比例进行缩放
//scale(比例) Thumbnails.of("image.jpg") .scale(1.10f) .toFile("newImage.jpg");
-
旋转
//rotate(角度) 正数:顺时针 负数:逆时针 Thumbnails.of("image.jpg") .size(1280,1024) .rotate(90) .toFile("newImage1.jpg"); Thumbnails.of("image.jpg") .size(1280,1024) .rotate(-90) .toFile("newImage2.jpg");
-
水印
//watermark(位置,水印图,透明度) Thumbnails.of("image.jpg") .size(1280,1024) .watermark(Positions.CENTER,ImageIO.read(newFile("watermark.png")),0.5f) .outputQuality(0.8f) .toFile("newImage.jpg");
压缩图片文件大小outputQuality实现,参数1为最高质量
-
裁剪
//sourceRegion() //图片中心400*400的区域 Thumbnails.of("image.jpg") .sourceRegion(Positions.CENTER,400,400) .size(200,200) .keepAspectRatio(false) .toFile("newImage1.jpg"); //图片右下400*400的区域 Thumbnails.of("image.jpg") .sourceRegion(Positions.BOTTOM_RIGHT,400,400) .size(200,200) .keepAspectRatio(false) .toFile("newImage2.jpg"); //指定坐标 Thumbnails.of("image.jpg") .sourceRegion(600,500,400,400) .size(200,200) .keepAspectRatio(false) .toFile("newImage3.jpg");
-
转化图像格式
//outputFormat(图像格式) Thumbnails.of("image.jpg") .size(1280,1024) .outputFormat("png") .toFile("newImage1.png"); Thumbnails.of("image.jpg") .size(1280,1024) .outputFormat("gif") .toFile("newImage2.gif");
-
输出到OutputStream
//toOutputStream(流对象) OutputStreamos = newFileOutputStream("newImageOutputStream.png"); Thumbnails.of("image.jpg") .size(1280,1024) .toOutputStream(os);
-
输出到BufferedImage
//asBufferedImage()返回BufferedImage BufferedImage thumbnail = Thumbnails.of("image.jpg") .size(1280,1024) .asBufferedImage(); ImageIO.write(thumbnail,"jpg",newFile("newImageBufferedImage.jpg"));
更多方法使用详情可查看API:https://coobird.github.io/thumbnailator/javadoc/0.4.8/