先把三层账摆出来
只有一张 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_400x300 | 4802 | 7515 | 1.565 |
| jp_flat 纯色 | 1698 | 4785 | 2.818 |
| jp_detail 高细节 | 107934 | 513021 | 4.753 |
| photo_400 | 9712 | 116541 | 12.0 |
| fake_webp_is_jpeg | 39587 | 504277 | 12.738 |
| jp_grad 渐变 | 7185 | 114102 | 15.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 都能逐项复算,涨的字节全在熵编码本体里。
图 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 个像素。
图 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 不携带透明信息,换容器也长不出来。