
抠图结果在编辑器里看着已经透明保存下来却像一张白底图。这个现象不能只靠换一个文件后缀解决有时是查看器的白色底板有时是导出时合成了底色也有时所谓“透明棋盘格”早已变成图片里的普通像素。我开发的图片猫PicCatwww.piccat.cn有智能抠图和批量格式转换两个相关入口。这次沿着现有代码把预览、蒙版合成和文件编码串起来看重点讨论怎样判断透明是否还在以及它在哪一步可能丢失。1 先区分页面背景和图片像素在常见的 8 位 RGBA 图像里R、G、B 表示颜色A 表示不透明度。A 为 0 是全透明255 是完全不透明中间值用来表达半透明边缘。透明图放在白色页面上看起来是白底并不意味着文件里真的存了白色背景。图 1 来自 image-smart-cutout 案例目录。中间的棋盘格和右侧的深色底板是为讲解而添加的图中没有重新调用抠图模型也不是本次浏览器运行截图。切换底板后人物之外的区域跟着变色才能直观展示透明的作用。SmartCutout.vue 把 checkerboard-bg 放在包裹 canvas 的容器上renderComposite() 则先 clearRect再绘制主体。CSS 背景只负责让透明区域容易辨认Canvas 编码不会自动把容器的背景样式一起保存。但如果下载的是页面截图或者把棋盘格主动画进 Canvas它就成了真实像素。此时导出 PNG 也不会让这些格子自动消失。2 蒙版怎样变成透明的主体图层智能抠图组件取得接口结果后把它画进 maskCanvas读取 Alpha并计算非透明区域的包围盒。随后新建局部 objCanvas先绘制蒙版再用 source-in 绘入原图。这个合成模式只保留源图像与目标已有像素重叠的部分并受目标 Alpha 影响。项目代码摘录SmartCutout.vue 的主体合成部分。省略了包围盒计算、上下文判空及图层入栈变量来自上文处理流程不能单独运行。objCtx.drawImage(maskCanvas, minX, minY, width, height,0, 0, width, height);objCtx.globalCompositeOperation source-in;objCtx.drawImage(originalImage, minX, minY, width, height,0, 0, width, height);source-in 的完整规则可查阅MDN 合成模式文档。这里新建的是局部画布处理后的图层会再绘回输出画布。有一个实现细节不能略过。当前读取蒙版时组件实际用了下面的二值化逻辑中间省略的代码只计算包围盒。const alpha (data[i 3] ?? 0) 0 ? 255 : 0;// 此处省略包围盒坐标计算data[i] 255;data[i 1] 255;data[i 2] 255;data[i 3] alpha;也就是说假如接口返回 Alpha128 的边缘它在这里会被改成 255。PNG 能保存半透明不代表这条业务链路保留了接口的半透明信息。对于发丝、纱布或玻璃边缘直接把所有非零值当成完全不透明可能造成边缘变硬实际程度还取决于接口输出。建议改进方向是先确认接口契约结果到底是二值分割蒙版、软 Alpha 蒙版还是用灰度表示概率的图片。只有它确实以 Alpha 表达透明度时才适合保留原始 Alpha包围盒检测可以另用阈值避免低强度噪点把裁剪范围撑大。这是改进建议当前代码尚未采用。另一个待完善点是renderComposite() 会把选中虚线框画进同一个输出 Canvas而 downloadResult() 直接编码它。按当前绘制路径选中状态存在把编辑标记带进导出的风险本文没有做浏览器复现。建议把选择框放在独立覆盖层或导出时用干净画布只重绘主体图层。3 PNG 保留透明 JPEG 需要明确的底色保存时要先决定文件的用途。如果还要把主体放进其他海报应该保留透明如果目标系统只接收 JPG就要明确选择底色再把前景与背景合成。把 .jpg 改成 .png 只改了名字不会恢复已经丢失的 Alpha。输出格式透明能力当前项目的相关行为PNG支持透明和半透明智能抠图固定以 image/png 编码转换核心可输出 PNGWebP支持透明和半透明转换核心保留 RGBA当前未设置 losslessTrue不宜称为无损转换JPEG普通 JPEG 不保存 Alpha转换核心先按指定背景合成 RGB再编码批量转换组件会提交 targetFormat、quality 和 backgroundColor。服务端 image_convert_core.py 在 EXIF 方向校正后对 JPEG 单独处理 RGBA、LA 和调色板 P 模式先建 RGB 底图再用 Alpha 作为粘贴蒙版。项目代码摘录convert_image_data() 中的 JPEG 分支。省略了参数校验、其他格式分支和最终编码变量 background_rgb 已由背景色参数解析得到。if normalized_format JPEG and image.mode in (RGBA, LA, P):background Image.new(RGB, image.size, background_rgb)if image.mode ! RGBA:image image.convert(RGBA)background.paste(image, maskimage.getchannel(A))image backgroundelif normalized_format JPEG and image.mode ! RGB:image image.convert(RGB)这里不能简单用 convert(RGB) 代替合成。丢掉 Alpha 不等于按白底混合全透明像素内部也可能带着 RGB 值直接去掉透明度会把这些颜色露出来。可以用一个具体像素理解混合前景 RGB(200,100,50)Alpha128放到白底上后按通道计算“前景 × 128/255 白色 × 127/255”约为 (227,177,152)。JPEG 还会做有损编码因此验收颜色时应容许小幅误差。当前核心对 JPEG 和 WebP 使用质量参数PNG 走保存逻辑中的 optimizeTrue没有把同一个 quality 值当作 PNG 的画质旋钮。WebP 的颜色压缩与 Alpha 是否存在也要分开判断。格式与保存参数参考Pillow 官方文件格式文档。具体是否能解码或编码还取决于部署环境的 Pillow 及底层库支持。4 一个容易误判的案例素材检查项目案例库时可以找到两张外观都像“抠图完成”的 WebPmodel_after.webp 和 bonsai_after.webp。实际读取文件后前者为 RGBAAlpha 范围为 0–255后者为 RGB统一解码成 RGBA 后 Alpha 全部为 255。盆景图中的灰白格仍然可见深色底板没有透出来。它适合做效果展示却不能作为“下载结果包含透明通道”的验收样本。这个发现针对仓库里的这份展示文件不表示线上抠图接口一定返回同样的内容。教学简化代码用 Pillow 读取图片并检查有效 Alpha。替换本地文件路径即可用于单张静态图片省略了命令行、文件异常与动画逐帧检查。from PIL import Imagewith Image.open(result.webp) as image:rgba image.convert(RGBA)alpha rgba.getchannel(A)lo, hi alpha.getextrema()print(存在透明或半透明像素:, lo 255)print(Alpha 范围:, lo, hi)转成 RGBA 再检查比只看 mode 是否等于 RGBA 更稳妥调色板 PNG 也可能通过透明信息表达透明区域。反过来即使文件有 Alpha 通道也可能每个像素都是 255。这个检查只回答“有没有透明像素”不回答“抠得准不准”。背景是否漏抠、主体是否被误删需要另外对照原图。若原始输入完全不透明格式转换也不会推断主体边界更不会自动生成可信的透明蒙版。5 把编码成功和透明正确分开验收这次在本地 Pillow 12.3.0 环境中独立加载并运行了 test_image_convert_direct.py 的 ImageConvertCoreTest6 项核心测试均通过。覆盖了透明 PNG 转指定底色 JPEG、PNG 透明保留、格式组合、非法格式拒绝、质量范围和非法背景色回退没有执行接口集成测试。另外用同色的三个色块构造 Alpha 分别为 0、128、255 的 PNG调用项目 convert_image_data() 再解码结果得到下面的中心像素检查结果。色块用于验证编码链路不是模型效果评测。本地验证项实际观察PNG 输出三个色块的 Alpha 仍为 0、128、255WebP 输出Alpha 仍为 0、128、255RGB 出现有损压缩差异JPEG 输出并指定白底统一解码后 Alpha 均为 255半透明块中心 RGB 为 (227,177,152)上述结果只证明这组输入在本地转换核心中的行为不能替代浏览器保存、线上部署或移动端分享的验收。前面展示的两份案例素材也单独读取过文件模式和 Alpha 范围。建议继续验收的边界1. 下载后重新解码文件核对实际格式、像素尺寸和 Alpha分别放在白底与深色底板上查看边缘避免只在编辑器内看一眼。2. 智能抠图在“对象选中”和“取消选中”两种状态下分别保存检查虚线框是否进入结果。对软蒙版、细发丝和全透明结果另设样例。3. 当前 downloadResult() 已处理 toBlob 回调得到 null 的情况但导出异常和下载 Promise 失败的统一收尾仍值得补齐防止下载状态不能复位。跨域图像还要检查来源授权污染的 Canvas 不能直接导出。4. 若以后增加浏览器端 WebP 导出应检查返回 Blob 的 type并据此确定文件扩展名。浏览器不支持所请求编码格式时toBlob 可能回退到 PNG。服务端 WebP 成功不等于每个浏览器都支持 WebP 编码。后两项的 API 行为参考MDN toBlob、MDN 跨域图像与 Canvas。当前项目没有在这次核验中做跨浏览器端到端测试。遇到“抠图后白底”我会先查文件 Alpha再查合成步骤最后查编码和保存路径。沿着这条顺序定位才能分清是显示方式、透明数据被改写还是格式本身要求铺底也能避免把导出问题误判成模型能力问题。