ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

FairScan如何快速生成PDF:PDFBox-Android与CCITT G4压缩优化实战

FairScan如何快速生成PDF:PDFBox-Android与CCITT G4压缩优化实战

【免费下载链接】FairScan

An Android app to scan your documents — simple and respectful

项目地址:https://gitcode.com/gh_mirrors/fa/FairScan
点击查看免费下载

FairScan 是一款开源的 Android 文档扫描应用。本文带你拆解它如何基于 PDFBox-Android 在手机上快速生成 PDF,并用 CCITT G4 压缩把黑白页面体积降到极小、同时让文字依然清晰可搜——即使不写一行代码,你也能理解这份"小体积 + 高清晰"背后的完整设计思路。

在 FairScan 里,从拍照到得到一份可分享的 PDF 只需几秒钟。但"快"背后藏着不少工程取舍:用哪个 PDF 库?黑白页和彩色页该怎么编码?页面尺寸如何对齐真实纸张?本文沿真实源码把这些问题讲清楚。

一、扫描流程全景:先看图,再谈压缩

FairScan 的处理管线是"拍摄 → 检测文档边缘 → 透视矫正 → 增强 → 导出 PDF",全部在本地完成,不经过任何云端。下面这张图就是一张等待处理的文档照片,FairScan 会自动把页面从桌面背景中"抠"出来:

真正与"生成 PDF"相关的代码集中在一条调用链上:

  • 界面层:ExportViewModel.kt 负责点击"导出"后准备数据
  • 文件层:FileManager.kt 通过PdfWriter接口写出 PDF 文件
  • 实现层:AndroidPdfWriter.kt 把每页图片"画"进 PDF 文档

这种"接口 + 实现"的分层设计很典型:PdfWriter是一个只含一个方法的函数式接口(FileManager.kt 中的fun interface),将来换成别的 PDF 库时,上层代码完全不用动。

二、为什么选 PDFBox-Android

桌面 Java 世界里写 PDF 几乎人人用 PDFBox,但原生的 PDFBox 依赖 Java AWT,无法直接跑在 Android 上。FairScan 的答案是使用 PDFBox 的 Android 移植版pdfbox-android,版本集中在 libs.versions.toml 中统一管理:

  • 依赖坐标:com.tom-roush:pdfbox-android:2.0.27.0
  • 包名从org.apache.pdfbox换成了com.tom_roush.pdfbox,API 与桌面版基本一致

于是 AndroidPdfWriter.kt 里的写 PDF 流程就是教科书式的三步:

  1. 创建PDDocument,写入创建时间和作者信息
  2. 为每一页创建PDPage,用PDPageContentStream.drawImage把图片画满整页
  3. 全部画完后document.save(outputStream)一次落盘

这里有一个新手容易忽略的细节:PDF 页面的单位是 point(点),1 英寸 = 72 point。FairScan 利用这个约定把像素换算成物理尺寸(AndroidPdfWriter.kt):

  • 知道纸张真实毫米尺寸时(比如 A4 合同),直接按毫米换算
  • 不知道时,按"A4 高度"等比缩放兜底

并且用constrainToMaxFormat函数把页面约束在 Letter 宽到 A4 高的合理区间内,避免生成的 PDF 页面大得离谱或小到无法阅读。

三、CCITT G4 压缩:黑白页为什么能小几个数量级

彩色照片用 JPEG 天经地义,但黑白文档页有个特殊优势:每个像素只有"黑/白"两种状态,1 个 bit 就够。FairScan 对黑白页走了完全不同的编码路线,这也是本文的核心。

第一步是二值化。在 PostProcessing.kt 中,binarizeDocument使用 Sauvola 自适应阈值把灰度图变成纯黑白图,再配合去噪点、填孔等操作,消除阴影和背景噪纹。

第二步是位打包。BitonalPacking.kt 里的packBitsMsbFirst函数把黑白图像压缩成"每像素 1 bit"的紧凑数组:

  • 每 8 个像素挤进 1 个字节,从高位开始(MSB first)
  • 每行按整字节补齐
  • 位被置 1 表示黑色——这个约定恰好符合 CCITT 编码器的默认行为

得到的数据结构非常简洁,定义在 Image.kt:

// One bit per pixel, MSB first, rows padded to whole bytes, set bit means black. class Bitonal(val width: Int, val height: Int, val bits: ByteArray)

第三步是调用 CCITT G4 编码器。CCITT(国际电报电话咨询委员会)G4 是传真标准的压缩算法,对"大片白色 + 稀疏黑色文字"的文档页有极佳的压缩率。FairScan 直接用 PDFBox 内置的CCITTFAX_DECODE过滤器来完成编码(AndroidPdfWriter.kt):

