前面的文章里我们说过,Smart 模式只压缩 PDF 里的图像流,文字和矢量图形保持不动,所以压缩完文字依然可选可复制。但具体是怎么做到的,大家都好奇,今天我们就拆开来讲。
先理解 PDF 的结构
在说具体原理之前,先简单科普一下 PDF 的构成。
一份 PDF 里面其实包含了多种内容:文字(以字体指令形式存在)、矢量图形(线条、矩形、路径)、图像流(照片、扫描件这类位图)。Smart 模式做的事情,就是精准找到所有的图像流,然后替换成体积更小的版本。
怎么找?PyMuPDF 提供了一个page.get_images(full=True)方法,可以拿到当前页面的所有嵌入图像信息,包括图像的唯一标识符(xref)。拿到 xref 之后,就可以提取图像、解码、重新压缩。
核心流程四步走
第一步:提取图像。
用doc.extract_image(xref)把图像从 PDF 里拿出来,得到原始字节数据、宽度、高度。
第二步:计算目标 DPI。
这是 Smart 模式最关键的一个细节。PDF 里嵌入的图像有自己的原始分辨率,如果直接按配置 DPI 压缩,可能会把本来就很小的一个图标放大,反而让文件变大。
代码里用了一个很巧妙的判断:
original_dpi = (img_width / rect.width) * 72target_dpi = min(original_dpi, config.dpi)scale = target_dpi / original_dpi也就是说,只会压缩,不会放大。如果原始 DPI 比目标 DPI 低,就保持原样不动;只有当原始 DPI 高于目标 DPI 时,才会按比例缩小。
第三步:用 Pillow 压缩。
用 Pillow 打开图像,按目标分辨率做一次 LANCZOS 重采样,然后以 JPEG 格式保存。JPEG 支持quality参数和optimize=True,后者会自动做一次内部优化,进一步减少体。
pil_img.save(out_buf, format='JPEG', quality=config.quality, optimize=True)第四步:判断是否真的需要替换。
这里有一个小细节——只有当新图像比旧图像更小时,才会真正替换回 PDF。这是"防呆"逻辑:如果压缩完了反而更大,就不做替换,避免无用操。
iflen(new_img_bytes) < len(img_bytes) or config.quality < 95: doc.update_object(xref, new_img_bytes) doc.update_image(xref, {'width': pil_img.width, 'height': pil_img.height})有人可能会问:PDF 里的矢量图形和文字为什么不动?
答案很简单——它们本身的体积就已经非常小了。PDF 的矢量指令是经过编码的字节流,占用空间通常只有几十到几百 KB,真正占体积的是嵌入的图像,特别是高分辨率的扫描图像和照片。
对矢量内容做压缩收益极低,反而容易破坏 PDF 的渲染精度,所以 Smart 模式选择了只动图像这一个策略,精准高效。
支持灰度图和彩色图
Smart 模式还支持把彩色图像转成灰度图,在config.grayscale=True时触发。转换后用'L'模式重新保存,JPEG 灰度图比彩色图体积通常小 2/3 左右,对颜色不敏感的场景特别有用,比如扫描的文稿、图纸。
总结
Smart 模式的核心逻辑:
整个过程保留了 PDF 的文字、字体、矢量图形,最终得到一个体积更小、但内容完全一致的 PDF 文件。
下一期我们来看 Raster 模式和 Split 模式,这两个是 Smart 模式解决不了时的"兜底方案"。
喜欢小居的文章,点赞、关注、转发。点个“在看”支持一下! 👇
有问题评论区或后台找小居!
PDF压缩工具(软件桌面版)已放入合集《桌面端PDF工具箱》。
PDF压缩工具,1秒压缩支持文件拖拽批量处理,3种模式满足各种PDF文件
关注评论“PDF压缩”,免费领取“PDF压缩工具(.exe)”
感谢支持,PDF软件工具持续更新中!