ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java用iText实现高清图片转PDF:像素、DPI与批量处理详解

Java用iText实现高清图片转PDF:像素、DPI与批量处理详解 简介面向Java开发者的iText图片转PDF高清版demo解决将PNG等图片高质量合成为PDF文档的常见需求。资源包共11个文件以iText.jar及依赖jar、演示用Java源码、项目配置文件(.project/.classpath)、JSP页面和readme说明文档为主整体仅1022KB轻量易用。已有5465人学习下载适合需要在项目中快速实现图片导PDF功能的中初级工程师。demo采用无坑化步骤完整展示Document、PdfWriter、Image等核心API的调用流程重点讲解如何通过image.setDpi(300,300)设置高DPI并选用SCALE_FIT_WIDTH、SCALE_FIT_HEIGHT等缩放方式保持图片清晰度还附带readme记录运行环境和排错提示可直接导入MyEclipse运行验证。 最近整理一批1500万像素的现场照片和设计稿客户要求合成一个PDF打印出来不能糊文件又不能大得离谱。之前我用看图软件直接打印凑合能用但图片一多、尺寸一杂就出问题——有的被自动缩放错位有的输出后肉眼可见的模糊。这次干脆换了个思路用iText在Java里自己写图片生成PDF的小工具顺便把“高清”这件事彻底捋清楚了。如果你也在批量做扫描件归档、设计稿交付、电子书或者产品图册这篇文章应该能帮你少走不少弯路。1. 为什么用iText做图片转PDF1.1 主流Java PDF图片生成方案对比先说选型。Java生态里做PDF生成的库不少iText、OpenPDF、PDFBox、Apache FOP各有各的适用场景我对比了一圈方案上手难度图片支持性能适合场景iText 5.x中等直接支持JPEG/PNG/GIF/TIFF高动态生成、批量处理、图文混排iText 7.x偏高更强支持更多图像类型高复杂文档、样式引擎、合规场景OpenPDF低基于iText 5开源分支支持常见格式中轻量需求、避免商业授权PDFBox中支持但API更底层中PDF解析、表单处理、底层操作Apache FOP高图片只是插图重点在XSL-FO低样式严格的XML转PDF如果需求就是“把图片变成PDF保持高清能批量处理”iText 5和iText 7都是好选择。我用5.x比较多因为存量项目都是这个版本社区教程多遇到问题好查。如果你是新项目且预算充足可以考虑iText 7如果是学习或内部工具OpenPDF和iText 5也够用。1.2 “高清版”到底指什么像素、DPI与PDF页面尺寸很多人以为高清就是“原图多大PDF就多大”这个理解只对了一半。PDF里图片是否清晰取决于三件事原图像素够不够。一张1200x800的图片不管你怎么放大打印出来都糊。PDF本身不会给图片增加细节它只是把像素数据原样嵌进去。PDF页面尺寸和图片显示尺寸是否匹配。PDF的页面尺寸单位是“点”Point1点1/72英寸。图片嵌入PDF时阅读器需要知道“这张图在页面上显示多大”。如果显示尺寸大于图片实际像素应该对应的物理尺寸放大看就会糊。图像的像素密度是否足够。印刷行业一般要求300 DPI每英寸300个像素点。一张3000像素宽的图片如果显示在10英寸宽的区域是300 DPI清晰如果被拉伸到20英寸宽就变成150 DPI打印出来明显发虚。所以“高清版”的正确姿势是保证原图分辨率足够同时把PDF页面尺寸设置成和图片的物理尺寸匹配。这里有一个换算公式像素宽度 ÷ DPI × 72 PDF页面宽度点比如一张3000x2000的图希望以300 DPI输出页面尺寸应该是3000÷300×72720点宽2000÷300×72480点高。iText其实可以自动帮我们算只要设置了图片的DPI信息。2. 高清图片转PDF的核心细节2.1 选对iText版本5.x与7.x怎么选iText 5.x的Maven坐标是dependency groupIdcom.itextpdf/groupId artifactIditextpdf/artifactId version5.5.13.3/version /dependencyiText 7.x则需要分模块引入dependency groupIdcom.itextpdf/groupId artifactIditext7-core/artifactId version7.2.5/version typepom/type /dependency两个版本的API差异很大。5.x用com.itextpdf.text.Image和Document7.x用com.itextpdf.layout.element.Image和PdfDocument。如果你是老项目用5.x最省心新建项目建议直接上7.x因为iText 5进入了维护期7.x是主线。但无论哪个版本核心逻辑是一样的创建图片对象、设置显示尺寸、设置页面大小、写入文档。这篇文章的示例代码以iText 5为主因为它的API对“图片转PDF”这种场景更直白最后我会补一段7.x的对应写法。2.2 页面尺寸与图片适配方案图片转PDF有两种典型场景对应的页面尺寸策略完全不同方案A原图尺寸页面适合单张高清图。页面大小完全跟随图片尺寸图片占满整页不做任何缩放。这是“高清版”最直接的做法适合扫描件、设计稿、摄影作品。方案B固定规格页面适合批量文档。比如统一用A4纸规格图片等比缩放适应页面。这种情况下要保证缩放算法不破坏清晰度同时不能变形。适合合同扫描、资料归档这类需要统一打印尺寸的场景。实际项目中我经常两种情况混着用。比如一批产品图单张高清图用方案A缩略图用方案B由文件名或目录结构区分。2.3 图片加载时机与内存管理这是最容易被忽视的细节。高清图片动辄几十MB像素数据如果用循环把所有图片先加载成Image对象再统一写入PDF很容易OutOfMemory。正确做法是逐张处理每加载一张图立即设置尺寸、添加进文档然后释放引用再处理下一张。这样任意时刻内存里最多只有一张图的完整数据。另外JPEG图片在iText里默认是“半懒加载”的图片对象创建时不一定立刻把全部像素解码进内存但PNG这类无损格式没有这个便利加载时就会占不少内存。所以大批量PNG转PDF时要格外注意堆内存设置或者考虑先转成高质量的JPEG再嵌入。3. 实操单张与批量图片生成高清PDF3.1 单张图片生成“原图尺寸”高清PDF直接上代码这是最核心的“原图尺寸”方案import com.itextpdf.text.Document; import com.itextpdf.text.Image; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfWriter; import java.io.FileOutputStream; public class ImageToPdf { public static void main(String[] args) throws Exception { String srcImagePath D:/photos/product_001.jpg; String targetPdfPath D:/pdf/product_001.pdf; // 1. 创建文档注意四个边距全部设为0否则图片四周会出现白边 Document document new Document(); PdfWriter.getInstance(document, new FileOutputStream(targetPdfPath)); document.open(); // 2. 创建图片对象 Image image Image.getInstance(srcImagePath); // 3. 关键设置图片DPIiText会根据DPI自动换算图片在PDF中的显示尺寸 // 这里以300 DPI为例印刷级清晰度 image.setDpi(300, 300); // 4. 获取按DPI换算后的显示宽度和高度单位是点Point float scaledWidth image.getScaledWidth(); float scaledHeight image.getScaledHeight(); // 5. 设置PDF页面大小和图片显示尺寸一致 Rectangle pageSize new Rectangle(scaledWidth, scaledHeight); document.setPageSize(pageSize); document.newPage(); // 6. 图片放在页面原点即左上角位置 image.setAbsolutePosition(0, 0); document.add(image); document.close(); System.out.println(PDF生成完毕页面尺寸 scaledWidth x scaledHeight pt); } }这个方案的核心逻辑就两行image.setDpi(300, 300)和document.setPageSize(new Rectangle(scaledWidth, scaledHeight))。setDpi告诉iText图片的物理密度iText内部会自动完成“像素→点”的换算setPageSize保证页面和图片一样大打印时不会有任何缩放。如果不想用DPI换算也可以直接用原始像素尺寸当页面大小new Rectangle(image.getWidth(), image.getHeight())这样生成的PDF每个像素对应1个点。但我更推荐先写Dpi因为PDF阅读器能识别DPI信息缩放比例更合理。3.2 批量扫描件合并成一个多页PDF批量场景比单张稍微复杂一点核心是多页循环和页面尺寸切换import com.itextpdf.text.Document; import com.itextpdf.text.Image; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfWriter; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; public class BatchImageToPdf { public static void main(String[] args) throws Exception { String[] imagePaths { D:/photos/scan_001.jpg, D:/photos/scan_002.jpg, D:/photos/scan_003.jpg }; String targetPdfPath D:/pdf/all_pages.pdf; Document document new Document(); PdfWriter.getInstance(document, new FileOutputStream(targetPdfPath)); document.open(); for (String filePath : imagePaths) { Image image Image.getInstance(filePath); // 统一按300 DPI输出保证打印清晰 image.setDpi(300, 300); float width image.getScaledWidth(); float height image.getScaledHeight(); // 每页尺寸跟随当前图片这样不同尺寸的图都能不被裁剪地展示 document.setPageSize(new Rectangle(width, height)); document.newPage(); image.setAbsolutePosition(0, 0); document.add(image); System.out.println(已写入 filePath 尺寸 width x height pt); // 释放引用交给GC image null; } document.close(); System.out.println(全部完成PDF路径 targetPdfPath); } }批量的关键是“循环内翻转页面尺寸”。因为每张图片大小可能不同如果只设置一次页面大小第二张图如果比第一张大就会被截断比第一张小页面又会有大片空白。每轮循环重新setPageSize相当于每页都自定义规格这也是iText做扫描件合并且保持高清的标准做法。如果固定要求A4规格那么循环里就不能用setPageSize跟随图片而是固定PageSize.A4图片用scaleToFit等比缩放Document document new Document(PageSize.A4, 0, 0, 0, 0); // 循环内 Image image Image.getInstance(filePath); image.setDpi(300, 300); // 等比缩放保证图片完整显示不变形 image.scaleToFit(PageSize.A4.getWidth(), PageSize.A4.getHeight()); document.add(image); document.newPage();注意scaleToFit内部会根据目标宽高计算缩放比例只缩不放不会把图片强行拉伸超过原始尺寸比手动算scaleAbsolute要安全。3.3 保持高清的几个额外细节细节一JPEG直接嵌入不要二次压缩。iText对JPEG图片有特殊优化它会把JPEG数据以DCTDecode的形式原样嵌入PDF不经过重新编码所以画质不会因为生成PDF而损失。但如果你用ImageIO先把图片读成BufferedImage再转字节数组嵌入就会经历一次解码和重新编码质量会变化文件也可能变大。所以原图如果是JPEG尽量直接用Image.getInstance(路径)加载。原图如果是BMP这类不常见格式建议先转成高质量JPEG再处理。细节二PNG透明背景问题。PNG带透明通道直接加进PDF某些PDF阅读器会显示成黑色。处理方式是在图片后面垫一个白色色块或者转成JPEG。我一般优先转成白色背景的JPEG简单且兼容性好。细节三手机拍摄图片的EXIF方向。手机竖屏拍的照片JPEG里带有EXIF方向信息直接嵌入PDF可能显示为“躺着”的。iText 5不会自动读取EXIF需要先用第三方库如metadata-extractor读取Orientation字段然后调用image.setRotationDegrees(90)修正方向。// 伪代码示意 int orientation readExifOrientation(srcImagePath); Image image Image.getInstance(srcImagePath); if (orientation 6) { image.setRotationDegrees(90); } else if (orientation 8) { image.setRotationDegrees(270); }细节四页面边距一定要设为0。默认的Document四边有大约36pt的边距图片加上去之后会被强行缩到内容区域导致图片四周出现白边打印的时候像照片外面套了个框。所以我建议所有图片转PDF场景下构造函数都显式传0边距。3.4 iText 7版本对应写法如果你用的是iText 7上面的代码对应如下import com.itextpdf.io.image.ImageDataFactory; import com.itextpdf.kernel.geom.PageSize; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Image; public class ImageToPdf7 { public static void main(String[] args) throws Exception { String srcImagePath D:/photos/design.png; String targetPdfPath D:/pdf/design.pdf; PdfWriter writer new PdfWriter(targetPdfPath); PdfDocument pdfDoc new PdfDocument(writer); Document document new Document(pdfDoc); // 页面边距手动归零 document.setMargins(0, 0, 0, 0); Image image new Image(ImageDataFactory.create(srcImagePath)); // 7.x中可以通过image.getImageWidth()拿到像素宽然后自己算页面尺寸 float width image.getImageScaledWidth(); float height image.getImageScaledHeight(); pdfDoc.addNewPage(new PageSize(width, height)); document.add(image); document.close(); } }7.x的API更现代化但“图片尺寸映射页面尺寸”的核心逻辑没有变只是换了一组类名。4. 常见问题与排查技巧实录4.1 高频问题速查表现象根本原因解决办法PDF图片放大后糊原图分辨率不足或DPI设置缺失导致显示尺寸过大确认原图像素调用image.setDpi(300, 300)必要时scaleToFit而不是直接拉伸图片四周出现白边创建Document时使用了默认页面边距new Document(pageSize, 0, 0, 0, 0)显式传0边距大图批量处理时报OutOfMemoryError所有图片同时被加载进内存逐张处理及时置空引用启动参数加-Xmx2gPNG图片背景变黑PNG透明通道在PDF渲染时未处理转成白底JPEG或手动添加白色底层矩形手机照片方向不对EXIF方向信息未被iText读取读取Orientation字段调用setRotationDegrees中文文件名或中文路径乱码文件编码问题使用UTF-8字符串注意Windows下路径编码4.2 独家排查经验第一个经验输出PDF后一定要在多个阅读器里检查一遍。福昕和Adobe Acrobat的渲染引擎不完全一样某些图片在福昕里正常在Acrobat里颜色会偏一点反过来也有可能。我每次生成完至少用两个客户端打开目测一遍确认没有黑块、白边、方向问题。第二个经验文件大小“异常”往往不是bug而是图片本身太大。iText默认不做压缩原样嵌入JPEGPDF大小和图片总文件大小差不多。如果觉得PDF太大有两条路一是把原图先用Bicubic缩放算法缩小到目标尺寸比如2000px宽再嵌入二是用ImageIO.write重新编码成质量85%的JPEG。注意第二条路会损失一点画质适合对清晰度要求不那么苛刻的场景。第三个经验调试定位问题要分“原图画质”和“PDF画质”。我在排查“PDF模糊”时习惯先把原图在本地图片查看器里放大到100%看一次如果原图本身就糊问题不在iText如果原图清晰PDF糊再去检查DPI和页面尺寸的换算以及scaleAbsolute是否被误用。分清责任环节调试效率会高很多。5. 一点后续可以扩展的方向这套图片转PDF的思路其实只是iText能力的冰山一角。线上跑通之后我顺手做了几件事给生成逻辑加了一个Spring Boot接口前端调一下就把图片传上来、PDF下载回去又在PDF里通过PdfContentByte叠加了简单的页脚水印这样归档的文件带上了时间和批次信息。批量扫描件、产品图册、设计稿交付这些场景基本都能覆盖了。如果你熟悉Java这套方案很容易做成一个小工具服务如果你只是偶尔用一次直接改改上面的main方法也完全够用。本文还有配套的精品资源点击获取
返回列表