JAVA + Itextpdf + thumbnailator + 图片压缩
背景: 项目开发中,如果需要处理图片数据,那么压缩步骤必不可少。一方面压缩后存储空间降低,IO、内存等资源消耗也同步减少。在Java开发中,使用thumbnailator压缩图片是一个常规用法,本文仅记录PDF中图片数据压缩成字节数组的场景。
博客内容精选:
1、Servlet请求体重复读&修改新姿势
2、根据请求获取后端接口详情
3、封装Springboot项目的starter-sdk新方式
4、Springboot全局处理完整版
5、itextpdf读取文本时上下行位置错乱
6、JAVA读取PDF表格内容
7、JAVA读取PDF出现内容错乱
具体步骤:
1、通过Itextpdf工具读取PDF,获取图片对象
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
// 首先自定义读取监听器类,将图片数据保存下来
public class PdfContentReader implements RenderListener {
private final List<ImageRenderInfo> renderImages = new ArrayList<>();
@Override
public void beginTextBlock() {
}
@Override
public void renderText(TextRenderInfo textRenderInfo) {
}
@Override
public void endTextBlock() {
}
//注意:PDF图片对象为ImageRenderInfo,不是BufferedImage,但可进一步转换
@Override
public void renderImage(ImageRenderInfo imageRenderInfo) {
renderImages.add(imageRenderInfo);
}
}
// 入口类,通过PdfReader对象和监听器对象,将图片数据抽取出来
PdfReader reader = new PdfReader(localFile.getAbsolutePath());
int pageNum = reader.getNumberOfPages();
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
PdfContentReader listener = new PdfContentReader();
for (int page = 1; page <= pageNum; page++) {
parser.processContent(page, listener);
}
reader.close();
2、逐个压缩ImageRenderInfo图片数据
/**
* @param requestId 请求ID
* @param pageNum 页序号
* @param renderImage PDF图片对象
* @return 压缩后的字节数组
* @throws ExtractException 异常
*/
public static byte[] compressionRenderImage(String requestId, int pageNum, ImageRenderInfo renderImage) throws ExtractException {
PdfImageObject imageObject = null;
try {
// 此处返回图片对象,特殊PDF可能为null
imageObject = renderImage.getImage();
byte[] initData = imageObject.getImageAsBytes();
// 仅考虑2M以上图片压缩,根据自身业务场景设置
return initData.length > 2097152 ? compressionBufferImage(requestId, pageNum, initData.length, imageObject.getBufferedImage()) : initData;
} catch (Throwable e) {
log.warn("Request[{}] compress render image failed, type: {}, reason: {}", requestId, e.getClass().getSimpleName(), e.getMessage());
if (imageObject == null) {
throw new ExtractException("PDF图片获取图片对象为空");
}
throw new ExtractException("PDF图片压缩失败");
}
}
/**
* @param requestId 请求ID
* @param pageNum 页序号
* @param imageSize 图片大小
* @param bufferedImage 图片对象
* @return 压缩后的字节数组
* @throws ExtractException 异常
*/
public static byte[] compressionBufferImage(String requestId, int pageNum, int imageSize, BufferedImage bufferedImage) throws ExtractException {
int compressCounter = 0;
long startTime = System.currentTimeMillis();
// 使用内存字节数据中转,压缩次数尽量少点
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
int width = bufferedImage.getWidth() / 2;
int height = bufferedImage.getHeight() / 2;
do {
// 重复使用记得reset下流对象
out.reset();
// 此处outputFormat设置格式为jpg,如果设置png,字节数明显变大
Thumbnails.of(bufferedImage).scalingMode(ScalingMode.BICUBIC).size(width, height).outputQuality(QUALITY).outputFormat("jpg").toOutputStream(out);
width *= QUALITY;
height *= QUALITY;
compressCounter++;
} while (out.size() > 2097152);
float durationTime = (System.currentTimeMillis() - startTime) / 1000f;
// 涉及到重要操作,日志记录要详细点
log.warn("Request[{}] compress {}th pdf image from {}KB to {}KB after {} times and total cost {}s", requestId, pageNum, imageSize / 1024, out.size() / 1024, compressCounter, durationTime);
return out.toByteArray();
} catch (IOException e) {
throw new ExtractException("BufferedImage字节数据获取失败");
} finally {
try {
out.close();
} catch (IOException e) {
log.warn("close compression image byte stream failed");
}
}
}
}
结语:以上内容就是pdf中图片数据的压缩过程了,仅个人项目需求,不具有代表性。虽然简单,其实里面也有几个问题需要注意,如果处理不好,内存、异常问题都不会少。