视觉稿中的静态图片识别
2023-05-24
背景
网页上的静态图片从代码上来说,是如 <img src="xxx" />
这样的代码。因此在设计稿转代码时,需要知道设计稿里哪个地方是静态图片,同时将这个静态图片导出后生成对应的链接地址,这样生成的代码才能完整的运行起来看到页面效果。
我司设计师使用的设计工具是 MasterGo。和其他设计工具无异,通过各种类型的节点裁切组合来创造图形。组合方式五花八门,设计师的绘图方式也可以千奇百怪,所以从直观感受来说一个静态图片内部节点的组成情况是非常复杂的。所以这个问题本质上是给你一个设计稿页面节点数据,判断哪些节点组合起来是静态图片。
下面是 MasterGo 节点的类型和一个静态图片的节点组成的部分片段。
思考&过程
众多的组合方式和绘图方法,意味着输入不固定、可能性无穷多,这种不确定性很大的问题仅通过编码手段来解决是否不太可行性呢?答案是否定的。第一,这种不确定性只是自己的感觉而已,有待证明;第二,如果有个解法可以覆盖其中的绝大多数场景,那么这个解决方案也是非常够用的;第三,可以添加限制条件,使得输入减少,往确定性高的方向靠拢,解决频率最高的场景。所以解决这个问题应当先立下要求(限定),再进行归纳,再提出解法。
根据我的场景,我提出了下面两个条件或要求:
- 静态图片里不含有文本节点。因为文本一般要做国际化,一般由开发人员编码
- 由于方案不一定完全准确,为了避免误判造成出码不准确和导出太多的静态文件造成困扰,导出静态图片以「宁可少导出不要误导出」为优先级原则
接下来我翻看大量的设计稿,主要看其中静态图片的节点组合方式,我逐渐发现了规律。
节点组成
- 图片由众多节点组成,节点之间必然会分组聚合,这些同组节点的父节点一般是:GROUP(组节点)、BOOLEAN_OPERATION(一种逻辑节点)、FRAME(容器节点)。
- 在设计(绘制)图片时,只会使用 PEN_NODE(路径节点,即各种自由绘制的路径,如曲线)、基础图形(方形、圆形等)、BOOLEAN_OPERATION、其他绘图辅助节点
图形类型
简单图形
- 仅使用 PEN_NODE 或基础图形绘制的
- FRAME 或 RECTANGLE 等基础图形作为一个外框,实际的内容是使用一张图片填充进去的
复合图形
- 各种节点以 GROUP 等分组聚合,组和组之间嵌套
下面是一些实际的示例。
单个 GROUP 节点
根节点下只存在 PEN_NODE、基础图形、BOOLEAN_OPERATION 节点。
GROUP 嵌套 GROUP
GROUP 下含有文本节点和 GROUP 节点
因为已经限定视觉切图是不含有文字的(国际化需要),所以这种情况,最外层的 GROUP 应该被取消认定为静态图片,内层 GROUP 取代认定。
GROUP 节点嵌套,共同组合为一个图片
多个 GROUP 组合为一张图,每个 GROUP 下只含有 PEN_NODE、基础图形、BOOLEAN_OPERATION 节点。
GROUP 节点嵌套,含有多张图形
GROUP 下有多个 GROUP 嵌套,每个 GROUP 各为一张图。
解决方案
通过归纳,可以判定一个静态图片从节点的组成上是有共性的:
- 以 GROUP、BOOLEAN_OPERATION、FRAME 作为父(根)节点
- 子节点为 PEN_NODE、基础图形、BOOLEAN_OPERATION、GROUP 等节点
从数据结构来说,整个设计稿可以抽象为一颗多叉树,所以判断设计稿中的静态图片,本质上可以抽象为从一颗树中寻找满足条件的所有子树。
const findStaticImgNode = node => {
if (node.isVisible === false) return [];
if (groupPossibleStaticImg(node)) {
return [{
id: node.id,
name: node.name,
}];
}
let collections = [];
for (let childNode of (node.children || [])) {
if (!['GROUP', 'BOOLEAN_OPERATION', 'FRAME'].includes(childNode.type)) continue;
const res = findStaticImgNode(childNode);
res.length && collections.push(...res);
}
return collections;
};
const groupPossibleStaticImg = node => {
if (!imgNodeAllowType(node.type)) return false;
if (['GROUP', 'BOOLEAN_OPERATION', 'PEN', '各种基础图形'].includes(node.type)) return false;
return (node.children || []).reduce((valid, curNode) => {
return valid && groupPossibleStaticImg(curNode);
}, true);
};