把手机变成一台随身扫描仪 —— 拍一张,自动找出纸的四边、拉直、去阴影,多页拼成一个 PDF,全程不联网
一个干净的 Android 文档扫描 App:不引任何第三方库、不申请任何权限、不联网。拍一张纸,它会自动找出纸张四边并把拍歪的 画面拉成正视;光线不均、一半在阴影里,也能压平;最后把多页合成为一个 PDF 分享出去。
最直白的一条:这个 App 的权限列表是空的。拍照交给系统相机、从相册选图走系统文件选择器、导出的 PDF 用自己的
ContentProvider 递出去——三件事都不需要权限。照片和文字没有任何技术路径离开这台设备。
当前版本 v0.1.0 · 纯 Java 实现 · 零第三方依赖 · 不需要联网 · 不需要存储权限 · 不需要账号
| 自动找边 |
拍完(或从相册选完)立刻自动猜出纸张的四个角,拖角微调即可。找不到明显的纸时退成整幅画面,等你手动拖—— 不会拦着你用。 |
| 四角编辑 |
四角可拖,同一侧会弹出放大镜,手指再粗也能对到纸的边缘上。拖过头会把四边形拧成自交或压成一条线, 这时直接「顶住」不让拖——因为那样解不出正确的变换。 |
| 透视拉直 |
由四组对应点解出单应矩阵,对输出图的每个像素反查它在原图的位置再双线性取色,斜着拍也能拉成正视, 而且不会出现空洞。 |
| 四种效果 |
原图 / 彩色 / 灰度 / 黑白,实时预览,改档位马上能看到结果。彩色档用「像素 × 全局均值 ÷ 局部背景」做光照归一化, 同一张纸上一半被灯照、一半在阴影里,压平后亮度一致;黑白档用 Bradley 自适应阈值,靠积分图做到 O(像素数), 阴影区不会被整片染黑。 |
| 批量导入 |
从相册一次多选十几张,每张自己找边、拉直、去阴影,直接落成一个多页文档。多选时不逐张让你拖角—— 十几张能拖到崩溃,找边不准的再进文档删掉重拍即可。 |
| 多页文档 |
一个文档就是一个多页扫描件:翻看、加页、删页、重命名、整份删除。缩略图后台解码 + LRU 缓存, 列表滚动不串图、不卡顿。 |
| 导出 PDF |
自写了一个不到 200 行的 PDF 生成器:PDF 的 |
| 分享 |
导出的 PDF 通过自己的 |
- 扫描文档:进扫描页 → 拍一张(或从相册导入)→ 调整页自动找边 → 拖角微调 / 选效果 → 完成。
- 继续加页:扫描页下方会攒出缩略图条,接着拍就行。
- 导入图片:首页 → 导入图片 → 一次多选 → 自动批量处理成一个文档。
- 导出:进文档页 → 导出 PDF → 交给微信 / 邮件 / 云盘。
- 管理:首页长按文档可重命名 / 删除;文档页右上角「更多」可分享 / 重命名 / 删除。
| 零第三方依赖 |
|
| 零权限 |
相机用 |
| 内存自适应 |
扫描流水线里同时活着好几个「每像素 4 字节」的整型数组(灰度图、背景、积分图…),分辨率写死就会在中低端机上 OOM。所以处理分辨率按堆上限自适应:堆够大用 2200(A4 合 168 DPI),小内存机器退到 1400——宁可锐度低一点, 也不能识别到一半崩掉。解码阶段就按 2 的幂次降采样,一张 4000×3000 的照片最多只分配一次。 |
| 算法都自己写 |
Otsu 阈值、积分图 box blur、连通域标记、Bradley 自适应阈值、高斯消元解单应矩阵、区域平均降采样、PDF 交叉引用表—— 全部手写,全部是纯算术。好处是这些类不碰任何 Android API,能在普通 JVM 上直接跑单元测试,不用起模拟器。 |
| 可测 |
|
app/src/main/java/com/ice/scan/
├── data/ Doc / Page / DocStore 文档模型与磁盘仓库(纯 java.io,可直接测)
├── img/ AutoCrop / Perspective / Homography / Enhance / Resize / Quad / PdfWriter
│ 全部纯算术,无 Android 依赖
├── scan/ Pipeline 找边 → 拉直 → 增强 → 落盘 的流水线
├── share/ ScanFileProvider 手写 FileProvider:相机写入 + PDF 分享
├── ui/ Main / Capture / Crop / Viewer / QuadView / ThumbLoader / BaseActivity
└── util/ Bitmaps / Storage 解码编码、原子写
- 不申请任何权限:没有 INTERNET、没有 CAMERA、没有存储权限。清单里能自己数出来。
- 不联网:
AndroidManifest.xml里根本没有INTERNET权限,代码里也没有任何网络调用。 - 照片只在本机:扫描件写在应用私有目录(
getFilesDir()),别的 App 看不到,卸载即清空。 - 没有账号、没有统计、没有上报:没有登录,没有埋点,没有崩溃上报。
- 自动找边的假设是「纸是画面里最亮的那一大块」。旋转 45°、深色纸上写字、或者背景比纸还亮时会猜错—— 所以四角始终可拖,猜错也能自己拖回来。
- 没有手电筒补光、没有连拍纠偏,靠系统相机拍;光线均匀、别用手挡效果最好。
- 追求的是「小、快、干净」。要 OCR(把扫描件变成可编辑文字)不在这个项目的范围内。
去 Releases 下载最新的 app-release.apk,直接安装。
Release 包是正式签名的,可直接覆盖安装升级。
需要 JDK 17、Android SDK(platform 36 / build-tools 36.0.0)。没有 NDK、没有原生库,纯 Java。
./gradlew testDebugUnitTest # 单元测试(纯 JVM,秒出)
./gradlew lintDebug # 静态检查
./gradlew assembleDebug # 产出 app/build/outputs/apk/debug/app-debug.apk正式发布需要签名。在仓库根目录放一个 keystore.properties(已被 .gitignore 忽略):
storeFile=../release.keystore
storePassword=…
keyAlias=…
keyPassword=…没有这个文件时 assembleRelease 会退回 debug 签名,仅供本地验证。
用最少的代码,做最干净的一件事。