这个世界没有什么好畏惧的,反正我们只来一次。
前言
主要实现了两个功能:
- 加水印
- 质量压缩
编写代码
1.编写工具类
ImageUtil代码如下:
package com.example.demo.util;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
/**
* 图片相关的工具类
* @author GMaya
* @dateTime 2022/3/29 14:21
*/
@Slf4j
public class ImageUtil {
/**
* 最小压缩质量参数
*/
private static final float QUALITY = 0.09f;
/**
* 指定压缩大小,单位为b字节 1kb = 1024b
*/
private static final float LIMIT_SIZE = 500 * 1024;
/**
* 质量压缩图片,循环压缩到指定大小
* @param bytes 原图片byte数组
* @return 压缩后图片byte数组
*/
public static byte[] compressPic(byte[] bytes) {
log.info("压缩前大小:{}{}", bytes.length / 1024, "kb");
// 如果图片小于指定压缩大小,则直接返回。
if (bytes.length < LIMIT_SIZE) {
return bytes;
}
TimeInterval timer = DateUtil.timer();
BufferedImage bufferedImage = null;
// 初始化质量参数
float q = 0.9f;
byte[] result = null;
try {
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
bufferedImage = ImageIO.read(in);
// 最小压缩到0.1质量,不管有没有到500kb
while (q > QUALITY) {
result = compressPic(bufferedImage, q);
log.info("图片压缩后大小:{}{},质量参数:{}", result.length / 1024, "kb", q);
if (result.length > LIMIT_SIZE) {
// 默认压缩到500kb,如果大于500kb,继续压缩
q -= 0.1f;
} else {
q = 0f;
}
}
//花费毫秒数
long interval = timer.interval();
log.info("图片压缩耗时:{}{}", interval, "ms");
return result;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 图片加水印
* @param pressText 水印文字
* @param targetImg 要加水印的图片路径
*/
public static void pressText(String pressText, String targetImg) {
try {
TimeInterval timer = DateUtil.timer();
BufferedImage src = ImageIO.read(new File(targetImg));
int width = src.getWidth(null);
int height = src.getHeight(null);
// 动态设置字体大小 , 以300像素15的基数大小设置
int fontSize = width < height ? width / 300 * 15 : height / 300 * 15;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.drawImage(src, 0, 0, width, height, null);
// 设置水印字体颜色. TODO 也可以变为动态参数传进来.
g.setColor(Color.RED);
// 设置水印字体,Font.PLAIN正常字体/BOLD加粗/ITALIC斜体. TODO 也可以变为动态参数传进来.
g.setFont(new Font("宋体", Font.PLAIN, fontSize));
// 透明度
// g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.9f));
/* 消除java.awt.Font字体的锯齿 */
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
/**
* TODO 如果是多行水印,以及超长水印,需要特殊处理.后期可升级.
* 思路:将水印分隔成数组pressTextList,循环数组
* int y = 1
* for(String text : pressTextList){
* g.drawString(text, 20, y * fontSize * 12 / 10);
* }
*/
// 开始加水印
g.drawString(pressText, 20, fontSize * 12 / 10);
// 输出图像
g.dispose();
File f = new File(targetImg);
ImageIO.write(image, targetImg.substring(targetImg.lastIndexOf(".") + 1), f);
//花费毫秒数
long interval = timer.interval();
log.info("图片加水印耗时:{}{}", interval, "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* @param image
* @param quality 参数qality是取值0~1范围内,压缩程度
* @return
* @throws IOException
*/
public static byte[] compressPic(BufferedImage image, float quality) throws IOException {
ByteArrayOutputStream outArray = new ByteArrayOutputStream();
// 指定写图片的方式为 jpg
ImageWriter imgWrier = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam imgWriteParams = new javax.imageio.plugins.jpeg.JPEGImageWriteParam(null);
// 要使用压缩,必须指定压缩方式为MODE_EXPLICIT
imgWriteParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// 设置压缩质量参数,参数qality是取值0~1范围内,
imgWriteParams.setCompressionQuality(quality);
imgWriteParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
ColorModel colorModel = image.getColorModel();
// 指定压缩时使用的色彩模式
imgWriteParams.setDestinationType(
new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(16, 16)));
try {
imgWrier.reset();
// 必须先指定 out值,才能调用write方法, ImageOutputStream可以通过任何
// OutputStream构造
imgWrier.setOutput(ImageIO.createImageOutputStream(outArray));
// 调用write方法,就可以向输入流写图片
imgWrier.write(null, new IIOImage(image, null, null), imgWriteParams);
} catch (Exception e) {
e.printStackTrace();
} finally {
imgWrier.dispose();
}
return outArray.toByteArray();
}
}
2.编写接口
ImageController代码如下:
package com.example.demo.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.demo.util.ImageUtil;
import com.example.demo.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
/**
* 用于图片处理的接口
* @author GMaya
* @dateTime 2022/3/29 14:13
*/
@Slf4j
@RestController
@RequestMapping("/image")
public class ImageController {
/**
* 压缩图片/图片加水印
* @param file 原图片文件
* @param jsonObject 传递的其他参数
* @return
* @throws IOException
*/
@PostMapping("/compressPic")
public R compressPic(MultipartFile file, String jsonObject) throws IOException {
if (file == null || file.isEmpty()) {
return R.error("文件不能为空,请检查!");
}
// 计时开始
TimeInterval timer = DateUtil.timer();
// json 转换
JSONObject json = JSONUtil.parseObj(jsonObject);
// 先将文件保存到本地
String targetImg = "D:\\work\\test-gmaya\\image\\" + IdUtil.simpleUUID() + ".jpg";
File destImageFile = new File(targetImg);
FileUtil.writeBytes(file.getBytes(), destImageFile);
// 是否需要加水印,默认为0是,1否
String isPressText = json.getStr("isPressText");
if ("0".equals(isPressText)) {
String pressText = json.getStr("pressText");
if(StrUtil.isEmpty(pressText)){
return R.error("水印文字不能为空,请检查!");
}
ImageUtil.pressText(pressText, targetImg);
}
// 是否需要压缩,默认为0是,1否
String isCompress = json.getStr("isCompress");
if ("0".equals(isCompress)) {
// 压缩并保存
byte[] bytes = ImageUtil.compressPic(file.getBytes());
FileUtil.writeBytes(bytes, destImageFile);
}
//花费毫秒数
long interval = timer.interval();
log.info("图片处理总耗时:{}{}", interval, "ms");
return R.data(targetImg);
}
}
3.测试接口
总结
问题:
选择图片过大,导致失败
The field file exceeds its maximum permitted size of 1048576 bytes.
解决办法:
spring:
servlet:
multipart:
max-file-size: 10MB # 单个上传文件大小限制10MB以下
max-request-size: 100MB #总上传文件大小限制100MB以下