跳到主要内容
ConvertOwl
技术阅读需 1 分钟作者 ConvertOwl

把 SVG 转成 PNG 时,究竟发生了什么

三件事决定你的 PNG:尺寸如何算出、字体为何消失,以及文件里最先被剥掉的是什么。

像素风场景:一幅矢量图正被转换成像素网格

把 SVG 转成 PNG,听上去像是一步显而易见的操作。并不是。因为 SVG 里根本没有像素,它装的是绘图指令。你开口要一张 PNG 的那一刻,就同时提出了三个文件未必回答得了的问题:多大、用什么字体、允许什么东西运行。

每个转换器都会回答这三个问题,只是多数不会告诉你它是怎么回答的。以下是实际发生的事,在当前版本的 Chrome、Firefox 和 Safari 上实测。

问题一:一个 SVG 到底多大?#

去问浏览器 SVG 有多大,它会非常笃定地给你一个错误的数字。

我们在 Chromium 149、Firefox 151 和 WebKit 26.5 上用三个文件做了测试:

SVG 声明了什么浏览器报告什么
width="120" height="60"120 x 60
viewBox="0 0 200 50",没有 width 和 height300 x 75
什么都没有300 x 150

三个引擎的答案一致,而问题出在第二行和第三行。300 x 150 是 CSS 给没有固有尺寸的元素留下的默认值,和一个加载失败的 <img> 拿到的默认值是同一个。当存在 viewBox 时,浏览器保留那 300 像素的宽度,再按宽高比算出高度。

于是,设计师明明按 200 x 50 画的图标,导出来变成 300 x 75 的 PNG,而且没有任何地方告诉你:你作品的真实尺寸被一个默认值顶替了。

真正管用的规则#

读文件,不要读 DOM:

  1. 如果 widthheight 都以绝对单位给出(无单位、pxptmmin),那就是真实尺寸。按 96 dpi 换算,即 1in 等于 96 像素。
  2. 否则,如果有 viewBox,就取它的宽和高。 这是作者自己的坐标系。这也是最要紧的一种情况,因为 viewBox 配上 width="100%" height="100%",正是 Illustrator 和 Figma 的默认输出。
  3. 否则,文件里确实没有尺寸。 总得选一个值,而诚实的做法是把这件事说出来,而不是悄悄编一个 300 x 150。

我们的 SVG 转 PNG 工具会为你放入的每个文件说明属于这三种情况中的哪一种,并在第三种情况下回退到 512 x 512,而不是浏览器那个非正方形的默认值。

问题二:我的字体去哪了?#

SVG 里的文字不是文字的图片。它是一串字符,外加一个字体名称,而那个字体被假定存在于别处。一个写着 font-family="Brandon Grotesque" 的 SVG,里面没有 Brandon Grotesque 的任何部分。

你可能以为在 SVG 内部写一条 @font-face 规则就能解决。并不能。我们在三个浏览器里测试了一个引用远程网络字体的 SVG,并监视网络请求:每个引擎都是零次字体请求。文字照样画了出来,用的是一个宽度不同的后备字体。

这就是"为什么转换之后我的文字看起来不对"的全部机制。只有两种文字能原样存活:

  • 安装在执行转换那台机器上的字体。 也就是说,同一个 PNG 在你的笔记本和同事的笔记本上可能不一样。
  • 已经转成轮廓的文字。 在 Illustrator 里是"文字 > 创建轮廓",在 Figma 里是 Flatten。当每个字母都变成路径,就不存在缺失字体这回事了。

如果文字重要,导出前先把它转成轮廓。这是唯一可靠的答案,而且值得知道:这是格式本身的性质,不是某个转换器的缺陷。

问题三:什么被允许运行?#

SVG 不是 PNG 那种意义上的图像格式。它是一份 XML 文档,而 XML 文档可以包含脚本。一个 SVG 可以携带 <script> 标签、onload 处理器、指向远程图片的引用,以及拉取外部样式表的 @import 规则。

这正是某些内容管理系统干脆拒绝上传 SVG 的原因。

好消息,同样是实测出来的:用 <img> 标签渲染 SVG,而不是把它注入页面,是一个真正的沙箱。面对一个同时带有内嵌 <script>、远程 <image href>xlink:href@import 和远程 @font-face 的文件,三个浏览器都没有执行任何脚本,也没有发出任何网络请求,从页面内部和外部双向确认。

所以,以这种方式渲染你 SVG 的转换器,无法被诱导去执行任何东西。我们的工具仍然会报告它发现了什么,因为被告知"你的徽标里有一个跟踪像素",比把它悄悄删掉更有用。

唯一仍然会被画出来的是 data: URI,也就是以文本形式直接嵌在 SVG 里的图片。这是有意为之:它自成一体,没有什么需要去获取。

这件事真正咬人的地方#

不在转换环节。它咬人的时候,是你拿 SVG 的源码内联粘贴进自己的页面时,那里以上保护一条都不适用。如果你正在这么做,无论是一套图标还是某个优化器的输出,文件都得先清洗。这就是我们的 SVG 优化工具每次运行都强制净化的原因,无论你有没有要求:它交还给你的是源码,而源码会进入 DOM。

这一切都不会改变的事#

有两样东西会原封不动地挺过转换,值得知道,免得你去找根本不存在的问题。

透明度。 PNG 带有完整的 alpha 通道,所以 SVG 没有绘制的地方保持真正透明,半透明填充也保留各自的不透明度。三个引擎中逐像素验证过。

任意尺寸下的锐利度。 这才是从矢量出发的真正优势。2x 导出不是把图放大一倍,而是从几何数据在更大尺寸上重新绘制,因此曲线在数学意义上保持锐利。这就是为什么按 2x 导出胜过事后放大 PNG,也是为什么从矢量绘制的 16 像素图标胜过从 512 像素缩下来的那一个。

最后这点可以量化。在一个边缘与像素网格对齐的标记上,直接以 16 x 16 绘制得到 141 个实心像素、零个半透明像素;同一份图稿从 512 像素的渲染缩下来,则得到 98 个实心和 64 个模糊像素。四成的墨色化成了糊状,这正是我们的 favicon 生成器逐个尺寸单独渲染、而不是缩放一张大图的原因。

一句话版本#

  • 尺寸:解析 width/height,没有就回退到 viewBox,永远不要相信浏览器报告的数字。
  • 字体:它们不会随文件一起走。文字重要就在导出前转成轮廓。
  • 脚本:转换过程中它们跑不起来,但你把 SVG 源码粘进页面的那一刻就可以。
  • 透明度与锐利度:两者都会保留。按你需要的尺寸导出,而不是事后再缩放。

试用这些工具

免费、私密、即时 - 一切都在你的浏览器中运行。