杂记 › 技术笔记 › BMP 进、PNG 出:12342 字节的位图变成 281 字节,文件名却还写着 .bmp

BMP 进、PNG 出:12342 字节的位图变成 281 字节,文件名却还写着 .bmp

等尺寸重导:源 12342 字节,回来 281 字节

「位图不压缩、所以体积大」这句我早想验一下,验法很笨:拿一张 64x64 的 BMP(12342 字节),宽和高都填回 64,等尺寸重导一次。结果下载回来只有 281 字节,开头 12 个字节是 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d——PNG 的魔数接 IHDR,文件名却写着 bmp_64_resized.bmp。复读那个 blob: 地址,type=image/png、size=281,与 resultBlob 逐字节相同,所以落盘的字节本身就是 PNG。

名字和字节由两处代码分别生成

模板 image-resize.html 第 227 行让 mimeType 继承输入的 file.type,第 228 行 if (mimeType === 'image/bmp') mimeType = 'image/png';,注释写着 Canvas 不支持 BMP 输出。名字归另一处:第 246 行 originalFile.name.replace(/\.([^.]+)$/, '_resized.$1') 只把原名后缀搬进 _resized.$1,没人去看容器换没换。于是调整BMP尺寸这条路上两处各写各的:跑完的 22 例导出一例 BM 头都没有,21 例 PNG、1 例 WebP,以 BMP 身份进来的 16 例下载名全以 .bmp 结尾。

边界要说清:预探测里请求 image/bmp 本来就会拿到 image/png,两种产物同为 1062 字节,删掉第 228 行也一样;但本篇的主证据是模板自己产物的前 12 字节与下载名。

解码很宽,闸门只看后缀

13 份结构完整的 BMP 样张都解出与头字段一致的宽高:1/4/8/16/24/32 bpp、BI_RLE8 的 5436、BI_RLE4 的 2428 字节、负 biHeight 的 top-down、行宽非 4 对齐的 63x64;被拒的只有 biSize=12 那份 3096 字节,另两例是 16 字节截断与 0 字节。24bpp 那份正好是 14+40+192*64=12342 字节,「不压缩」在 BI_RGB 这条上算得通。

闸门看的是后缀给的 file.type:同一份 BMP 改名 .png、.webp 都进得来并照样解出 BMP 像素,改名 .dat 与 .txt 则被弹「不支持的图片格式」;JPEG 字节挂 .bmp 名(9712 字节)会按 BMP 身份转成 116541 字节的 PNG,是源的 12 倍。只有后缀不是 .bmp 时三者才对得上——改名 .webp 那份真去请求 image/webp、产物 570 字节、下载名也带 .webp。站内那篇讲后缀和真实格式为什么会对不上说的是用户改名的通用情形,本篇这条是第 228 行与第 246 行自己造出来的不一致。

变小是换容器,不是缩放

等尺寸重导的 12 例产物 IHDR 一律是 bitDepth=8 colorType=6,调色板与 565 位域统统展开成 RGBA8 再压。我在那份 32bpp 样张的左上 16x16 显式写了 alpha=0,缩放后画布 alpha min=255 max=255,透明在解码阶段就掉了;对照一张 1x1 全透明 PNG,放大后 alpha min=0 max=0 活着出来。

结构源字节等尺寸产物字节
24bpp BI_RGB,64x6412342281
8bpp 调色板,64x645174577
16bpp BI_BITFIELDS(565)82581442
BI_RLE4,64x6424281953

同一张图换位深进来,出去的字节数几乎只跟着像素数走:16438 与 12342 两份都变成 529 字节。

4 bpp 87.6% RLE4 80.4% 1 bpp 60.5% 8 bpp 11.2% 24 bpp 2.28% JPEG 挂 .bmp 名 = 1199.8%

图 1:产物字节占源字节的比例

第 224 行的 drawImage 用模板从未设置的默认档 imageSmoothingQuality=low,目标尺寸不是 1:1 就要过一次重采样;糊到什么程度站内另有一篇讲截图缩小为什么发糊,本篇不接。

取整两套打架,质量滑块对 BMP 无效

宽高框走第 211 与 212 行的 parseInt,是截断:填 1.5 得 1、2.9 得 2、1e3 得 1 而不是 1000、0.9 得 0 并弹「请输入有效的宽高尺寸」。等比联动与百分比走 Math.round、0.5 向上,于是 120x80 那张(aspectRatio=1.5)宽填 100 得高 67,比例被取整成 1.4925;2000x100 那张宽填 9 算出高 0,被第 213 行拦下。锁 ON 只做等比联动,改高度框不会反推宽度。

质量那一行在 BMP 输入时是 display=none,第 229 行却照样把值传进 toBlob:0.1、0.5、1 三档产物都是 281 字节,同一根滑杆对 JPEG 源给出 2038 到 54687 字节的不同结果。上限只有两道:文件侧判字节数,20971521 才弹「图片大小不能超过 20MB」,等于 20971520 放行,而那两例样张是全零 Blob,刚好 20 MiB 的合法 BMP 能不能解开本机没测过;尺寸侧第 216 行只判单边,10000x10000 也放行。

失败全静默,还能带着上一张出货

模板只挂第 141 行的 onload,全文找不到 onerror:72 字节的坏 PNG、0 字节、16 字节截断、biSize=12 的 3096 字节,四例全是 toast=[],既不报错也不清空上一张。第 137 行又赶在解码之前写了 originalFile:先缩放出 134 字节的产物,再上传那个坏文件,点下载会得到成功语气的「下载已开始」+新图的文件名+旧图的 134 字节。

批量也没有:第 120 行与第 124 行只取 files 的第一个,选择框上没有 multiple;resetAll() 后宽高框与质量的残留值实测还在。站内文案 12 条主张里 [冲突] 4 条、[部分冲突] 3 条,能站住的只有一句:BMP 进、PNG 出,文件名仍带 .bmp。

常见问题

Q:下载回来的那个 .bmp 到底是什么?

是 PNG。前 12 字节是 PNG 魔数接 IHDR,blob.type=image/png,容器头还是 8 位 RGBA;名字里的 .bmp 只是第 246 行搬过来的后缀,没有任何一行代码去改它。

Q:能不能指定输出容器?

不能。全文没有格式选择器,MIME 只能由第 227 行继承输入、再由第 228 行改写,等尺寸重导也一样被重编码成 281 字节的 PNG;真要一个位图容器得换一条别的路径。

Q:文件一定会变小吗?

12 例都变小,幅度却差很大:24bpp 只剩 2.28%,本来紧凑的 RLE4 还有 80.4%;反方向是 JPEG 挂 .bmp 名涨到 116541 字节,1x1 全透明 PNG 等尺寸重导从 70 变 88 字节。

📎 相关文章

171! 才溢出,23! 就已经算错:阶乘页那句「0~1000 不会溢出」的实测边界 2026-10-27 一个汉字只算 5 bits:密码强度评估器的破解时间什么时候变成宇宙寿命级别 2026-10-27 文本↔ASCII 里的「ASCII」是 UTF-8:1 个汉字吐出 3 个数,无分隔档 100% 回不去 2026-10-27 它把余数为 0 的那行也打给你:GCD 过程展示页的步数、折叠顺序与 29 步斐波那契 2026-10-27 GIF 改完尺寸只剩第一帧:一个名叫 .gif 的 PNG 文件 2026-10-27