前 言
一位亲戚,在事业单位工作很多年了,最近准备申请高级工程师的职称,但是审核材料的网站,上传的图片必须为 jpg 格式且大小不能超过 512kb ,于是找到我帮忙。
我一想,直接回复,很简单啊,用很多 app 或者截图保存的时候,改一下后缀就行了,我帮你搞定。
结果,对方直接发过来一个差不多 500M 的压缩包,把我人看傻眼了,不是只有一点点吗?怎么这么多?!
我解压后瞅了一眼,最外层1个压缩包,里面有31个压缩包,每个压缩包下,又有各自的多个文件夹或者图片,更多的是 pdf 文档。
亲戚告诉我,这是她收集了差不多两个月的所有材料了,需要全部搞成 jpg 图片且图片大小不能超过 512kb。
好家伙,我答应得太爽快了,这下掉大坑里了!
如果按上面说到这种做法,手动用 app 或者截图的方式去搞,得搞到猴年马月,怕是提交审核的时间都耽误了。
这要是放在我还没参加工作或者刚参加工作没多久那会儿,我估计就不要脸地拒绝回去了!不过现在信誓旦旦地答应了下来,总不能怂了,得想办法搞一搞。
有了这种想法,这时候,我就拿这东西,当作一个需求来处理了,转身进入开发者模式。
需求分析
作为开发者,拿到一个需求,第一步不是想着怎么写代码,而是做好一个需求分析,明确具体要求是什么,要达到什么样的目标,有哪些可以利用的资源等待。
我再三和亲戚确认,得到了以下较为清晰的信息:
- 要 jpg 图片
- 所有图片大小不超过 512 kb
- 只有这一个接近 500 M 的压缩包,没有别的了
- 交付时间最迟是 4 天后
- 如果是 pdf 文档就拆分成 jpg 图片,如果已经是图片的,就压缩图片大小,限制在 512 kb以下,但是不改变图片的尺寸大小。
综合所有信息,我发现我就需要做两个事:
- 一个是把所有 pdf 文件拆分成 jpg 图片
- 另外一个就是压缩图片大小
看到东西不复杂,我松了口气,不过也不算太轻松,因为这东西,量太多。
写代码处理文档,解决问题
总共两步走,第一步就是将所有 pdf 文档拆分成 jpg 图片;第二部就是将所有超过 512kb 的图片,进行压缩。
将pdf文档拆分成jpg图片
这里给出主要的是实现方法,由于涉及多个文件夹且多个 pdf 文档,所以在写测试方法时,是需要根据实际情况,加上 for 循环调用的。
/**
* 将PDF文档拆分成多张图片,并返回所有图片的路径
*
* @param pdfPath
* @param pictureFolderPath
* @return
* @throws Exception
*/
public static List<String> pdfSwitchToPicture(String pdfPath, String pictureFolderPath) throws Exception {
List<String> picUrlList = new ArrayList<>();
File file = new File(pictureFolderPath);
if (!file.exists()) {
file.mkdirs();
}
String pictureRootName = file.getName() + "_";
List<byte[]> imageList = handlePdf(pdfPath);
AtomicInteger pictureNameNumber = new AtomicInteger(1);
for (byte[] image : imageList) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(image);
String pictureUrl = file.getAbsolutePath() + File.separator + pictureRootName + pictureNameNumber.getAndIncrement() + ".jpg";
byteArrayOutputStream.writeTo(new FileOutputStream(pictureUrl));
picUrlList.add(pictureUrl);
byteArrayOutputStream.close();
}
return picUrlList;
}
/**
* 处理PDF文档
*
* @param pdfPath
* @return
* @throws Exception
*/
public static List<byte[]> handlePdf(String pdfPath) throws Exception {
File pdfFile = new File(pdfPath);
//加载PDF文档
PDDocument pdDocument = PDDocument.load(pdfFile);
//创建PDF渲染器
PDFRenderer pdfRenderer = new PDFRenderer(pdDocument);
int pageNum = pdDocument.getNumberOfPages();
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < pageNum; i++) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//将PDF的每一页渲染成一张图片,并放大1.5倍
BufferedImage image = pdfRenderer.renderImage(i, 1.5F);
ImageIO.write(image, "jpg", outputStream);
list.add(outputStream.toByteArray());
outputStream.close();
}
pdDocument.close();
return list;
}
通过这一步,就将所有的 pdf 文档拆分成图片了,接下来就是压缩。
压缩图片
有一部分图片是 pdf 文档拆分出来就超过 512kb 的,有一部分是直接用手机拍摄的,动不动就是好几 M 大小,但又有一些事符合要求的。
因此在处理时,需要加好判断条件,别搞错了。同样的,由于涉及多个文件夹,所以在测试方法中,一样需要增加 for 调用处理的方法。
下面是主要的压缩方法,其余的需要根据具体的业务场景,自己定制化开发。
/**
* 根据指定大小压缩图片
*
* @param imageBytes 原图片字节数组大小
* @param targetFileSize 要压缩的大小
* @param picturePath 图片绝对路径
*/
public static void compressPicForScale(byte[] imageBytes, long targetFileSize, String picturePath) {
long srcSize = imageBytes.length;
if (srcSize > targetFileSize * 1024) {
log.info("当前压缩的图片是:{}", picturePath);
File file = new File(picturePath);
long tempSize = srcSize;
double accuracy = getAccuracy(srcSize / 1024);
try {
while (tempSize > targetFileSize * 1024) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
FileOutputStream fileOutputStream = new FileOutputStream(file);
Thumbnails.of(inputStream)
.scale(accuracy)
.outputQuality(accuracy)
.toOutputStream(fileOutputStream);
tempSize = file.length();
}
} catch (Exception e) {
log.error("====压缩图片失败====", e);
}
}
}
总结
下班回来后,花了一个晚上的时间,帮她解决了这个事,压缩成一个 100 多 M 的包交付给她。
她收到并查看,然后露出了一个长辈对晚辈满意的微笑,然后没然后了,只有我在风中凌乱…
这个事,难度不大,就是想清楚逻辑,然后动手就行。
不过能这么快解决,我第一次觉得,写代码还是有点意思的,解决起这种重复性这么高的问题来,还挺香,点个运行,就不用管了,如果有点小问题就多运行两次调试一下 ,省时省事!