前端导出的JPEG红字发糊,质量要拉到100才清楚

放假前把这几个坑记了下来,也祝祖国生日快乐。节前排进来的最后一个需求是给活动页加个「保存海报」按钮。前端先用 canvas 把海报画出来再用 toBlob 导出 JPEG 给用户存图。代码里的质量 0.8 是从别的导出功能里抄来的。上线前我照例压测了一轮。白底上那行 22 像素的小红字边上发脏,像蒙了一圈淡粉色。旁边同字号的黑字却很干净。我把质量改到 0.95 再看还是脏的。「再调高点试试」这种结论我交不出去,就把这件事从头量了一遍。

测试图是我用程序画的一张 1600×900 促销风海报。上半截是红底黄字的大标题。下半截是一张白卡,印着 72、32、22 像素三档红字。每档旁边都放了同字号的黑字作对照,整张图存成 PNG 是 123 KB。测试机用的 Apple M4,内存 16 GB。浏览器是 Chromium 149 开源构建而不是日常装的 Chrome。每个质量点我都用 canvas.toBlob('image/jpeg', q) 导出一次。干不干净我没靠眼睛判,只在文字边缘那一圈像素上算色差 ΔE 的平均值。大面积纯色不算在内,算进去会把问题稀释掉。一般认为 ΔE 到 5 以上就是一眼能看出来的色边。

质量 体积 色度抽样 字边 ΔE 均值
0.80 118 KB 4:2:0 8.11
0.90 154 KB 4:2:0 7.11
0.95 193 KB 4:2:0 6.66
0.99 265 KB 4:2:0 6.35
1.00 428 KB 4:4:4 0.19

从 0.80 调到 0.99 文件涨到了 2.25 倍。字边色差却只从 8.11 降到 6.35,还停在一眼可见的那一档里。转折出现在最后一行,质量到 1.00 时色差一下掉到 0.19,跟原图几乎没有差别。它是在最后一格整个换了挡。原因就在「色度抽样」这一列。0.99 和它以下导出的都是 4:2:0,只有 1.00 是 4:4:4。4:2:0 给每个像素都留了亮度,颜色信息却是每 2×2 个像素才留一份。黑字和白底差的主要是亮度,这一刀砍不到它。红字和白底之间的差别大头落在颜色上,颜色只剩四分之一的分辨率时字边就晕开了一圈。

这个挡到底在哪儿换?我测了三个浏览器内核。抽样方式是从导出文件的文件头里直接读出来的采样因子,没有靠猜。

这张图要看的是红格和绿格的分界线在哪儿,红格代表 4:2:0、绿格代表 4:4:4。Chromium 149 和 WebKit 26.5 一直红到 0.99,到 0.995 才变绿。Firefox 151 在 0.90 就已经是绿的了,单独补测出来的起点是 0.895。Chromium 会先把质量四舍五入成整数再交给编码器。0.995 进位成 100 以后跟 1.00 就是同一档。在 Chromium 里想拿到 4:4:4 只有让质量取整后等于 100 这一条路。同样填 0.9 时 Firefox 导出 246 096 字节,Chromium 只有 157 626 字节。差出来的这一大截来自抽样方式而不是编码器的好坏。这三个都是开源构建,Firefox 和 WebKit 的结果我没在正式版 Firefox 和 Safari 上验过。

