Java使用Apache?PDFBox進(jìn)行PDF轉(zhuǎn)圖片體積過大的優(yōu)化指南
一、背景
在項(xiàng)目中,我們有一個(gè)接口用于將 PDF 按頁拆分為圖片,方便前端逐頁展示。
原始實(shí)現(xiàn)邏輯:
- 使用 Apache PDFBox 的
PDFRenderer - 調(diào)用
renderImageWithDPI - 使用
ImageIO.write(image, "PNG", file)輸出 PNG - 每一頁生成一張大圖,路徑存入數(shù)據(jù)庫
上線一段時(shí)間后發(fā)現(xiàn):
- 服務(wù)器磁盤增長很快
- 圖片加載明顯變慢
- 圖片接口內(nèi)存壓力升高
- 單頁圖片體積動(dòng)輒幾 MB
于是開始排查問題。
二、問題分析
造成圖片體積過大的核心原因有 3 個(gè):
DPI 過高
原始代碼:
renderer.renderImageWithDPI(i, 144);
144 DPI 會(huì)生成較高分辨率圖片,頁面尺寸較大。
DPI 越高,生成的圖片分辨率越大,文件體積呈指數(shù)級(jí)增長。
不做尺寸限制
直接按 PDF 原始尺寸渲染輸出。
例如:
- A4 頁面在 144 DPI 下
- 寬度可能接近 2000+ 像素
對(duì)于僅用于網(wǎng)頁展示來說完全沒有必要。
使用 PNG 無損格式
PNG 是無損壓縮,適合:
- 圖標(biāo)
- 透明圖片
- 插畫
但對(duì)于:
- 含大量文字 + 圖片的課件 PDF
- 整頁截圖型內(nèi)容
PNG 文件體積會(huì)非常大。
總結(jié)一句話
高 DPI + 原始大尺寸 + PNG 無損 = 單頁圖片非常大
三、優(yōu)化目標(biāo)
在保證肉眼清晰可讀的前提下:
- 降低圖片體積
- 降低磁盤增長速度
- 減少網(wǎng)絡(luò)傳輸時(shí)間
- 降低內(nèi)存壓力
并且:
- 不改接口簽名
- 不改 Controller
- 只在工具類內(nèi)部優(yōu)化
四、整體優(yōu)化方案
優(yōu)化策略:
- 降低 DPI
- 限制最大寬度,等比例縮放
- 保持 PNG(兼容現(xiàn)有業(yè)務(wù))
五、最終實(shí)現(xiàn)代碼(核心優(yōu)化版)
public class PdfToImageUtil {
/**
* 將 PDF 逐頁轉(zhuǎn)為壓縮后的 PNG 圖片
*/
public static List<BizPptImage> convertPdfToImages(File pdfFile,
String outputDir,
String courseDetailId) throws IOException {
List<BizPptImage> list = new ArrayList<>();
try (PDDocument doc = PDDocument.load(pdfFile)) {
PDFRenderer renderer = new PDFRenderer(doc);
int pageCount = doc.getNumberOfPages();
for (int i = 0; i < pageCount; i++) {
// ① 降低 DPI(原 144 → 96)
BufferedImage sourceImage = renderer.renderImageWithDPI(i, 96);
// ② 限制最大寬度,等比例縮放
BufferedImage scaledImage = scaleIfNecessary(sourceImage, 1280);
String fileName = UUID.randomUUID() + "_" + (i + 1) + ".png";
File outFile = new File(outputDir, fileName);
ImageIO.write(scaledImage, "PNG", outFile);
BizPptImage vo = new BizPptImage();
vo.setImagePath(outFile.getAbsolutePath());
vo.setFileName(fileName);
vo.setCourseDetailId(courseDetailId);
list.add(vo);
}
}
return list;
}
/**
* 按最大寬度等比例縮放
*/
private static BufferedImage scaleIfNecessary(BufferedImage source, int maxWidth) {
int width = source.getWidth();
int height = source.getHeight();
if (width <= maxWidth) {
return source;
}
double scale = (double) maxWidth / width;
int targetWidth = maxWidth;
int targetHeight = (int) Math.round(height * scale);
BufferedImage target = new BufferedImage(
targetWidth,
targetHeight,
BufferedImage.TYPE_INT_RGB
);
Graphics2D g2d = target.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(source, 0, 0, targetWidth, targetHeight, null);
g2d.dispose();
return target;
}
}
六、參數(shù)調(diào)優(yōu)建議
平衡清晰度 + 體積(推薦)
- DPI = 96
- maxWidth = 1280
適合 PC 展示場(chǎng)景。
清晰度優(yōu)先
- DPI = 120
- maxWidth = 1600 ~ 1920
適合高分屏場(chǎng)景。
體積優(yōu)先(縮略圖/預(yù)覽)
- DPI = 72
- maxWidth = 800 ~ 1024
圖片體積可以大幅降低。
七、優(yōu)化順序建議
建議按以下步驟逐步調(diào)優(yōu):
- 固定 DPI(例如 96)
- 調(diào)整 maxWidth
- 觀察清晰度和體積
- 如仍然過大,再降低 DPI
八、如果體積仍然偏大
可以考慮:
改為 JPEG
JPEG 是有損壓縮,體積會(huì)顯著下降。
缺點(diǎn):
- 需要自定義
ImageWriter - 需要設(shè)置壓縮質(zhì)量參數(shù)
- 對(duì)文字邊緣可能略微模糊
如果對(duì)清晰度要求不極端,可以考慮此方案。
九、優(yōu)化效果驗(yàn)證方式
同一份 PDF:
- 用老代碼生成圖片
- 用新代碼生成圖片
對(duì)比:
ls -lh
du -sh 目錄名
重點(diǎn)觀察:
- 單頁圖片大小
- 總目錄大小
- 前端加載速度
- 服務(wù)器磁盤增長趨勢(shì)
十、最終效果
在本項(xiàng)目中:
原方案:
- DPI = 144
- 不縮放
- 單頁 2~5MB
優(yōu)化后:
- DPI = 96
- maxWidth = 1280
結(jié)果:
- 單頁體積顯著下降
- 總目錄大小大幅縮減
- 前端加載速度明顯提升
- 磁盤增長速度趨緩
并且:
- 接口簽名未變
- Controller 無感知
- 業(yè)務(wù)層零改動(dòng)
十一、經(jīng)驗(yàn)總結(jié)
PDF 轉(zhuǎn)圖片時(shí),一定要考慮:
- DPI
- 輸出尺寸
- 圖片格式
默認(rèn)配置往往偏“高清優(yōu)先”,不適合大規(guī)模線上使用。
一句話總結(jié):PDF 轉(zhuǎn)圖片不優(yōu)化,遲早磁盤爆炸。
到此這篇關(guān)于Java使用Apache PDFBox進(jìn)行PDF轉(zhuǎn)圖片體積過大的優(yōu)化指南的文章就介紹到這了,更多相關(guān)Java解決PDF轉(zhuǎn)圖片體積過大內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Idea查看源代碼警告Library source does not mat
在使用IDEA開發(fā)時(shí),遇到第三方j(luò)ar包中的源代碼和字節(jié)碼不一致的問題,會(huì)導(dǎo)致無法正確打斷點(diǎn)進(jìn)行調(diào)試,這通常是因?yàn)閖ar包更新后源代碼沒有同步更新造成的,解決方法是刪除舊的jar包,通過Maven重新下載或手動(dòng)下載最新的源代碼包,確保IDE中的源碼與字節(jié)碼版本一致2024-10-10
spring基礎(chǔ)概念A(yù)OP與動(dòng)態(tài)代理理解
這篇文章主要為大家詳細(xì)介紹了spring基礎(chǔ)概念A(yù)OP與動(dòng)態(tài)代理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Java發(fā)送帶html標(biāo)簽內(nèi)容的郵件實(shí)例代碼
下面小編就為大家?guī)硪黄狫ava發(fā)送帶html標(biāo)簽內(nèi)容的郵件實(shí)例代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11
spring boot2.0圖片上傳至本地或服務(wù)器并配置虛擬路徑的方法
最近寫了關(guān)于圖片上傳至本地文件夾或服務(wù)器,上傳路徑到數(shù)據(jù)庫,并在上傳時(shí)預(yù)覽圖片。本文通過實(shí)例代碼給大家分享spring boot2.0圖片上傳至本地或服務(wù)器并配置虛擬路徑的方法,需要的朋友參考下2018-12-12
jmeter中json提取器如何提取多個(gè)參數(shù)值
關(guān)于jmeter中的正則表達(dá)式及json提取器可以提取響應(yīng)值,但是實(shí)際可以需要上個(gè)接口的多個(gè)響應(yīng)值,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解一下2021-11-11
StateMachine 狀態(tài)機(jī)機(jī)制深入解析
這篇文章主要介紹了,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08

