MLKitDocScanner 拓展:文档扫描(自动找边缘、纠正透视、导出 PDF)

« 返回首页

logo MLKitDocScanner 拓展

调起系统级的文档扫描界面:对着纸张一拍,自动找到四个角、纠正透视变形,还能裁剪、加黑白/增强滤镜、去阴影污点,最后导出 JPG 图片和 / 或 PDF 文件。整套扫描 UI 由 Google 提供,我们不用自己做取景框和边缘检测。

为什么选它:

  • 不用写”初始化”块。扫描参数全是设计器属性,拖进来设好就能直接调 开始扫描。
  • 返回的是能直接用的文件路径。扫描器原本给的是临时 content:// 地址,在 App Inventor 里几乎没法用;本拓展统一复制到应用自己的目录,返回的路径可以直接喂给「图像」组件显示、「分享」组件发送、「文件」组件读取。
  • 模式用词不用数字。base / filter / full,而不是记不住的 1 / 2 / 3。
  • 不重复造轮子。没有自带”分享”方法 —— App Inventor 本来就有「分享」组件,用它的 分享文件 块即可。

重要限制:文档扫描界面由 Google Play 服务提供,ML Kit 没有离线版本。 因此设备必须装有 Google Play 服务,且首次使用需要联网下载扫描模块(约十几 MB,之后可离线)。 没有 GMS 的设备调用 开始扫描 会触发 ScanFailed 事件并说明原因。 这一点和本仓库其它 ML Kit 拓展(条码、文字识别用的是离线捆绑模型)不同,请务必在文档里向用户交代清楚。

不需要申请摄像头权限 —— 拍照在 Google 的扫描界面里完成,不经过我们的 App。

  • .aix 拓展下载:

cn.fun123.MLKit.DocScanner.aix


快速上手

一、扫一页并显示出来

设计器里放一个 MLKitDocScanner、一个按钮、一个「图像」组件。

when 按钮_扫描.Click() {
  DocScan1.Scan()
}
when DocScan1.GotScanResult(pageCount, imagePaths, pdfPath) {
  标签1.Text = join("扫描完成,共 ", pageCount, " 页")
  if length(imagePaths) > 0 {
    图像1.Picture = imagePaths[1]
  }
}
when DocScan1.ScanCancelled() {
  标签1.Text = "已取消扫描"
}
when DocScan1.ScanFailed(errorMessage) {
  标签1.Text = errorMessage
}

二、扫多页导出 PDF 并分享

设计器里再放一个「分享」组件(Sharing)。把 输出格式 属性设为 pdf(或保持默认 both)。

global lastPdf = ""

when 按钮_扫描.Click() {
  DocScan1.PageLimit = 20
  DocScan1.ResultFormat = "pdf"
  DocScan1.Scan()
}
when DocScan1.GotScanResult(pageCount, imagePaths, pdfPath) {
  lastPdf = pdfPath
  标签1.Text = join("已生成 ", pageCount, " 页 PDF")
  按钮_分享.Enabled = true
}
when 按钮_分享.Click() {
  信息分享器1.ShareFile(lastPdf)
}

三、把每一页都显示在列表里

when DocScan1.GotScanResult(pageCount, imagePaths, pdfPath) {
  列表显示框1.ElementsFromString = ""
  列表显示框1.Elements = imagePaths
  标签1.Text = join("共 ", length(imagePaths), " 张图片")
}

四、只要单页、简洁模式(比如拍身份证)

when Screen1.Initialize() {
  DocScan1.PageLimit = 1
  DocScan1.ResultFormat = "jpeg"
  DocScan1.ScannerMode = "base"
  DocScan1.AllowGalleryImport = false
}

五、用完清理占用的空间

扫描结果会一直留在应用目录里,做成”清空历史”功能时调用:

global removed = 0

when 按钮_清除.Click() {
  removed = DocScan1.ClearCache()
  标签1.Text = join("已清理 ", removed, " 个文件")
}

属性

PageLimit
一次最多扫描多少页。至少 1 页,默认 10 页。拍身份证、发票这类单页场景设为 1,用户扫完一页就直接进入确认界面。
AllowGalleryImport
是否允许用户在扫描界面里从相册选图片(而不是只能现场拍)。默认允许。 需要保证是现场拍摄(例如考勤、核验场景)时设为「假」。
ResultFormat
输出格式。
  • jpeg — 只出图片
  • pdf — 只出 PDF
  • both — 两种都出(默认)
ScannerMode
扫描界面的功能档次。
  • base — 只有基础的边缘检测和裁剪,界面最简单
  • filter — 增加黑白 / 增强滤镜
  • full — 再增加去阴影去污点、重新拍摄单页等完整能力(默认)
CaptureMode
拍摄方式。auto 检测到纸张就自动拍(默认),manual 由用户手动按快门。
Scanning
只读。扫描界面是否正在进行中。

事件

GotScanResult(pageCount,imagePaths,pdfPath)
扫描完成(用户在扫描界面点了「完成 / 保存」)。
  • pageCount — 页数。
  • imagePaths — 每页 JPG 图片的文件路径列表,输出格式 含 jpeg 时才有内容。 列表里的路径可以直接给「图像」组件的 图片 属性。
  • pdfPath — PDF 文件路径,输出格式 含 pdf 时才有内容。可以用「分享」组件的 分享文件 发送。
ScanCancelled()
用户取消了扫描(例如按了返回键)。这不是错误,一般不用提示什么。
ScanFailed(errorMessage)
扫描失败。最常见的两个原因:设备没有 Google Play 服务,或首次使用时无法联网下载扫描模块。 错误文本里已经带了可操作的提示,直接显示给用户即可。

方法

Scan()
打开扫描界面开始扫描。 用户扫完点确认 → GotScanResult;按返回键取消 → ScanCancelled;起不来 → ScanFailed。
ClearCache()
清空本拓展生成的所有扫描文件,返回删除的文件个数。

文件放在哪里

扫描结果存在应用私有的外部目录下的 MLKitDocScanner/ 子目录,形如:

/storage/emulated/0/Android/data/<你的包名>/files/MLKitDocScanner/
    page_1751234567890_1.jpg
    scan_1751234567890.pdf

这个位置的好处:不需要申请存储权限、卸载 App 自动清理、路径可以直接给其它组件用。 文件名带毫秒时间戳,多次扫描不会互相覆盖。


常见问题

调用 开始扫描 立刻触发 ScanFailed。 九成是设备没有 Google Play 服务(国产 ROM、部分海外定制机),或者 Play 服务版本太旧。 请在应用里对用户说明清楚,或改用别的方案(例如本仓库的 MLKitCodeScanner 是完全离线的,但它做的是条码而不是文档)。

首次扫描要等一会儿,还提示下载。 Google 的扫描模块是按需下载的(十几 MB),首次使用需要联网。下载完之后再用就不需要网络了。 可以在应用启动时先调一次 开始扫描,让用户尽早完成下载。

GotScanResult 里 imagePaths 是空的。 输出格式 设成了 pdf。想要图片就设 jpeg 或 both。

「图像」组件显示不出扫描结果。 确认传的是 imagePaths 里列表的某一项(如 选择列表项 imagePaths 1),不是整个列表。

能不能自己做扫描界面(不用 Google 那套)? 本拓展的价值恰恰在于复用 Google 那套成熟 UI(边缘检测、透视纠正、滤镜都是现成的)。 自己做需要 OpenCV 级别的图像处理,不在本拓展范围内。

文档反馈