简介

Thumbnailator 是一个优秀的图片处理的Google开源Java类库。处理效果远比Java API的好。从API提供现有的图像文件和图像对象的类中简化了处理过程,两三行代码就能够从现有图片生成处理后的图片,且允许微调图片的生成方式,同时保持了需要写入的最低限度的代码量。还支持对一个目录的所有图片进行批量处理操作。

支持的处理操作:图片缩放、裁剪、水印添加、旋转、保持比例、格式转换等等。Thumbnailator至今仍在不断更新……

准备工作

依赖

<!-- Thumbnailator图片处理 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>

上传页面

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>图上上传处理</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>

<body>
<form enctype="multipart/form-data" method="post" id="upload" role="form">
<input type="file" name="file" id="file" accept="image/jpeg,image/jpg,image/png,image/gif">
<input type="button" id="but" value="上传图片">
</form>

</br></br>
<input type="text" style="border:none;width:500px" name="tbg" id="tbg">
</br></br>
<img src="" alt="" id="tbgShow">

</br></br>
<input type="text" style="border:none;width:500px" name="ntbg" id="ntbg">
</br></br>
<img src="" alt="" id="ntbgShow">

</br></br>
<input type="text" style="border:none;width:300px" name="ppt" id="ppt">
</br></br>
<img src="" alt="" id="pptShow">

</br></br>
<input type="text" style="border:none;width:300px" name="rt" id="rt">
</br></br>
<img src="" alt="" id="rtShow">

</br></br>
<input type="text" style="border:none;width:300px" name="cp" id="cp">
</br></br>
<img src="" alt="" id="cpShow">

</br></br>
<input type="text" style="border:none;width:300px" name="wm" id="wm">
</br></br>
<img src="" alt="" id="wmShow">

</br></br>
<input type="text" style="border:none;width:300px" name="ti" id="ti">
</br></br>
<img src="" alt="" id="tiShow">

</br></br>
<input type="text" style="border:none;width:300px" name="ci" id="ci">
</br></br>
<img src="" alt="" id="ciShow">

</br></br>
<input type="text" style="border:none;width:300px" name="os" id="os">
</br></br>
<img src="" alt="" id="osShow">

</br></br>
<input type="text" style="border:none;width:300px" name="bi" id="bi">
</br></br>
<img src="" alt="" id="biShow">

</br></br>
<input type="text" style="border:none;" name="avatar" id="avatar">
</br></br>
<img src="" alt="" id="avatarShow">

<script type="text/javascript">
$("#but").click(function () {
var data = new FormData($("#upload")[0]);
if ($("#file").val() != "" && $("#file")[0].files[0].size / 1000 < 1024 * 5) { // 限制图片大小
$.ajax({
async: false,
type: "POST",
url: "/process",
dataType: "json",
data: data,
fileElementId: "file",
cache: false, // 上传文件不需要缓存
contentType: false, // 不设置contentType值,已经声明属性enctype="multipart/form-data",所以这里设置为false。
processData: false, // data值是FormData对象,不需要对数据做处理
success: function (d) {
if (
d.hasOwnProperty("srcPath") || d.hasOwnProperty("tbgPath") ||
d.hasOwnProperty("pptPath") || d.hasOwnProperty("ntbgPath") ||
d.hasOwnProperty("rtPath") || d.hasOwnProperty("cpPath") ||
d.hasOwnProperty("wmPath") || d.hasOwnProperty("tiPath") ||
d.hasOwnProperty("ciPath") || d.hasOwnProperty("osPath") ||
d.hasOwnProperty("biPath")
) {
//图片显示
$("#avatar").attr("value", d.srcMsg);
$("#avatarShow").attr("src", d.srcPath);

$("#tbg").attr("value", d.tbgMsg);
$("#tbgShow").attr("src", d.tbgPath);

$("#ntbg").attr("value", d.ntbgMsg);
$("#ntbgShow").attr("src", d.ntbgPath);

$("#ppt").attr("value", d.pptMsg);
$("#pptShow").attr("src", d.pptPath);

$("#rt").attr("value", d.rtMsg);
$("#rtShow").attr("src", d.rtPath);

$("#cp").attr("value", d.cpMsg);
$("#cpShow").attr("src", d.cpPath);

$("#wm").attr("value", d.wmMsg);
$("#wmShow").attr("src", d.wmPath);

$("#ti").attr("value", d.tiMsg);
$("#tiShow").attr("src", d.tiPath);

$("#ci").attr("value", d.ciMsg);
$("#ciShow").attr("src", d.ciPath);

$("#os").attr("value", d.osMsg);
$("#osShow").attr("src", d.osPath);

$("#bi").attr("value", d.biMsg);
$("#biShow").attr("src", d.biPath);
} else {
alert("上传失败");
}
},
error: function (e) {
alert("上传异常");
}
});
}
}
)
</script>
</body>
</html>

