ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从像素级操作用到真实纸张质感:make-look-scanned的6大图像算法完整拆解

从像素级操作用到真实纸张质感:make-look-scanned的6大图像算法完整拆解
  • 图像处理
  • 开发工具

【免费下载链接】make-look-scanned

Makes PDFs look scanned (CLI or in the browser via WASM)

项目地址:https://gitcode.com/gh_mirrors/mak/make-look-scanned
点击查看免费下载

make-look-scanned 是一个 Go 编写的 PDF 扫描效果生成工具:它把一份电子 PDF 逐页"劣化"成扫描件的样子——倾斜、灰度、暖纸色、扫描颗粒、失焦模糊、边缘阴影,再加上低质量 JPEG 压缩痕迹。它既能作为命令行 CLI 使用,也能通过 WebAssembly 在浏览器里本地运行,全程不上传文件。

本文带你从像素级操作开始,完整拆解它的 6 大图像算法,看懂"电子文档"是如何一步步变成"真实纸张质感"的。

效果流水线:顺序为什么决定真实感

核心思想在 internal/effects/pipeline.go 中一句话讲清:每个效果是一个独立阶段,对应一个 CLI 参数;数值参数为 0 时该阶段自动关闭。

流水线按固定顺序执行,这个顺序本身就是"算法设计":

  1. 灰度 → 2. 纸张色调 → 3. 高斯模糊 → 4. 噪声 → 5. 边缘阴影 → 6. 倾斜旋转

页面先"做旧"、再旋转,这样之前所有效果都会跟着纸张一起歪斜,而不是贴在画面上的死板滤镜。第 7 步 JPEG 压缩则放在组装阶段(见下文),因为低质量压缩本身就是"扫描仪"最露馅也最可信的特征。

🎯 另一个容易被忽略的细节:每个随机效果(噪声、倾斜)各自持有独立的随机数生成器,且播种顺序固定。这样噪声的随机数消耗不会因为 DPI 变化(像素更多)而干扰倾斜角度,保证"同一输入 + 同一种子 = 字节级一致的 PDF"。

6大算法逐个拆解

以下每个算法都对应一个源文件,位于 internal/effects/ 目录,全部用纯 Go 手写像素运算,不依赖图像处理库。

① Grayscale:把整页压成单一亮度

源码:internal/effects/grayscale.go

算法只有一行核心逻辑:对每个像素按 Rec.601 亮度公式计算灰度值:

Y = 0.299R + 0.587G + 0.114B

然后 R、G、B 三通道全部赋为 Y。这是人眼感知最接近真实的去饱和方式,也是老式扫描仪默认输出的"传真灰"基础。

② Paper tone:暖白纸色 + 黑色抬升

源码:internal/effects/papertone.go

真实纸张不是纯白。这个算法做两件事:

  • 白纸变暖白:把纯白 (255,255,255) 映射到纸色 (250, 247, 236) —— 略带米黄的 off-white;
  • 黑色抬升:最黑的墨迹不再是 0,而是抬升 18 个亮度等级,变成"扫描灰"。

参数--paper-tone(0~1)控制原图与调色结果的混合强度。这一步让整页瞬间从"屏幕感"变成"打印纸感"。

③ Blur:可分离高斯模糊模拟失焦

源码:internal/effects/blur.go

实现教科书级的可分离高斯卷积:

  1. 根据 sigma 生成 1D 高斯核(半径取ceil(3σ),权重归一化);
  2. 先做水平方向卷积到临时缓冲,再做垂直方向卷积回结果。

相比直接做 2D 卷积,可分离写法把复杂度从 O(n²·σ²) 降到 O(n²·σ),性能提升巨大。sigma 即 CLI 的--blur参数,模拟扫描仪光学系统的轻微失焦。

④ Noise:高斯分布的扫描颗粒

源码:internal/effects/noise.go

对每个像素加上一个高斯随机数,标准差由参数线性放大:stddev = noise × 40。

关键在三通道加同一个噪声值(n同时加给 R/G/B),这样噪声只改变亮度、不改变色相——这正是真实 CCD 传感器读出噪声的表现,而不是花屏。

⑤ Edge shadow:纸张微微翘起的边缘暗影

源码:internal/effects/edgeshadow.go

模拟纸张没有完全贴合玻璃、边缘微微翘起造成的边缘暗角(vignette):

  • 影带宽度取页面短边的 12%;
  • 每个像素到最近边缘的距离 d 决定变暗程度,边缘最深、向内线性淡出到 0;
  • 远离边缘的像素直接跳过,不做无谓计算。