FilterFactory.INSTANCE.getFilter(COSName.CCITTFAX_DECODE) .encode(ByteArrayInputStream(bitonal.bits), encoded, decodeParms, 0)

这里藏着两个关键参数:

参数值含义
COLUMNS/ROWS图像宽高解码端还原尺寸所必需
K-1K < 0即 Group 4(二维)模式,压缩率最高

源码注释还解释了一个"避坑点":PDFBox 自带的CCITTFactory强制要求ALPHA_8位图,FairScan 无法复用,于是照着官方调用序列手写了一遍;同时因为编码器把置 1 的位读作黑色,所以故意不设置/BlackIs1。

对比一下效果就明白了:一张 2100×2800 的纯黑白 JPEG 大约要几 MB,而同一页经 CCITT G4 编码后往往只有几十 KB——体积缩小几十倍,画质却无损,因为 1 bit 本身就承载了页面全部信息。

而彩色或灰度页则回退到 JPEG:JPEGFactory.createFromByteArray直接嵌入(AndroidPdfWriter.kt)。两条路线的选择只取决于一行判断——页面颜色模式是否为BLACK_AND_WHITE。

四、三档导出质量:清晰度与体积的平衡

"快而小"不等于"一味压缩"。FairScan 提供了 LOW / BALANCED / HIGH 三档质量(ExportQuality.kt),差异体现在两个维度:

档位JPEG 质量像素上限适用场景
LOW60100 万快速分享、节省存储
BALANCED75200 万日常默认
HIGH80400 万需要放大阅读细节

注意一个精巧的设计:黑白档位的像素上限是彩色档的4 倍(maxPixels * 4)。原因正呼应上一节——黑白页每像素只有 1 bit,同样分辨率下编码后依然极小,所以可以放心保留更多细节。

质量对导出结果的影响在 ExportPreparation.kt 中落地:LOW 档会把图片缩到像素上限以内再编码,HIGH 档则回到原始照片重新走一遍完整处理管线。

五、隐藏彩蛋:OCR 文字层让 PDF 可搜索

上面这张黑白书页扫描图,如果直接导出成"图片 PDF",文字是无法被搜索和复制的。FairScan 的做法是:在 PDF 里叠加一层不可见的隐藏文字层,实现"看图是图、复制是字":

实现同样在 AndroidPdfWriter.kt:

  1. 调用 Tesseract OCR 拿到每个文字行的文本和位置
  2. 嵌入一个"无字形字体"(TesseractGlyphLessFont.ttf)——这个字体只有度量信息、没有字形轮廓,专门用来给 PDF 阅读器做文本选择
  3. 生成一份 ToUnicode CMap 映射,让任意 Unicode 字符都能被正确识别

思路直接借鉴自 Tesseract 官方的pdfrenderer.cpp。另外有个性能细节:黑白页在 PDF 里是 4 倍像素密度,OCR 时先用halfSize缩回一半,避免文字识别白白多花 4 倍时间。

六、核心模块速查

模块职责文件
PDF 写入与 CCITT G4 编码组装 PDF、嵌入图像、OCR 文字层AndroidPdfWriter.kt
位打包(1 bit/像素)MSB first 打包/解包BitonalPacking.kt
二值化与去噪Sauvola 阈值、填孔、去噪点PostProcessing.kt
导出质量定义三档质量与像素上限ExportQuality.kt
导出前准备按质量分档处理页面ExportPreparation.kt
依赖版本pdfbox-android 2.0.27.0libs.versions.toml

七、小结:可复用的三个经验

如果你也在做移动端文档扫描类应用,FairScan 的这套方案有几点可以直接借鉴:

  1. 分模式编码:黑白页走 CCITT G4(1 bit/像素 + 传真编码),彩色页走 JPEG,一行判断即可分流,收益是数量级的
  2. 位图约定要写清楚:Bitonal的注释"置 1 为黑、MSB first、按字节补齐"是编码与解码双方不出错的保障
  3. 页面单位用物理量:以"毫米 → 72 点/英寸"换算页面尺寸,并约束在 Letter/A4 区间,生成的 PDF 在任何阅读器里都大小合适

FairScan 证明了:不依赖云端、只用开源组件,也能在手机上做出"秒出 PDF、体积小、文字可搜索"的文档扫描体验。想深入阅读源码时,从 AndroidPdfWriter.kt 这个入口一路跟下去,你会发现整个导出过程比想象中更清晰。

【免费下载链接】FairScan

An Android app to scan your documents — simple and respectful

项目地址:https://gitcode.com/gh_mirrors/fa/FairScan
点击查看免费下载
上一篇:Sub-Zero与Bazarr对比:为什么这仍是Plex用户的最佳字幕解决方案
下一篇:LXMusic开源项目音源配置技术解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表