文件上传工具类

/**
* @author Nicky
* @version 1.0
* @className FileUtils
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 文件上传工具类
* @date 2021/3/10 15:47
*/
public class FileUtils {

/**
* @param file 文件
* @param path 文件存放路径
* @param fileName 保存的文件名
* @return
*/
public static boolean upload(MultipartFile file, String path, String fileName) {

//确定上传的文件名
String realPath = path + "\\" + fileName;

File dest = new File(realPath);

//判断文件父目录是否存在
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdir();
}

try {
//保存文件
file.transferTo(dest);
return true;
} catch (IllegalStateException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}

请求接口

@RequestMapping("/process")
public String imagePro(MultipartFile file) throws IOException {
Map<String, Object> map = new HashMap<>();

String filename = null;
String url = "http://127.0.0.1:8080/img/";
String localPath = null;
String srcPath = null;
String suffixName = null;

String type = file.getContentType();
if (type.equals("image/jpeg") || type.equals("image/jpg") || type.equals("image/png") || type.equals("image/gif")) {
filename = file.getOriginalFilename();
suffixName = filename.substring(filename.lastIndexOf("."));
String uuidName = UUID.randomUUID().toString().replace("-", "") + suffixName;
localPath = this.getClass().getResource("/").getPath() + "static/img/";
srcPath = localPath + uuidName;

if (FileUtils.upload(file, localPath, uuidName)) {
map.put("srcPath", url + uuidName);
map.put("srcMsg", "原图大小为:" + String.format("%.0f", file.getSize() / 1024f) + " KB");
}
}

thumbnailImg(map, 200, 300, url, localPath, srcPath, suffixName);
thumbnailImg2(map, 200, 300, url, localPath, srcPath, suffixName);
proportionImg(map, 0.5, url, localPath, srcPath, suffixName);
rotatingImg(map, 200, 300, 90, url, localPath, srcPath, suffixName);
compressionImg(map, 1, 0.5, url, localPath, srcPath, suffixName);
watermarkImg(map, 500, 500,"1.jpg",0.5f,0.8, url, localPath, srcPath, suffixName);
tailoringImg(map, 300, 300, url, localPath, srcPath, suffixName);
conversionImg(map, "gif", url, localPath, srcPath, suffixName);
outputStream(map, "gif", url, localPath, srcPath, suffixName);
bufferedImg(map, "bmp", url, localPath, srcPath, suffixName);
return JSON.toJSONString(map);
}

原图

文件大小:468 KB 分辨率:2560*1440像素
2022617.jpg

功能演示

按指定大小缩放图片(遵循原图高宽比例)

/**
* @method thumbnailImg
* @description 按指定大小缩放图片(遵循原图高宽比例)
* @param [map, wdith 宽度, heigth 高度, url 请求地址, localPath 本地项目地址, srcPath 原图片地址, suffixName 文件后缀名]
* @return void
*/
private void thumbnailImg(Map<String, Object> map, int wdith, int heigth, String url, String localPath, String srcPath, String suffixName) {
try {
String uuidName = UUID.randomUUID().toString().replace("-", "") + suffixName;
// of()可设置为图片目录地址,toFile()可设为图片转换后的目录地址,图片批量处理
Thumbnails.of(srcPath)
.size(wdith, heigth)
.toFile(localPath + uuidName);
File file = new File(localPath + uuidName);
BufferedImage bin = ImageIO.read(file);
map.put("tbgPath", url + uuidName);
map.put("tbgMsg", "遵循原图缩略大小为:" + String.format("%.0f", file.length() / 1024f) + " KB; " + "比例大小为:" + bin.getWidth() + "*" + bin.getHeight());
} catch (IOException e) {
e.printStackTrace();
}
}

按指定大小缩放图片(不遵循原图比例)

/**
* @method thumbnailImg2
* @description 按指定大小缩放图片(不遵循原图比例)
* @param [map, wdith, heigth, url, localPath, srcPath , suffixName]
* @return void
*/
private void thumbnailImg2(Map<String, Object> map, int wdith, int heigth, String url, String localPath, String srcPath, String suffixName) {
try {
String uuidName = UUID.randomUUID().toString().replace("-", "") + suffixName;
// keepAspectRatio值为false,默认为true
Thumbnails.of(srcPath)
.size(wdith, heigth)
.keepAspectRatio(false)
.toFile(localPath + uuidName);
File file = new File(localPath + uuidName);
// 图像缓存区类
BufferedImage bin = ImageIO.read(file);
map.put("ntbgPath", url + uuidName);
map.put("ntbgMsg", "不遵循原图缩略大小为:" + String.format("%.0f", file.length() / 1024f) + " KB; " + "比例大小为:" + bin.getWidth() + "*" + bin.getHeight());
} catch (IOException e) {
e.printStackTrace();
}
}

按比例率缩放图片

/**
* @method proportionImg
* @description 按比例率缩放图片
* @param [map, percentag 比例值, url, localPath, srcPath, suffixName]
* @return void
*/
private void proportionImg(Map<String, Object> map, double percentag, String url, String localPath, String srcPath, String suffixName) {
try {
String uuidName = UUID.randomUUID().toString().replace("-", "") + suffixName;
// scale 取值范围:大于1:放大,反之缩小;等于1:比例不变,压缩图片大小;等同于百分比)
Thumbnails.of(srcPath)
.scale(percentag)
.toFile(localPath + uuidName);
map.put("pptPath", url + uuidName);
map.put("pptMsg", "比例图大小为:" + String.format("%.0f", new File(localPath + uuidName).length() / 1024f) + " KB; " + "比例值为:" + percentag);
} catch (IOException e) {
e.printStackTrace();
}
}

旋转图片

/**
* @method rotatingImg
* @description 旋转图片
* @param [map, wdith, heigth, angle 角度, url, localPath, srcPath, suffixName]
* @return void
*/
private void rotatingImg(Map<String, Object> map, int wdith, int heigth, int angle, String url, String localPath, String srcPath, String suffixName) {
try {
String uuidName = UUID.randomUUID().toString().replace("-", "") + suffixName;
// rotate角度:正数顺时针旋转,反之亦然
Thumbnails.of(srcPath)
.size(wdith, heigth)
.rotate(angle)
.toFile(localPath + uuidName);
map.put("rtPath", url + uuidName);
map.put("rtMsg", "旋转角度为:" + angle);
} catch (IOException e) {
e.printStackTrace();
}
}

压缩图片文件大小

比例图大小为:72 KB; 比例值为:0.5 (压缩图与原图分辨率一致,此代码未做比例缩放处理)

/**
* @method compressionImg
* @description 压缩图片文件大小
* @param [map, percentag 比例值, compressValue 压缩值, url, localPath, srcPath , suffixName]
* @return void
*/
private void compressionImg(Map<String, Object> map, double percentag, double compressValue, String url, String localPath, String srcPath, String suffixName) {
try {
String uuidName = UUID.randomUUID().toString().replace("-", "") + suffixName;
// outputQuality 取值范围:0.0-1.0之间,等于1质量最高,等同于百分比,文件大小变大
Thumbnails.of(srcPath)
.scale(percentag)
.outputQuality(compressValue)
.toFile(localPath + uuidName);
map.put("cpPath", url + uuidName);
map.put("cpMsg", "压缩图大小为:" + String.format("%.0f", new File(localPath + uuidName).length() / 1024f) + " KB; " + "压缩值为:" + compressValue);
} catch (IOException e) {
e.printStackTrace();
}
}

添加水印

/**
* @method watermarkImg
* @description 添加水印
* @param [map, wdith, heigth, fileName 图片名称, transparency 透明度, compressValue 压缩值, url, localPath, srcPath, suffixName]
* @return void
*/
private void watermarkImg(Map<String, Object> map, int wdith, int heigth, String fileName, float transparency, double compressValue, String url, String localPath, String srcPath, String suffixName) {
try {
String uuidName = UUID.randomUUID().toString().replace("-", "") + suffixName;
// 读取水印图片
BufferedImage read = ImageIO.read(new File(localPath + fileName));
/*
* watermark(位置,水印图,透明度):Positions.BOTTOM_RIGHT表示在右下角,有9个位置枚举可选
* transparency 取值范围:0.0-1.0之间,1为不透明
*/
Thumbnails.of(srcPath)
.size(wdith, heigth)
.watermark(Positions.BOTTOM_RIGHT, read, transparency)
.outputQuality(compressValue).toFile(localPath + uuidName);
map.put("wmPath", url + uuidName);
map.put("wmMsg", "添加水印成功");
} catch (IOException e) {
e.printStackTrace();
}
}

图片裁剪

