杂记 › 技术笔记 › 截图缩到四分之一,小字糊成一团的三种原因

截图缩到四分之一,小字糊成一团的三种原因

上个月改版,运营丢来一批 1280×720 的界面截图,卡片位只给到 320×180。宽高我照 1/4 重算过,图当天被退回:截图里 12px 的小字糊成一团,竖笔断开。同批顺手试的 1024×576,像素量降到 64%,体积却从 62,744 B 涨到 79,480 B。于是我把这一页真跑起来的代码测了 16 组。

先把三个原因摆出来

  1. 实测结果画布读到的是 imageSmoothingQuality="low",模板第 220–224 行 0 处重采样赋值,生效的是浏览器默认档。
  2. 全模板只有第 224 行一处 drawImage,1280×720 到 320×180 是一次成像:320×180 出 12,310 B、160×90 出 5,023 B、12×7 出 215 B,都是一步出的。
  3. 画布永远等于请求值,doResize 只用第 221/222 行的宽高原值:非等比 800×600 出 69,321 B,是等比 640×360 那档 28,596 B 的 2.42 倍——先变形,再被默认档采一次。

原因一:这一页从没设过采样质量

canvas.width=w
canvas.height=h
const ctx=canvas.getContext('2d')
ctx.drawImage(originalImage,0,0,w,h)

这 4 句(第 221 到 224 行)没有一句碰过 imageSmoothingEnabled 或 imageSmoothingQuality,全文 grep 命中 0 次。缩完 320×180 后直接读第 223 行返回的上下文,实测就是 true 与 "low";同目录的 upscale-image.html 第 317–318 行却显式写了 true 和 'high'。不是浏览器没给高质量档,是这一页没去要。

对小字为什么致命:1px 的竖笔在 1/4 缩比下连一个完整像素都占不满,能借到的邻域样本全由那个默认档决定。我最初那批卡片图就是在调整 PNG 尺寸这一页出的。

原因二:缩多少倍都只有一次采样

第 224 行是模板里唯一的绘制语句:没有第二处 drawImage,没有减半循环,也没有按倍率拆档的分支,4 倍、2 倍、8 倍走同一条路。实测表里每一档——160×90 出 5,023 B、12×7 出 215 B——都是一次成像的结果。一次核函数采样,等于把源图 4×4 邻域的能量一次性压进 1 个像素,没有第二次机会校正笔画。

得划一条线:我不能说「分两步缩会更清晰或更省字节」,模板里根本没有第二步,1280→640→320 的两步对照本轮没跑出来,事实文件第 1.4 节标了未实测。这条只主张:你只有一次采样机会,那次用的是没人赋值的默认档。

原因三:画布只认输入框里那两个数

第 211/212 行用 parseInt 读宽高(800.9 截成 800),第 221/222 行原样赋给画布,16 组实测里画布与解码尺寸都精确等于请求值,321×181 也输出 14,297 B,比 320×180 的 12,310 B 大 16.1%。

第 106 行 aspectLocked 初始为 true,第 188–193 行在锁开时把高度回填成 Math.round(w/aspectRatio),锁关时第 189 行直接 return,而 doResize 从不读它——锁只是输入框层面的辅助,不是缩放约束:800×600 就是 16:9 的截图被塞进 4:3 画布,横竖各带一个缩放率,再交给默认档一步采样。

缩小不一定省体积

请求尺寸输出字节实测备注
320×18012,310 B画布精确,占源 19.6%
640×36028,596 B占源 45.6%
1024×57679,480 B像素量降到 64%,比源大 26.7%
1280×72062,744 B同尺寸往返,校验和同为 6e58cae9
源 62,744 B 320×180 12,310 B 占源 19.6% 640×360 28,596 B 占源 45.6% 1024×576 79,480 B(+26.7%) 像素量降到 64% 1280×720 62,744 B 往返

图 1:1280×720 源图(62,744 B)四个档位的输出字节,虚线是源体积。

19.6% 只是这张图恰好如此,640×360 占源 45.6% 就不等比:缩放只承诺尺寸,不承诺体积。

放大和变形都在加价

800×600 69,321 B 非等比 640×360 28,596 B 等比 69,321 B 是它的 2.42 倍 1920×1080 210,255 B(3.35 倍) 2560×1440 294,510 B(4.69 倍)

图 2:非等比请求与放大档位的真实输出字节。

滑杆也管不着体积:第 64 行那个 min=10 max=100 value=90 的 range 只对 image/jpeg 与 image/webp 显示(第 152–153 行),PNG 下整行隐藏,可第 229 行仍无条件把 parseInt(quality)/100 传进第 236 行的 toBlob——PNG 下它改变了什么本轮没测,所以我不写「拉动滑杆就能压体积」。真在意字节走PNG 压缩;滑杆数字一路传到哪,质量档那篇拆解另有实测。

顺手测的透明 Logo

512×512 的透明 Logo(23,119 B)缩到 64×64 得 2,454 B,alpha 分布是全透 1,941、半透 303、全不透 1,852,合计 4,096。全透占 4,096 里的 47.4%,输出仍是 image/png,没铺白底也没改投 JPEG;但边缘新冒出 303 个半透像素,说明 alpha 值本身被重采样改写,第 331 行「缩放后Alpha透明背景不丢失」被判口径过强就是这个道理。

其余口径我只敢按源码念:第 133 行上限 file.size > 20 * 1024 * 1024,第 216–218 行只判 w>10000,第 246 行把 shot.png 换成 shot_resized.png。运行时 toast 本轮没抓到,事实文件第 7 章整节标了未实测。

三条问答

Q:那 1/4 这一档到底还能不能用?

尺寸这一头是准的:画布精确 320×180,界面文案是 "320 × 180 | 12.0 KB"。但「缩完小字还能读」它给不出保证——没有第二处 drawImage,也没有一处 imageSmoothingQuality 赋值,糊是默认状态。

Q:放大一档是不是比缩更划算?

210,255 B 与 294,510 B 对应 3.35 倍与 4.69 倍,而源只有 62,744 B。

Q:为什么不给出灰度级、边宽、字干 IoU 这些清晰度数字?

因为没跑出来。探针在 512×512 放大到 1024×1024 那一组中断,两次分别设 --virtual-time-budget=120000 与 900000 都停在同一处,三项指标连同两步降采样对照全是空缺,事实文件第 3 章只剩测法。与其编一个看着像实测的百分比,不如把缺口摆明处:这篇关于「糊」的主张只到机制层。

📎 相关文章

PNG 压了三遍只减 426 字节:无损压缩的真实边界 2026-10-22 WebP 转 PNG 后体积涨了几倍:什么时候必须转,怎么把涨幅压住 2026-10-22 同样裁 300×300,头像为什么总偏右下角半格 2026-10-22 按每月 1.7 倍预估 18 个月,脚本和表格为什么给出两个数 2026-10-22 接口 JSON 贴成 PHP 数组,哪些值会悄悄变形 2026-10-22