把抠好的透明 PNG 放到深色背景上以后边缘一圈发白。很多人搜「抠图 白边」就是因为这个。我这次想弄清的是另一半问题同一张 PNG 放进 canvas 2D 和放进 WebGL 以后白边会不会一样重。结论是不一样。WebGL 里有一种很常见的配法会让半透明边的亮度直接翻一倍。先交代文件从哪来抠图我用的是图映 ImgIng的快速档 ISNet INT8样本是两张网上找的白底图导出后我统一合成到黑底看边缘。模型约 42 MB下载一次后抠图都在浏览器里完成。导出时我开着 Network 面板看了一路非 GET 请求一直是 0。商品图能不能出内网在我们这边要先过合规。精修区有句原话「默认逐像素直接使用 AI 输出不改透明度、不改边缘颜色」。导出文件里的半透明边就是模型给的原样后面量到的偏差只能算在文件和合成头上。默认导出的边缘本身亮了约11小狗图出自 Wikimedia Commons作者 George Hodan许可 CC0。默认导出的 113,438 个半透明像素原色平均亮度是 160.1。主体贴边 3 px 那一圈的平均亮度是 108.6。接近白色亮度 200 以上的半透明像素有 39.9%。半透明边里混着原来白底的颜色这就是发白的根。我按正确公式把它合成到黑底以后边缘平均亮度是 75.4。按主体色估算应该在 64.3 左右多出来约 11 个亮度单位。我测了一次 WebP 导出做对照两种格式的半透明像素数完全相同亮度差也不超过 0.2。格式不背锅。这 11 是文件自带的前端合成写对了也还在让我真正警觉的其实是下一步。直通和预乘存的是两种颜色透明图的颜色有两种存法直通 alpha 把颜色原样存下来透明度单独放一个通道。预乘 alpha 存的是颜色乘过透明度以后的值。PNG 按直通存往底色上合成时要先乘一次 alpha。预乘过的不能再乘。canvas 2D 在内部替你管了这件事WebGL 则要你自己配两处。一处是纹理上传时要不要预乘的UNPACK_PREMULTIPLY_ALPHA_WEBGL这个标志。另一处是blendFunc的第一个参数它决定混合时源颜色乘什么。两处不配套就会偏。配错上传方式会让边缘亮一倍测试用的是小狗默认导出里一块 480×480 px 的边缘裁块半透明像素共 43,740 个。我在 Chromium 149 里测了 canvas 2D 和 WebGL 的四种组合分别合成到黑底再把像素读回来。按公式算这块边缘的平均亮度应该是 54.9。这张图主要看三栏的下沿和毛尖左边 canvas 2D 的毛发往黑底过渡得很自然。中间是 WebGL 不预乘上传却配了 ONE 混合的结果肚皮下方多出一条亮白带毛尖也在发亮。右边预乘上传又配了 SRC_ALPHA 的那一栏边缘整体发暗发薄像被削掉了一圈。数字和肉眼对得上canvas 2D 读回 54.9和公式的最大差是 0。WebGL 不预乘上传配 SRC_ALPHA 和预乘上传配 ONE 读回来也都是 54.9。和公式的平均差只有 0.2。不预乘上传配 ONE 读回 108.9。边缘亮了一倍。它少乘了一次 alpha白底残留被原样放了出来。预乘上传配 SRC_ALPHA 读回 48.8alpha 被乘了两次以后边缘就暗了下去。我又在 CPU 上按三条公式各算了一遍结果同样是 54.9、108.9 和 48.8。两种错误就是少乘一次和多乘一次中间没有别的因素掺进来。去除背景色边改的是透明度文件自带的那 11 能不能先去掉图映精修区有个默认关闭的「去除背景色边」开关旁注写着「可选 · 会改变 AI 原始边缘」。小狗开了以后黑底上的边缘亮度是 76.0估算应有 75.6。白边基本没了。代价是这张图有 76,236 个像素的 alpha 被改前腿和肚皮交界处还多了一块锯齿硬边。主体里有接近白色的部分时它会连那部分一起压成透明。我现在倾向先不开它。前端合成的配对先弄对开不开再按图决定。上线前我先核对混合配置用 canvas 2D 合成的这一层基本不用担心。2D 画布读回的原色误差是 0alpha 小于 32 的像素也一样。用 WebGL 的先查上传标志和 blendFunc 是不是同一套。上传时没预乘混合就用 SRC_ALPHA上传时预乘了混合就用 ONE。我觉得最省事的核对办法是拿同一张透明图在 canvas 2D 上画一次当参照再和 WebGL 读回的边缘对照差得远就是配错了。这次只测了 Chromium 149WebGL 可能走的是软件渲染。Firefox 和 Safari 我还没测过。这块先留着。