导出没报错,长图却少了一截:我加了道尺寸校验

2 阅读6分钟

导出日志一路成功,打开图才发现最后一页半没了,这种事我碰上过一回。那次是在浏览器里把 8 页 A4 讲义画进 canvas 再要一张 WebP。回调拿到的 Blob 类型没错也能正常打开,可高度只有 16383。后来我把手边几条导出路线超限时的反应全记了一遍,一共五种:报错、返回 null、返回 false 不出文件、裁掉底部、整张等比缩小。前三种只要把返回值接住就能当场发现。后两种不给任何信号,只能事后把宽高读回来跟这张图本该有的尺寸比一比。这篇记的就是这道比对我是怎么加的。机器是一台 M4 芯片的 Mac,样本是我自己排的一份 A4 讲义复制成 8、20、40、60 页。

先算出这张图本该多大

比对的前提是手里先有预期尺寸。PDF 量页面用的是 pt,72 个 pt 是 1 英寸。A4 宽 595 pt 也就是 8.264 英寸,乘上 216 DPI 得到 1785 像素宽。842 pt 的页高按 216 DPI 换成像素是 2526。长图的宽只跟一页有关,高是每页的高乘上页数。照这样算 8 页的预期是 1785×20208、20 页是 1785×50520、60 页是 1785×151560。这几个数在读进 PDF 的时候就能算好而且一页都不用渲染。

宽度不变高度变小就是被裁

Chromium 149 是裁的典型。我开一块 1786×20208 的画布要 WebP 时拿回来的是 1786×16383。再开 1786×65535 要 JPEG 时拿回来的是 1786×65500。两次的宽都没动过,只有高被截到了上限。16383 是 WebP 码流里宽高各 14 位能存下的最大值。JPEG 规格写的是 65535。Pillow 这类常见实现只写到 65500,Chromium 给的也是 65500。判裁的条件很直白:宽等于预期而高小于预期。Firefox 151 遇到同样两个请求直接把 null 交给了回调。WebKit 26.5 要 WebP 时会换成同尺寸的 PNG 交回来。这两种用不着读尺寸,查一下 blob 是不是空、type 对不对就够了。

宽高一起按比例变小就是被缩

网页工具走的是另一条路。我用的是图映 ImgIng(imging.cn/)的 PDF 转图片里「整份 PDF · 拼接成一张长图」这一项,浏览器 Chromium 149。20 页选 216 DPI 时预期是 1785×50520,它给的是 578×16380。宽和高都比预期小,比例却基本没变。我核过 60 页那张产物的末行。最后一行内容还在,说明是缩不是裁。判缩的条件就是宽小于预期并且宽高比跟预期一致。

这里有个小坑卡了我一下。60 页的预期比例是 151560 除以 1785 约等于 84.91。图映给的 192×16380 算出来是 85.31,差了将近 0.5%。毛病出在宽度上。缩到 192 这么窄的时候取整丢掉的零点几个像素占比就大了。比例要是按严格相等去比的话这张图会落进「既不是裁也不是缩」那一档。我最后给比例留了 1% 的余量。这个数是拿手上四档 A4 拍出来的,换别的页面尺寸可能还得再调。判断写成函数大概是这样:

function sizeCheck(out, pic, wantW, wantH, wantType) {
  if (out === null) return '拿到空值';
  if (out.type !== wantType) return '格式不对';
  if (pic.width === wantW && pic.height === wantH) return '完整';
  if (pic.width === wantW) return '被截短';
  const off = Math.abs((pic.height / pic.width) / (wantH / wantW) - 1);
  return off < 0.01 ? '等比缩了' : '对不上';
}

out 是导出回调拿到的那个 Blob,pic 是用 createImageBitmap(out) 解出来的位图,wantW 和 wantH 填上一节算好的预期宽高。脚本里处理的要是文件,用 Pillow 打开读 size 也是一个意思。剩下两种情况不走这个函数。Pillow 超过 WebP 的 16383 或 JPEG 的 65500 时会直接抛异常,这个接住就行。ImageIO 那条路得多留个心眼。我用它写 1785×101040 的 JPG 时收尾那个函数只回了一个 false,目录里也没有文件。我把它的返回值放在比对前面先判,是 false 就算失败,根本走不到读尺寸那一步。

没有DPI选项的工具拿短文件当基准

上面的预期尺寸是按 DPI 算的。可我拿来对照的某在线长图工具压根没给 DPI 选项,手里就没有「本该多大」这个数。我的办法是先喂它一份短的。8 页讲义它给的是 892×10104,10104 除以 8 正好是一页 1263 行。这就是它的默认档,折下来大约 108 DPI。有了这个基准后面几份就能照着比。20 页的预期是 892×25260,它给的也是 892×25260,一点没动。40 页的预期是 892×50520,它给的却是 483×27350。两个比例一个是 56.64、一个是 56.63,差在 1% 以内就判成等比缩小。它自己的说明是过了 50 页才动分辨率,按基准一比,40 页那份已经被缩过了。这家还有个「加间距」的勾。勾上以后每两页之间多出 30 行白条,8 页那份就变成了 892×10314。基准要是拿勾了间距的那份来定,预期高度就得再加上页数减 1 乘 30 的白条。不然每一份都会落进「对不上」。我测这家时一律不勾。

网页工具里加不了代码,可下载下来的文件照样能读尺寸。图映的结果卡上直接写着宽高,8 页那次是 1446 × 16376。跟预期的 1785×20208 一比就知道是缩过的。折成 DPI 也就 175 上下。我现在的批处理脚本每写完一张图都会重新打开读一次宽高,对不上就把那份 PDF 标出来。A4 按 216 DPI 超过 6 页的我直接拆段重导。还没想好的是 WebKit 换格式的那种情况,它给回来的 PNG 尺寸是对的而且图其实能用。我暂时一律当失败处理。这些图在手机和微信里看是什么样子我没测过。