    /**
* @method tailoringImg
* @description 图片裁剪
* @param [map, wdith, heigth, url, localPath, srcPath, suffixName]
* @return void
*/
private void tailoringImg(Map<String, Object> map, int wdith, int heigth, String url, String localPath, String srcPath, String suffixName) {
try {
String uuidName = UUID.randomUUID().toString().replace("-", "") + suffixName;
/*
* sourceRegion(位置,裁剪宽度,裁剪高度)
* 位置:Positions.CENTER 表示在中间,有9个位置枚举可选,也可用两个像素值定位
*/
Thumbnails.of(srcPath)
// .sourceRegion(Positions.CENTER, wdith, heigth)
.sourceRegion(0,0, wdith, heigth)
.size(wdith, heigth)
.toFile(localPath + uuidName);
map.put("tiPath", url + uuidName);
map.put("tiMsg", "图片裁剪成功");
} catch (IOException e) {
e.printStackTrace();
}
}

图片格式转换

/**
* @method conversionImg
* @description 图片格式转换
* @param [map, format 图片格式, url, localPath, srcPath, suffixName]
* @return void
*/
private void conversionImg(Map<String, Object> map, String format, String url, String localPath, String srcPath, String suffixName) {
try {
String formatPath = srcPath.substring(0, srcPath.lastIndexOf(".")) + "." + format;
String fileName = srcPath.substring(srcPath.lastIndexOf("/") + 1, srcPath.lastIndexOf(".")) + "." + format;
// outputFormat: 支持bmp,jpg,png,gif,jpeg格式
Thumbnails.of(srcPath)
.scale(0.5f)
.outputFormat(format)
.toFile(formatPath);
map.put("ciPath", url + fileName);
map.put("ciMsg", "格式为:" + format + " 图片大小为:" + String.format("%.0f", new File(formatPath).length() / 1024f) + " KB; ");
} catch (IOException e) {
e.printStackTrace();
}
}

把图片输出至输出流

/**
* @method outputStream
* @description 把图片输出至输出流
* @param [map, format, url, localPath, srcPath, suffixName]
* @return void
*/
private void outputStream(Map<String, Object> map, String format, String url, String localPath, String srcPath, String suffixName) {
try {
String formatPath = srcPath.substring(0, srcPath.lastIndexOf(".")) + "." + format;
String fileName = srcPath.substring(srcPath.lastIndexOf("/") + 1, srcPath.lastIndexOf(".")) + "." + format;
OutputStream os = new FileOutputStream(formatPath);

Thumbnails.of(srcPath).size(300, 400).outputFormat(format).toOutputStream(os);
map.put("osPath", url + fileName);
map.put("osMsg", "输出文件流成功");
} catch (IOException e) {
e.printStackTrace();
}
}

输出图片缓冲流

/**
* @method bufferedImg
* @description 输出图片缓冲流
* @param [map, format, url, localPath, srcPath, suffixName]
* @return void
*/
private void bufferedImg(Map<String, Object> map, String format,String url, String localPath, String srcPath, String suffixName) {
try {
String formatPath = srcPath.substring(0, srcPath.lastIndexOf(".")) + "." + format;
String fileName = srcPath.substring(srcPath.lastIndexOf("/") + 1, srcPath.lastIndexOf(".")) + "." + format;
OutputStream os = new FileOutputStream(formatPath);

BufferedImage bi = Thumbnails.of(srcPath).size(300, 400).asBufferedImage();
ImageIO.write(bi, format, os);
map.put("biPath", url + fileName);
map.put("biMsg", "输出图片缓冲流成功");
} catch (IOException e) {
e.printStackTrace();
}
}

注意:若png、gif格式图片中含有透明背景,使用该工具压缩处理后背景会变成黑色,这是Thumbnailator的一个bug

源码地址:https://github.com/wangdaicong/spring-boot-project/tree/master/imageProcessing-demo