【免费下载链接】FairScan
An Android app to scan your documents — simple and respectful
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 流程就是教科书式的三步:
- 创建
PDDocument,写入创建时间和作者信息 - 为每一页创建
PDPage,用PDPageContentStream.drawImage把图片画满整页 - 全部画完后
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 | -1 | K < 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 质量 | 像素上限 | 适用场景 |
|---|---|---|---|
| LOW | 60 | 100 万 | 快速分享、节省存储 |
| BALANCED | 75 | 200 万 | 日常默认 |
| HIGH | 80 | 400 万 | 需要放大阅读细节 |
注意一个精巧的设计:黑白档位的像素上限是彩色档的4 倍(maxPixels * 4)。原因正呼应上一节——黑白页每像素只有 1 bit,同样分辨率下编码后依然极小,所以可以放心保留更多细节。
质量对导出结果的影响在 ExportPreparation.kt 中落地:LOW 档会把图片缩到像素上限以内再编码,HIGH 档则回到原始照片重新走一遍完整处理管线。
五、隐藏彩蛋:OCR 文字层让 PDF 可搜索
上面这张黑白书页扫描图,如果直接导出成"图片 PDF",文字是无法被搜索和复制的。FairScan 的做法是:在 PDF 里叠加一层不可见的隐藏文字层,实现"看图是图、复制是字":
实现同样在 AndroidPdfWriter.kt:
- 调用 Tesseract OCR 拿到每个文字行的文本和位置
- 嵌入一个"无字形字体"(
TesseractGlyphLessFont.ttf)——这个字体只有度量信息、没有字形轮廓,专门用来给 PDF 阅读器做文本选择 - 生成一份 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.0 | libs.versions.toml |
七、小结:可复用的三个经验
如果你也在做移动端文档扫描类应用,FairScan 的这套方案有几点可以直接借鉴:
- 分模式编码:黑白页走 CCITT G4(1 bit/像素 + 传真编码),彩色页走 JPEG,一行判断即可分流,收益是数量级的
- 位图约定要写清楚:
Bitonal的注释"置 1 为黑、MSB first、按字节补齐"是编码与解码双方不出错的保障 - 页面单位用物理量:以"毫米 → 72 点/英寸"换算页面尺寸,并约束在 Letter/A4 区间,生成的 PDF 在任何阅读器里都大小合适
FairScan 证明了:不依赖云端、只用开源组件,也能在手机上做出"秒出 PDF、体积小、文字可搜索"的文档扫描体验。想深入阅读源码时,从 AndroidPdfWriter.kt 这个入口一路跟下去,你会发现整个导出过程比想象中更清晰。
【免费下载链接】FairScan
An Android app to scan your documents — simple and respectful
相关推荐
终极指南:如何快速优化Android字体压缩与Calligraphy应用
终极指南:如何快速优化Android字体压缩与Calligraphy应用 想让你的Android应用拥有独特的字体风格,同时保持APK体积精简吗?Calligr
移动开发UI组件如何快速掌握HTMLMinifier:新手压缩优化的完整实战指南 🚀
如何快速掌握HTMLMinifier:新手压缩优化的完整实战指南 🚀 HTMLMinifier是一款基于JavaScript的高性能HTML压缩器,专门用于优
前端开发工具构建工具Android视频压缩终极指南:如何快速优化视频文件大小
Android视频压缩终极指南:如何快速优化视频文件大小 VideoCompressor是一个基于Android硬件编解码API(MediaCodec)的高性能
音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考