一张做了半天的动图,为了塞进对话框把宽从 200 改成 100,下载回来发现它不动了。这不是「缩太小所以看不出来」——把它当二进制打开,前 8 个字节是 89 50 4E 47 0D 0A 1A 0A,PNG 的签名;GIF 应该以 GIF8 开头。而文件名后缀还是 .gif。我把这条路径从头查了一遍,结论比预想的更靠底层。
先把钉得住的事实摊开:
- 源文件
anim_2frame.gif597 B,字节级遍历读到 2 个图形控制扩展和 2 个图像描述符,帧延时是 30 和 30;产物 1028 B 里这两类块的计数都是 0,APNG 的 acTL 也是 0。 - 5 帧的
fiveframe_rg.gif5223 B 和只有 1 帧的oneframe_rg.gif1071 B,喂同一个输出尺寸,产物字节一模一样,都是 1028 B。 - 质量滑杆从 10 拉到 100,GIF 的三次产物全是 1028 B;同一根滑杆对 JPEG 给出 2038 B 与 54687 B。
帧数不能靠解码器报,也不能数裸字节
模板里 frame、delay、loop、fps 出现 0 次,drawImage 全文只有 1 次(第 224 行):取帧这个动作压根不存在,只有一次把整张 <img> 重画到画布上的重绘。
但「代码里没写」不等于「产物里没有」。帧数得实测。ImageDecoder 在本机这份构建里对这批样张读不出帧,不能当证据;退一步数 GIF 图像描述符那个 0x2C 也会翻车:2912 B 的 PNG 产物上朴素扫描数出 9 个,3068 B 那份数出 7 个,zlib 压完的数据里这些字节天然会出现。所以帧数一律走结构化遍历,再用产物 chunk 链(IHDR IDAT IDAT IEND)与 acTL=0 确认它既不是 GIF 动画也不是 APNG。
像素侧更直接:twoframe_rg.gif 第一帧红、第二帧纯蓝,画到画布后 meanR=179.52 / meanB=0,蓝通道恒为 0;t0、t700、t2200 三次取样数值一字不变——画上去的就是首帧。
产物字节与源帧数完全脱钩
图 1|三张动图的帧数与源体积都不同,同一输出尺寸下的产物却全停在 1028 B;灰柱是源字节,绿柱是产物字节,1 帧那张是对照组。
产物体积只跟画布上的像素有关,与源文件里叠了几层无关。
请求、文件名、魔数,三样各说各话
| 口径 | 实测 |
|---|---|
| 第 227 行交给编码器的 mime | image/gif |
| 第 246 行拼出的下载名 | anim_2frame_resized.gif |
| 产物前 8 字节 | 89 50 4E 47 0D 0A 1A 0A |
同画布显式请求 image/png | 384 B,哈希 cf6a3a06,与上一行逐字节相同 |
回落不在代码里:第 228 行那条格式改写只管 image/bmp,GIF 在第 227 行是无条件透传 originalFile.type。浏览器压根没有 GIF 编码器,于是 toBlob 安静地改用了 PNG。表格最后一行就是证据:请求换成 image/png,长度与哈希一位不差。
质量滑杆对 GIF 是一根空杆
滑杆取 10、90、100 时,第 229 行 parseInt(value) / 100 确实把 0.1、0.9、1 传进了 toBlob,但三次产物都是 1028 B、哈希都是 5808201c,最低档与最高档逐字节相同。同一根滑杆对 JPEG 样张给出 2038 B 与 54687 B——滑杆没坏,是 PNG 编码器不读这个参数。
编码器在场也一样丢帧
「浏览器没有 GIF 编码器」解释得了格式,解释不了帧的去向。拿动画 WebP 做对照:338 B 那张有 2 个 ANMF 块,产物 620 B 仍是真 WebP(魔数 52 49 46 46),ANMF 却是 0;582 B 那张有 3 个 ANMF,产物 622 B、ANMF 也是 0。编码器明明在工作,动画控制块照样全数消失——丢帧在画布这一侧。这两张只是对照样本,不是可选的输出格式。
透明比动画活得久
twoframe_rg.gif 在 200x120 下有 7104 个 alpha 小于 255 的像素(共 24000),缩到 100x60 是 1776 个(共 6000),放大到 400x240 是 29500 个(共 96000),其中 2176 个是重采样在透明边界新造的半透明像素。产物 PNG 头 depth=8 ctype=6,alpha 通道完整保留——丢的是时间维度,颜色反而留得更完整。
上限只判一边,缩小尺寸也不等于缩小体积
第 216 行是 w > 10000 || h > 10000,只看单边,代码里没有总像素上限。实测 2000x2000(4000000 像素)产物 84599 B、4000x4000(16000000 像素)产物 316985 B,请求的都是 image/gif,魔数都是 PNG。体积也一样:597 B 那张按原尺寸 200x120 输出反而得到 1028 B,比源文件大 431 B;只有降到 100x60 才第一次小于 597 B,是 384 B。
后缀不算数,失败也不吭声
能不能进门只看第 129 行对 file.type 的那处正则,后缀和魔数都不参与:5928 B 的 PNG 改名 .gif 被嗅探成 image/gif,按 640x640 解码成功后照样走完 GIF 那条导出分支。坏文件的反馈是零——模板只在第 141 行挂了 onload,全文 onerror 出现 0 次,72 B 的 corrupt.png 与 0 B 的 GIF 实测 toast 数都是 0。体积闸门是严格大于 20971520 B,要到 20971521 B 才触发第 134 行那句提示。
文案说对了前半句
工具页那段介绍写着「本工具仅调整GIF第一帧的尺寸,不改变动画播放」,由 layout/tool.html 第 66 行渲染,读者看得见。前半句与实测吻合,后半句被产物证伪:动画控制块一个都没有,不是「没被改变」,是不存在。也别以为 GIF 有专属优待,全文 gif 只出现 4 次,全在第 11、12、129、130 行的文案与白名单里。要逐帧处理,站内把 GIF 拆成一帧一帧那篇方向相反,这里不重复;只想给首帧换个大小,调整 GIF 尺寸这一步没问题,只是拿到的是 PNG——这份模板被五个工具共用,横向对比可走通用的图片尺寸调整。
Q:手动改一次后缀,能把动画救回来吗?
不能。改后缀只改名字,文件里本来就没有第二帧,也没有帧延时和循环计数,遍历产物得到的是空数组。
Q:那这张产物到底该怎么用?
把它当静态 PNG 用:alpha 通道在,首帧的透明边界也在,比原图那张多帧 GIF 更好控制。
Q:宽高与质量之外,还有别的参数能保住动画吗?
没有。界面上能动的只有宽高和质量,而后者对 GIF 连体积都不影响。