我还拿线上工具做了一次对照。用的是图映 ImgIng(https://imging.cn/)的「压缩 + 转换」,测试条件还是同一台 M4 和 Chromium 149 开源构建。同一张海报上我只改了输出格式和质量滑杆这两项。

转 JPG 以后我把质量滑杆拉到 100,右边的推荐值是图形/文字档的 88。结果是 264.6 KB,比原图大 114%。旁边还提示想更小就换 WebP。我把这份 JPG 跟 toBlob 质量 0.99 的文件逐字节对了一遍,两份完全相同。也就是说滑杆上的 100 送进编码器时是 0.99。在 Chromium 里它导出的 JPG 一直是 4:2:0,字边色差也是 6.35。我猜它这么改是为了躲开浏览器在 1.0 上的特殊处理。这点我没证实。不改任何参数的话它默认不走 JPG,会把这张海报识别成线稿存成 124 色的 PNG-8。结果是一个 48 KB 的文件,界面上标着省 62%。这个 62% 只对这种几种纯色加字的平面图成立。

程序画的海报毕竟太规整。我又从网上找了一张现成的 2026 年国庆放假通知模板来验,是稿定设计的模板图。它是 1242×2688 的真彩 PNG,有 3.18 MB。红底白字的标题、米白卡片上的红字和一排红字日历它都有。换成它结论还是同一个方向。浏览器 JPEG 质量从 0.80 拉到 0.99 体积涨到 3.2 倍。「假期安排」那几行红字的字边色差只从 6.56 降到 4.48。到 1.00 才切成 4:4:4,色差是 0.22。这时文件有 3 643 511 字节,比原图 PNG 还大。

这是图映处理那张模板时的界面截图。我手动改成了转 JPG,再把质量滑杆拉到 100。上面的红框是滑杆那一栏,右边写着推荐值仍是图形/文字档的 88。下面的红框是结果。3.18 MB 变成了 1.91 MB,标着省 40%。这份文件读文件头是 4:2:0,字边色差 4.48。它解码后的像素跟 toBlob 质量 0.99 逐像素相同。这张模板它默认也没走 PNG-8。它判成截图/界面存成了 WebP 84,界面标着省 82%。那份 WebP 的字边色差是 5.12,跟 WebP 有损在同一个水平,谈不上保住了红字。

落到代码上我给导出函数改了两处,第一处是必须给 JPEG 的场合。质量直接填 1.0 并在旁边写一行注释说明为什么是 1.0。这样下一个人就不会当成手滑改回 0.9。代价是一个比原图 PNG 大 3.5 倍的 428 KB 文件。第二处是带小号红字的海报默认导 PNG 不再导 JPEG。这种几种纯色加文字的图存 PNG 本来就只有 123 KB。这条只对平面图有效。那张带渐变插画的模板存 WebP 无损是 2 359 014 字节,比 JPEG 0.9 还大 2.5 倍。这时候不如交给服务端出质量 75 的 4:4:4。它是 680 616 字节、色差 4.29。它比浏览器 0.99 更干净,体积只有三分之一左右。质量从 0.8 调到 0.99 是最不划算的改法。体积多花了一倍多,字还是脏的。

上线前可以自己验一下导出文件用的是哪种抽样。拿十六进制查看器打开 JPEG 找到 FF C0 这两个字节,渐进式的 JPEG 是 FF C2。从 FF 算起数到第 12 个字节,它是 22 就是 4:2:0,是 11 就是 4:4:4。还有一件事我没来得及测。428 KB 的海报在手机上存图会不会明显变慢,我留到节后再看。

相关推荐
思考着亮41 分钟前
2.skill
人工智能
code_slave(码畜)44 分钟前
微服务架构落地:基础服务 —— 报表服务(AI 集成篇:AI 增强报表能力)
人工智能·spring boot·spring cloud·微服务·架构
码上观世界44 分钟前
Paseo 是如何统一管理 Claude Code 和 Codex 的?
人工智能·codex
长弓三石1 小时前
把 AgentScope Harness 装进 RuoYi-Vue-Plus:纯 Java AI 平台的集成实践
java·人工智能·agent
思考着亮1 小时前
3.什么是Harness Engineering?什么又是Loop Engineering?
人工智能
蜗牛互联网1 小时前
MongoDB Atlas Agent Engine之后,如何用版本门禁防止陈旧写入
java·数据库·人工智能·后端·mongodb
长弓三石1 小时前
企业级智能体的权限到底怎么落地?以 BizBuddy 为例
java·人工智能·agent
慢云智慧空间1 小时前
从智能终端到空间AI,慢云科技如何重新定义智慧建筑的核心能力?
人工智能·python·科技
合调于形1 小时前
Jusshen zhzzneng《具身智能》词条汉语拼音字母标调拼写实测案例
人工智能·自然语言处理·人机交互·语音识别·学习方法