图片能显示,画进canvas却空白:像素面积上限

课设要做一个在网页里裁图的小工具。思路很普通:用户选了图就先塞进 <img> 显示出来再按原尺寸画进一张 canvas 裁完导出。我拿一张 20000×20000 的大图去试。<img> 显示得好好的,naturalWidth 也是 20000。可画进 canvas 再导出拿到的是空白。当时我脑子里的逻辑是「浏览器都把图解出来了,画一下总该没问题」。这个假设是错的。解码能解多大和画布能画多大是两回事,两条线在不同的地方断。

大图能显示为什么画进canvas是空白

为了看清到底在哪断,我用脚本造了 10 张大图。从 1 亿像素到 6.25 亿像素全是渐变加色块加噪声的合成图。每张图我跑了三条路:<img> 画成 256×256 的缩略图;createImageBitmap 画成缩略图;按原尺寸画进画布再 toBlob。内核用的是三个构建版本:Chromium 149 开源构建、WebKit 26.5 构建和 Firefox 151 构建。它们分别不等于我们平时装的 Chrome、Safari 和正式版 Firefox。机器是一台 M4 Mac。1 亿像素那一档我另外拿图映 ImgIng(imging.cn/)的压缩转换对照过。1... 的 JPEG 转 WebP 和 JPG 在三个内核上都走完了,全程非 GET 请求 0 次。

表里最该看的是 16384×16384 和 16384×16400 这相邻两行。前者三条路三个内核全部成功。后者只多了 16 行像素。Chromium 和 WebKit 构建的第三列就成了空白,toBlob 回调 null。我当时有点懵。原因是这两个内核的画布面积上限都是 268435456 像素。16384×16384 刚好卡在线上,16384×16385 就过线了。20000×20000 的 4 亿像素离这条线更远。可缩略图那两列在这几行都是绿的。<img> 的解码显然不归这条线管。

这张是表里 16384×16400 那一行。我把它放进自己写的复现小页面,在 WebKit 26.5 构建里点了一遍。左边预览区图是完整的,下面标着 16384 × 16400、25.3 MB。中间缩到 380×380 的那张画布上图也一点没少。右边点 Export JPEG 按原尺寸导出后红框里只剩一个带问号的破图图标。下面那行写着 export.jpg 只有 4 bytes,这是回调拿到的 null 被包成了文件。这张图的面积是 268,697,600 px,只比 2^28 多出 16 行。

img和canvas的上限不是同一条线

Chromium 149 里 <img> 能解到 23170×23170,23171×23171 才触发 onerror,界线落在 2^29 像素附近。createImageBitmap 早一点,23170×23170 就 reject 了。画布在 2^28 就停了;中间差了整整一倍。我的裁图工具正好掉在这个空档里。

Firefox 151 构建是反过来的:它的画布能画到 23168×23168。20000×20000 原尺寸画进去能成功导出。到了 23170×23170 它的 <img> 照样触发 load 并给出真实的 naturalWidth,画出来却全是透明的。只看 load 事件会以为一切正常。WebKit 26.5 构建又是另一种样子:它的 <img> 一直到 25000×25000 都正常。createImageBitmap(blob) 在 23170×23170 以上会返回一个尺寸正确的位图。它不报错而内容全透明。三家在同一个地方能差这么多,是我做之前完全没想到的。我是一格一格对着表看的。

这张截的是同一个复现小页面在 Firefox 151 构建里的那一格。左边预览区能看到图,尺寸读出来是 23170 × 23170、13.4 MB,load 照常触发了。中间把同一张图缩到 380×380 画进画布。红框里只有棋盘格而一个像素都没画上。drawImage 并没有抛错。右边原尺寸导出失败了。页面上直接写着 Export failed。控制台是空的。

大图裁剪怎么避开画布上限

想通以后改法就不难了。缩略图那条路一直是绿的。问题不在「图太大」,在「目标画布太大」。我现在先按原图尺寸算一遍面积和单边。超过 Chromium 与 WebKit 构建测到的面积就不按原尺寸画:预览用缩小后的画布;真正导出时只按用户框出来的那一块去画。裁剪区一般远小于整张图,画布也就不会过线。整张图非要原尺寸处理的时候我能想到的只有分块。这个我还没写过,也不知道拼回去会不会有接缝。

表里 70000×1500 那张 PNG 只有 1.05 亿像素。面积远没过线而 Chromium 构建照样空白。这跟它 65535 px 的单边上限对得上。WebKit 构建能把它画上去,中心像素也对,只是导出 JPEG 时回调 null。它在我另一组测试里能写出 65535 px 宽的 JPEG,70000 是不是正好超了它的线我没单独测。

如果你的页面也是「先显示再画」,可以拿一张 16384×16400 的图试一次。先看 <img> 显示正不正常。再看画进原尺寸画布后 toBlob 回调是不是 null。两个结果放在一起就知道代码有没有掉进这个空档。

相关推荐
尤水就下几秒前
🔥说一说在线文档的 Block 架构是如何实现的
前端·javascript·架构
是江迪呀29 分钟前
介绍一个开源项目 —— 烂笔头
前端·后端
恋猫de小郭34 分钟前
Flutter 紧急发布 3.47.8 安全升级版本,建议都升级
android·前端·flutter
紧跟先前的步伐36 分钟前
Go AI Agent 实战 18|Tool Calling 的基本原理与完整执行流程
前端·数据库·人工智能·ai·golang·1024程序员节
captain37640 分钟前
Spring Web MVC(2)
java·前端·spring boot·后端·spring·mvc
晓得迷路了1 小时前
栗子前端技术周刊第 151 期 - ViteConf 2026、React Router 8.5、React Native 0.88...
前端·javascript·vue.js
ai工具集合1 小时前
老项目位图图标放大模糊?PNG 一键转 SVG 矢量化实操,附前端避坑指南
前端
2601_968708031 小时前
DreamsChat Web – Chat, Audio, Video Web APP with Admin Dashboard
前端·架构
小大宇1 小时前
Vue知识点8-Watch
前端·javascript·vue.js
垆边人似月.1 小时前
选型与快速跑通(Tesseract / EasyOCR / PaddleOCR)
前端·python