杂记 › 技术笔记 › JPG 转 PNG:27760000 个像素一位没变、体积涨到 15.881 倍,JPEG 的块效应被原样锁进无损容器

JPG 转 PNG:27760000 个像素一位没变、体积涨到 15.881 倍,JPEG 的块效应被原样锁进无损容器

先把三层账摆出来

只有一张 JPG,交付方要 PNG。转完之后三件事同时发生:27760000 个像素一位没变,体积涨到 15.881 倍,JPEG 留下的块效应与色度重复被原样锁进无损容器。全部数字来自实测:探针页原样注入 image-convert.html 的 8908 字节 inline script,由无头 Chrome 直接调 addFiles() 与 convertAll(),取真实导出函数的产物。

恒等:三路互比逐通道零差异

源 JPEG 解码像素、模板画布像素、产物 PNG 解回像素三路互比,pixelsDiffering、maxChannelDiff 与逐通道 rDiff/gDiff/bDiff/aDiff 全是 0:7 张合计 3760000 个像素,再加 6000x4000 那张的 24000000 个像素。8 张产物的 IHDR 一律 bitDepth 8、colorType 6、interlace 0,8 对 8 无二次量化。魔数 89504e470d0a1a0a、blob.type 与下载名三处都认定 PNG,后缀伪装成 .webp 的 39587 字节 JPEG 也照样出 504277 字节的 .png。

代价:涨幅只用每像素字节就算得清

样张源字节产物字节倍数
exif6_400x300480275151.565
jp_flat 纯色169847852.818
jp_detail 高细节1079345130214.753
photo_400971211654112.0
fake_webp_is_jpeg3958750427712.738
jp_grad 渐变718511410215.881

同一 400x400 画幅下纯色涨 2.818 倍、渐变涨 15.881 倍,差距全在熵上:源 0.0449 字节每像素对产物 0.7131 字节每像素,商正好是 15.881。容器封装只是小项:签名 8 加 IHDR 25 加 IEND 12 共 45 字节,每片 IDAT 再收 12 字节,45+360+116136=116541、45+348+113709=114102 都能逐项复算,涨的字节全在熵编码本体里。

字节按对数取高 1698 4785 2.818 倍 7185 114102 15.881 倍 9712 116541 12.0 倍 39587 504277 12.738 倍

图 1 柱高按字节对数等比,暗柱源、亮柱产物。

JPEG 的失真痕迹被无损固化

产物解回像素与源 JPEG 解出像素的 8×8 块统计,42 个字段序列化后逐字符相等——相等本身就是坏消息:块效应一丝没被抹掉。photo_1600 的块边界梯度 hRatio 9.286;同一张渐变图从画布直存 PNG 只有 0.136,先过 JPEG 再转就变 1.319。

4:2:0 的色度二次采样也原样留着:相邻像素 Cb 完全相同的比例从对照组 38.15% 涨到 72.93%,cbRatio 从 0.698 变 4.606,cbByResidue 从约 28.7 齐平变 6.283 与 3.379 交替。有损那一步全在 JPEG 编码:原始画布对 JPEG 解码,高细节样张 159994 个差异像素、最大单通道差 142、sumAbsDiff 12524610,同一画布对 PNG 产物是同一组数字,JPEG 解码对产物却恒为 0。所以 PNG 这一跳是不再额外掉画质,不是画质恢复。

往返:只有低熵图收敛

jpg→png→jpg 跑 7 组,回程字节是源头的 0.622、0.79、0.921、0.926、0.941 与两个 1;反向的 PNG转JPG 另算。中间那跳 PNG 一像素不动,只把 8948 字节抬回 116078。照片每趟继续掉:photo_400 第一趟 12185 个差异像素、sumAbs 45085,第二趟 13762 与 59824。低熵图收敛:jp_grad 从第 2 跳起固定在 7185 字节、70 个差异像素、sumAbs 93,第 3 跳 PNG 114131 字节、差 29 字节抖动;jp_flat 回程与源一模一样是 1698。jp_detail 第 3 趟从 101576 到 99952 只降 1.6%,却再多坏 412 个像素。

回程除以源头 0.622 0.79 0.921 0.926 0.941 1 1 照片继续掉 低熵图收敛

图 2 7 组往返的回程字节比值,虚线是比值为一的参照。

元数据无处容身,方向已经烘焙

exif6_400x300.jpg 源段表里 34 字节的 APP1-EXIF(orientation 为 6)在 7515 字节的产物里没留下任何 eXIf chunk,tEXt/iTXt/zTXt/iCCP/bKGD/pHYs/sRGB/gAMA/cHRM 全为 0、metaBytes 0,jp_ 样张源里的 APP2-ICC 472 字节同样没进。方向没被转坏:解码宽高与产物 IHDR 都是 300x400,浏览器已把 orientation 烘焙进像素,丢的只是 34 字节记录。

模板不设上限,失败分支一次没触发

与 convertOne 同一个 toBlob 调用,在 16384x16384(编码输入 1073741824 字节)上出 5012121 字节 / 2320 毫秒,12000x12000 出 2707968 字节 / 1203 毫秒,第 244 行的转换失败分支实测 0 次触发;真拒绝分支只有第 167 行的非 image/ 前缀与第 178 行的读不出图,15 条输入实测 11 成功 4 被拒。第 143 行让 PNG 方向的质量行 display 为 none。

批量 10 张:汇总条与字节级幂等

10 张一批 loadMs 21、convertMs 162,汇总条写「已转换 10 / 10 张 · 合计 281.2 KB → 1.94 MB(体积增加 606%)」。再跑一次 convertAll(),产物 sha 前缀仍是 d72921d5dd519312 与 1f85ef0821cd4944;7 张打 zip 时 DEFLATE 与 STORE 都是 1584975 字节、部件合计 1584197,压缩增益 0 字节——PNG 已把熵压到位。入口在 JPG转PNG。

常见疑问

Q:体积涨十几倍,是不是转换把图搞坏了?

没有。产物与 JPEG 解码逐像素相同,涨的只是记账方式:JPEG 拿块边界梯度换字节,PNG 用无损熵编码如实记录每个像素,0.0449 字节每像素变 0.7131 字节每像素。

Q:转成 PNG 能把 JPEG 的块效应去掉吗?

不能。42 个块统计字段逐字符相等就是答案,块效应与 72.93% 的色度重复一起被无损固化。

Q:有 alpha 通道是不是就有透明背景?

colorType 6 带 alpha 槽位,但 alphaMin 与 alphaMax 同为 255,3760000 个像素里 0 个非全不透明——JPG 不携带透明信息,换容器也长不出来。

📎 相关文章

文本对比的代价全在 (m+1)×(n+1) 这张表:6000 行还能跑,8000 行直接堆内存崩 2026-10-28 10 万个键全进 DOM:JSON 视图的渲染规模、爆栈深度与复制重排 2026-10-28 带 g 的正则是有状态的:同一个 RegExp 连打 4 次 test 只过 2 个 2026-10-28 20 行文件名拆成 18 行两列:粘进 Excel 只占 1 列,1e5 变成 100000 2026-10-28 拖进去一整个文件夹,候选池只多了 1 个名字:文件随机抽取读不到目录里的 2558 个文件 2026-10-28