⑥ Skew:随机小角度旋转 + 双线性抗锯齿

源码:internal/effects/skew.go

扫描时纸放歪了——这是最容易被识别、也最难伪造的特征。算法:

  1. 在 ±--skew度内随机取一个角度;
  2. 对每个目标像素做反向旋转,回到源图坐标;
  3. 用双线性插值采样:落在图像外的邻近点贡献纸色 (250,247,236) 而不是钳制到 0,于是旋转后的页面边缘自然与纸底色混合,形成平滑的抗锯齿边线,而非锯齿状硬边。

💡 "反向映射 + 亚像素采样"是旋转类效果的通用正确姿势,正向映射会留下空洞像素。

隐藏的第七步:JPEG 压缩痕迹

前 6 个算法处理完,页面还是"无损的"。真正让它像扫描件的是组装阶段的低质量 JPEG 重编码,源码见 internal/assemble/assemble.go:

  • 每页编码为 JPEG(默认质量 70,可低至 1),直接嵌入图片型 PDF;
  • 页面尺寸保留 PDF 原始的 point 尺寸,非标准页幅不丢精度;
  • 特意选用 gopdf 而非 fpdf,因为它按确定顺序序列化、不打时间戳——这是"确定性输出"的最后一环。

快速上手:一条命令生成扫描件

go build -o make-look-scanned . ./make-look-scanned in.pdf # 输出 in.scanned.pdf ./make-look-scanned in.pdf --noise 0.4 --skew 2.5 --jpeg-quality 30

主要参数一览(数值为 0 即关闭对应效果):

参数默认值对应算法
--skew0.6⑥ 倾斜角度上限(度)
--grayscaletrue① 灰度(唯一布尔参数)
--paper-tone0.6② 暖纸色调强度 0~1
--blur0.4③ 高斯 sigma
--noise0.08④ 扫描颗粒 0~1
--edge-shadow0.15⑤ 边缘暗角 0~1
--jpeg-quality70⑦ JPEG 质量 1~100
--dpi150渲染分辨率(最高 1200)
--seed内容哈希随机种子

所有参数会先经过Validate()统一校验(见 pipeline.go 第 44~62 行),超范围直接报错,避免浪费渲染时间。

配置预设:把常用参数存成 config.toml

在~/.make-look-scanned/config.toml(或$XDG_CONFIG_HOME/make-look-scanned/config.toml)中定义预设:

[presets.medium] skew = 1.5 paper_tone = 0.6 noise = 0.2 blur = 0.6 edge_shadow = 0.3 jpeg_quality = 45

之后./make-look-scanned --preset medium in.pdf即可。优先级为:内置默认 → 预设 → 显式 CLI 参数(后者永远胜出),实现逻辑在 internal/config/config.go。

为什么同一份文件总生成同样的扫描件?

默认情况下种子由输入 PDF 的字节内容用 FNV-64a 哈希得出(见 main.go 的contentSeed),因此:

  • 同一 PDF 反复运行 → 完全相同的扫描件(方便复现、便于比对);
  • 传--seed N→ 换一个可复现的"长相";
  • 同一输入 + 同一种子 →字节级一致的 PDF。

同一套算法跑在浏览器里

由于 go-fitz/MuPDF 无法编译到 wasm,浏览器版换用 PDF.js 渲染页面,但拿到像素后交给同一套 Go 效果算法(编译为 wasm),入口在 cmd/wasm/main_js.go,页面在 web/index.html:

./web/build.sh # 开发模式 task build:web # 打包成单文件 dist/make-look-scanned.html

单文件 HTML 内联了 wasm、Go 运行时胶水代码和 PDF.js,双击即可离线使用。浏览器版与 CLI 视觉效果一致(渲染器不同,字节不保证一致)。

小结

make-look-scanned 的魅力在于"用工程换真实":灰度、暖纸色、失焦、颗粒、暗角、歪斜六个算法各自只解决一个物理现象,顺序和随机性又都经过精心设计,最后用低质量 JPEG 收尾。理解它 6 大算法的实现,你对图像流水线(可分离卷积、反向映射采样、按通道一致的噪声、确定性随机播种)也会多一层实战认知。

  • 图像处理
  • 开发工具

【免费下载链接】make-look-scanned

Makes PDFs look scanned (CLI or in the browser via WASM)

项目地址:https://gitcode.com/gh_mirrors/mak/make-look-scanned
点击查看免费下载

相关推荐

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进