Skip to main content
Glama
1yhy
by 1yhy
icon-detection.md67.9 kB
# 设计稿转代码 - 图片/图层识别算法研究报告 ## 一、背景与问题 ### 1.1 核心问题:碎片化图层 (Fragmented Layers) 在设计稿转代码的过程中,最常见的问题是 **碎片化图层**: - 设计师创建一个图标时,会使用多个独立图层(矩形、圆形、路径等)组合 - 直接按图层生成代码会产生冗余的 DOM 结构 - 影响代码的可维护性、可读性和性能 **示例:一个简单的搜索图标** ``` 设计稿结构(碎片化): ├── Group "搜索图标" │ ├── Ellipse (圆圈) │ ├── Line (手柄) │ └── Rectangle (装饰) 错误的代码输出: <div class="search-icon"> <div class="ellipse"></div> <div class="line"></div> <div class="rect"></div> </div> 正确的代码输出: <img src="search-icon.svg" alt="搜索" /> ``` ### 1.2 问题分类 | 问题类型 | 描述 | 检测难度 | | ---------------- | ------------------------ | -------- | | 图层未合并 | 多个图层应合并为单一图片 | 中等 | | 图层交叉重叠 | 图层位置有重叠 | 困难 | | 复杂背景 | 背景与前景混合 | 困难 | | Icon vs 布局元素 | 区分图标和页面结构元素 | 中等 | --- ## 二、业界解决方案 ### 2.1 阿里 Imgcook **官方链接**: https://www.imgcook.com/ **技术架构**: ``` 设计稿输入 → CV分析 + NLP分析 → 智能识别 → 可视化干预 → 代码生成 ``` **图层处理层次**: 1. **图层处理层**: 分离设计稿中的图层,整理元信息 2. **图层再加工层**: 规范化处理 3. **布局算法层**: 绝对定位 → Flex 布局 4. **语义化层**: 多维特征语义化表达 **Icon 识别方案**: - 使用 **ResNet** 深度学习模型 - 训练集覆盖 105 种 icon 分类 - 模型测试集准确度: **83%** - 支持图片聚类推测语义 **合并策略**: - Sketch 插件提供 `merge` 功能 - 自动检测 + 人工可视化干预 **参考**: - https://www.alibabacloud.com/blog/imgcook-intelligent-code-generation-from-design-drafts-with-a-100%25-accuracy-rate_598093 ### 2.2 美团 UI2DSL **官方链接**: https://tech.meituan.com/2021/03/25/ui2dsl-dsl2code.html **核心理念**: 事前干预 > 事后修复 ``` Imgcook: 自动处理 → 出错后可视化干预 (更智能,出错概率更大) 美团: 生成前可视化干预 → 更稳定,效果更好 ``` **图层问题处理**: | 问题 | 解决方案 | | ---------- | ---------------------------- | | 图层未合并 | 提供工具快速合并删除冗余图层 | | 图层交叉 | 检测工具 + 快速修复 | | 复杂背景 | 边干预边生成 | **布局生成算法**: 1. 将设计稿区域视为子区域 2. 基于图层位置/大小计算边缘坐标关系 3. 寻找切割点,递归切割 4. 横向切割 → 列布局,纵向切割 → 行布局 5. 直到所有子区域只剩不可切割的图层 ### 2.3 UILM (浙江大学) **论文**: UI Layers Merger: Merging UI Layers via Visual Learning and Boundary Prior **GitHub**: https://github.com/zju-d3/UILM **两阶段算法**: ``` 阶段1: MAD (Merging Area Detector) - 基于目标检测的合并区域检测 - 融入边界先验知识提升检测精度 阶段2: Layer Merging Algorithm - 基于检测框的规则合并 - 将框内图层合并为单一组件 ``` **局限性**: - 假设边界框内所有图层都属于同一组 - 视觉重叠时可能误判背景图层 ### 2.4 EGFE (ICSE 2024) **论文**: EGFE: End-to-end Grouping of Fragmented Elements in UI Designs with Multimodal Learning **GitHub**: https://github.com/test2975/EGFE **创新点**: - 端到端学习,避免两阶段误差累积 - Transformer 编码器建模 UI 元素关系 - 多模态表示学习 (视觉 + 结构信息) **性能提升** (相比 UILM): - Precision: +29.75% - Recall: +31.07% - F1-score: +30.39% --- ## 三、Figma 生态最佳实践 ### 3.1 Icon 处理标准流程 ``` 1. 识别: 检测 Group/Frame 是否为 Icon 单元 2. 展平: Flatten 所有子图层为单一 Vector 3. 清理: 移除空图层、优化路径 4. 导出: SVG (路径化,无 stroke) 或 PNG ``` ### 3.2 推荐 Figma 插件 | 插件 | 功能 | 链接 | | ---------------------- | ---------------------- | ---------------------------------------------- | | Flatten for Icon | 一键展平,移除空图层 | figma.com/community/plugin/1466407474947737326 | | Flatten Wizard | 合并重叠路径,简化结构 | figma.com/community/plugin/1513933733767623504 | | IconLab | 清理优化 icon | figma.com/community/plugin/1470150361236399232 | | Really Flatten Vectors | 深度展平为最少路径 | figma.com/community/plugin/1099596352042014853 | ### 3.3 SVG 导出标准 ``` ✅ 正确的 SVG: - 所有形状展平为 <path> 元素 - 无 stroke,只有 fill - 无 clip-path 或复杂 fill-rule - 路径已优化合并 ❌ 错误的 SVG: - 保留原始 <rect>, <ellipse> 等元素 - 包含 stroke 属性 - 多余的嵌套 <g> 组 ``` --- ## 四、算法设计方案 ### 4.1 检测条件 基于研究,Icon/可合并图层组应满足以下条件: ```typescript interface IconDetectionCriteria { // 1. 容器类型 containerTypes: ["GROUP", "FRAME"]; // 2. 尺寸约束 (典型 icon 尺寸范围) sizeConstraints: { maxWidth: 200; // px maxHeight: 200; // px minWidth: 8; // 过小可能是装饰点 minHeight: 8; }; // 3. 子元素类型分析 childAnalysis: { // 可合并的图形类型 mergeableTypes: [ "VECTOR", "RECTANGLE", "ELLIPSE", "LINE", "POLYGON", "STAR", "BOOLEAN_OPERATION", "REGULAR_POLYGON", ]; // 排除类型 (有这些则不合并) excludeTypes: ["TEXT", "INSTANCE", "COMPONENT"]; // 可合并类型占比阈值 mergeableRatioThreshold: 0.7; // 70% }; // 4. 结构约束 structureConstraints: { maxDepth: 4; // 最大嵌套深度 minChildren: 1; // 最少子元素 maxChildren: 50; // 最多子元素 (过多可能是布局容器) }; // 5. 语义特征 (可选) semanticFeatures: { // 名称包含 icon 相关关键词 iconKeywords: ["icon", "logo", "symbol", "图标", "箭头", "arrow"]; // 典型 icon 宽高比范围 aspectRatioRange: [0.5, 2.0]; }; } ``` ### 4.2 合并决策流程 ``` 输入: Figma 节点树 对每个节点执行: │ ├─ 是容器节点 (GROUP/FRAME)? │ ├─ 否 → 检查是否为独立 SVG 元素 → 标记 exportInfo │ └─ 是 → 继续 │ ├─ 尺寸在 Icon 范围内? │ ├─ 否 → 递归处理子节点 │ └─ 是 → 继续 │ ├─ 包含 TEXT/INSTANCE 子元素? │ ├─ 是 → 递归处理子节点 (不合并) │ └─ 否 → 继续 │ ├─ 可合并类型占比 >= 70%? │ ├─ 否 → 递归处理子节点 │ └─ 是 → 继续 │ ├─ 嵌套深度 <= 4? │ ├─ 否 → 递归处理子节点 │ └─ 是 → ✅ 标记为 Icon,整体导出 │ 输出: 标记了 exportInfo 的节点树 ``` ### 4.3 导出格式选择 ```typescript function selectExportFormat(node: IconNode): "SVG" | "PNG" { // 1. 纯矢量图形 → SVG if (isAllVectorChildren(node)) { return "SVG"; } // 2. 包含图片填充 → PNG if (hasImageFill(node)) { return "PNG"; } // 3. 复杂效果 (阴影、模糊) → PNG if (hasComplexEffects(node)) { return "PNG"; } // 4. 默认 SVG return "SVG"; } ``` --- ## 五、完整实现链路分析 ### 5.1 算法架构总览 ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ Icon Detection Algorithm │ │ src/algorithms/icon/detector.ts │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ │ │ │ Figma Node Tree │ │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │ │ analyzeNodeTree() [入口函数] │ │ │ │ 协调整个检测流程,返回处理结果和统计摘要 │ │ │ └────────────────────────────────────┬───────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────┴───────────────────────────┐ │ │ ▼ ▼ │ │ ┌─────────────────────┐ ┌────────────────────┐ │ │ │ processNodeTree() │ │collectExportable │ │ │ │ 自底向上递归处理 │─────── 处理完成后 ──────────▶│ Icons() │ │ │ │ 标记 _iconDetection│ │ 收集可导出图标列表 │ │ │ └──────────┬──────────┘ └────────────────────┘ │ │ │ │ │ │ 对每个节点调用 │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │ │ detectIcon() [核心检测] │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │ │ │ │ 9步检测规则 │───▶│collectNode │───▶│ IconDetectionResult │ │ │ │ │ │ (详见下图) │ │ Stats() │ │ {shouldMerge, format} │ │ │ │ │ └─────────────┘ │ O(n)单次遍历│ └─────────────────────────┘ │ │ │ │ └─────────────┘ │ │ │ └────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 5.2 detectIcon() 九步检测流程 ``` ┌─────────────────────┐ │ detectIcon() │ │ 输入: FigmaNode │ └──────────┬──────────┘ │ ┌────────────────────┴────────────────────┐ ▼ │ ┌───────────────────────┐ │ Step 1 │ exportSettings 检查 │ │ │ 设计师是否标记导出? │ │ └───────────┬───────────┘ │ │ │ ┌────────YES────────┐ │ ▼ │ │ ┌───────────────────┐ │ │ │ 尺寸 ≤ 400px? │ │ │ │ 不含 TEXT? │ │ │ └─────────┬─────────┘ │ │ │ │ │ YES────┴────NO │ │ │ │ │ │ ▼ │ │ │ ┌────────┐ │ │ │ │✅ 合并 │ └────────────┼───────────────┐ │ │按设计师│ │ │ │ │设置导出│ │ │ │ └────────┘ │ │ │ ▼ ▼ │ ┌───────────────────────────────┐ │ Step 2 │ 是容器类型或可合并单元素? │ │ │ CONTAINER: GROUP/FRAME/... │ │ │ MERGEABLE: VECTOR/ELLIPSE/... │ │ └───────────────┬───────────────┘ │ │ │ ┌─────────NO─────────┐ │ ▼ │ │ ┌─────────┐ │ │ │❌ 不合并 │ │ │ │非目标类型│ │ │ └─────────┘ │ │ ▼ │ ┌─────────────────────────┐ │ │ 是单元素 (VECTOR等)? │ │ └───────────┬─────────────┘ │ │ │ YES────────┴────────NO │ │ │ │ ┌───────────────┘ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ 排除单独 RECTANGLE │ │ │ │ (通常是背景) │ │ │ │ 检查尺寸 ≤ 300px │ │ │ └──────────┬──────────┘ │ │ │ │ │ PASS───┴───FAIL │ │ │ │ │ │ ▼ ▼ │ │ ┌────────┐ ┌─────────┐ │ │ │✅ 合并 │ │❌ 不合并 │ │ │ │单矢量 │ │背景/过大│ │ │ └────────┘ └─────────┘ │ │ │ │ ┌─────────────────────────┘ │ ▼ │ ┌───────────────────────────┐ │ Step 3 │ 尺寸检查 (容器节点) │ │ │ 8px ≤ size ≤ 300px │ │ └─────────────┬─────────────┘ │ │ │ PASS──────┴──────FAIL │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │ ❌ 不合并 │ │ │ │ 过大/过小 │ │ │ └─────────────┘ │ ▼ │ ╔═══════════════════════════════════════════════════════╗ │ ║ collectNodeStats() 单次遍历 ║ │ ║ ┌────────────────────────────────────────────┐ ║ │ ║ │ O(n) 复杂度收集以下 7 项统计数据: │ ║ │ ║ │ • depth - 树深度 │ ║ │ ║ │ • totalChildren - 总子节点数 │ ║ │ ║ │ • hasExcludeType - 含排除类型 (TEXT等) │ ║ │ ║ │ • hasImageFill - 含图片填充 │ ║ │ ║ │ • hasComplexEffects - 含复杂效果 │ ║ │ ║ │ • allLeavesMergeable - 叶节点全可合并 │ ║ │ ║ │ • mergeableRatio - 可合并类型占比 │ ║ │ ║ └────────────────────────────────────────────┘ ║ │ ╚═══════════════════════════════╤═══════════════════════╝ │ │ │ ▼ │ ┌───────────────────────────────────┐ │ Step 4 │ 排除类型检查 │ │ │ hasExcludeType? (TEXT/COMPONENT) │ │ └─────────────────┬─────────────────┘ │ │ │ YES───────┴───────NO │ │ │ │ ▼ │ │ ┌─────────┐ │ │ │❌ 不合并 │ │ │ │含文本等 │ │ │ └─────────┘ │ │ ▼ │ ┌───────────────────────────────────┐ │ Step 5 │ 深度检查 │ │ │ depth ≤ 5? │ │ └─────────────────┬─────────────────┘ │ │ │ NO────────┴────────YES │ │ │ │ ▼ │ │ ┌─────────┐ │ │ │❌ 不合并 │ │ │ │嵌套过深 │ │ │ └─────────┘ │ │ ▼ │ ┌───────────────────────────────────┐ │ Step 6 │ 子节点数量检查 │ │ │ totalChildren ≤ 100? │ │ └─────────────────┬─────────────────┘ │ │ │ NO────────┴────────YES │ │ │ │ ▼ │ │ ┌─────────┐ │ │ │❌ 不合并 │ │ │ │子节点太多│ │ │ └─────────┘ │ │ ▼ │ ┌───────────────────────────────────┐ │ Step 7 │ 可合并类型占比检查 │ │ │ mergeableRatio ≥ 60%? │ │ └─────────────────┬─────────────────┘ │ │ │ NO────────┴────────YES │ │ │ │ ▼ │ │ ┌─────────┐ │ │ │❌ 不合并 │ │ │ │占比过低 │ │ │ └─────────┘ │ │ ▼ │ ┌───────────────────────────────────┐ │ Step 8 │ 叶节点可合并性检查 │ │ │ allLeavesMergeable? │ │ └─────────────────┬─────────────────┘ │ │ │ NO────────┴────────YES │ │ │ │ ▼ │ │ ┌─────────┐ │ │ │❌ 不合并 │ │ │ │有不可合 │ │ │ │并的叶子 │ │ │ └─────────┘ │ │ ▼ │ ┌───────────────────────────────────┐ │ Step 9 │ 导出格式决策 │ │ │ 基于 hasImageFill/hasComplexEffects│ │ └─────────────────┬─────────────────┘ │ │ │ ┌────────────────────┼────────────────────┐ │ ▼ ▼ ▼ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐│ │ hasImageFill │ │hasComplex │ │ 纯矢量 ││ │ = true │ │Effects = true │ │ ││ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘│ │ │ │ │ ▼ ▼ ▼ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐│ │ ✅ shouldMerge │ │ ✅ shouldMerge │ │ ✅ shouldMerge ││ │ format: PNG │ │ format: PNG │ │ format: SVG ││ └───────────────┘ └───────────────┘ └───────────────┘│ │ └─────────────────────────────────────────────────────────────┘ ``` ### 5.3 自底向上处理流程 (Bottom-Up Processing) ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ processNodeTree() 自底向上处理 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 示例输入: │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ Frame "Card" │ │ │ │ ├── Frame "Header" │ │ │ │ │ ├── Group "Logo" ◄─── 可能是图标 │ │ │ │ │ │ ├── Vector (path1) │ │ │ │ │ │ └── Ellipse (circle) │ │ │ │ │ └── Text "Title" │ │ │ │ └── Frame "Content" │ │ │ │ └── Group "Icon-Set" │ │ │ │ ├── Group "Search" ◄─── 可能是图标 │ │ │ │ │ ├── Ellipse │ │ │ │ │ └── Line │ │ │ │ └── Group "Menu" ◄─── 可能是图标 │ │ │ │ ├── Line │ │ │ │ ├── Line │ │ │ │ └── Line │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ 处理顺序 (自底向上): │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 第 1 轮: 处理最深层叶节点 │ │ │ │ Vector, Ellipse, Text, Line... → 都是叶子,无 _iconDetection │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 第 2 轮: 处理第一层容器 │ │ │ │ │ │ │ │ Group "Logo" → detectIcon() → ✅ shouldMerge (纯矢量) │ │ │ │ Group "Search" → detectIcon() → ✅ shouldMerge (纯矢量) │ │ │ │ Group "Menu" → detectIcon() → ✅ shouldMerge (纯矢量) │ │ │ │ │ │ │ │ ⚠️ 标记后,子节点的 _iconDetection 被清除 (会被合并) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 第 3 轮: 处理上层容器 │ │ │ │ │ │ │ │ Frame "Header" │ │ │ │ → 子节点包含 Logo(icon) + Text │ │ │ │ → 不是所有子节点都是图标 │ │ │ │ → detectIcon() → ❌ 含 TEXT │ │ │ │ → Logo 保留 _iconDetection 标记 │ │ │ │ │ │ │ │ Group "Icon-Set" │ │ │ │ → 所有子节点 (Search, Menu) 都已标记为图标 │ │ │ │ → allChildrenAreIcons = true │ │ │ │ → detectIcon() → 检查是否可以提升合并 │ │ │ │ → 如果尺寸/深度允许 → ✅ 合并为单个图标 │ │ │ │ → 清除 Search, Menu 的 _iconDetection │ │ │ │ 或 │ │ │ │ → 如果不满足条件 → 保持子节点各自的 _iconDetection │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 最终结果: 收集可导出图标 │ │ │ │ │ │ │ │ collectExportableIcons() 遍历处理后的树: │ │ │ │ │ │ │ │ - 遇到有 _iconDetection 的节点 → 加入导出列表 │ │ │ │ - 不递归进入已标记节点的子树 (子节点会被合并) │ │ │ │ - 继续遍历未标记节点的子树 │ │ │ │ │ │ │ │ 输出: [Logo, Search, Menu] 或 [Logo, Icon-Set] │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 5.4 collectNodeStats() 单次遍历优化 ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ collectNodeStats() 性能优化对比 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 优化前: 6 个独立递归函数,O(6n) 复杂度 │ │ ┌───────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ calculateDepth() ─────▶ 遍历整棵树 ────▶ depth │ │ │ │ countTotalChildren() ─────▶ 遍历整棵树 ────▶ count │ │ │ │ hasExcludeTypeInTree() ─────▶ 遍历整棵树 ────▶ boolean │ │ │ │ hasImageFillInTree() ─────▶ 遍历整棵树 ────▶ boolean │ │ │ │ hasComplexEffectsInTree() ─────▶ 遍历整棵树 ────▶ boolean │ │ │ │ areAllLeavesMergeable() ─────▶ 遍历整棵树 ────▶ boolean │ │ │ │ │ │ │ │ 总遍历次数: 6n │ │ │ └───────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ ▼ │ │ │ │ 优化后: 单次遍历收集所有数据,O(n) 复杂度 │ │ ┌───────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ collectNodeStats(node) │ │ │ │ │ │ │ │ │ ┌───────────────┴───────────────┐ │ │ │ │ ▼ ▼ │ │ │ │ ┌─────────────┐ ┌─────────────────┐ │ │ │ │ │ 叶子节点 │ │ 容器节点 │ │ │ │ │ │ 无 children │ │ 有 children │ │ │ │ │ └──────┬──────┘ └────────┬────────┘ │ │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ │ 直接计算本节点: 递归处理子节点后聚合: │ │ │ │ • depth = 0 • depth = max(child.depth) + 1 │ │ │ │ • totalChildren = 0 • totalChildren = Σ(1 + child.total) │ │ │ │ • hasExcludeType • hasExcludeType = any(children) │ │ │ │ • hasImageFill • hasImageFill = any(children) │ │ │ │ • hasComplexEffects • hasComplexEffects = any(children) │ │ │ │ • allLeavesMergeable • allLeavesMergeable = all(children) │ │ │ │ • mergeableRatio • mergeableRatio = count/total │ │ │ │ │ │ │ │ 总遍历次数: n │ │ │ └───────────────────────────────────────────────────────────────────────┘ │ │ │ │ 性能提升: ~28% (64 → 82 nodes/ms) │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 5.5 类型常量与配置 ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ 类型分类常量 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ CONTAINER_TYPES (容器类型 - 可包含图标的父节点) │ │ │ │ ├── GROUP Figma 组 │ │ │ │ ├── FRAME Figma 画框 │ │ │ │ ├── COMPONENT 组件定义 │ │ │ │ └── INSTANCE 组件实例 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ MERGEABLE_TYPES (可合并类型 - 可作为图标元素) │ │ │ │ ├── VECTOR 矢量路径 │ │ │ │ ├── RECTANGLE 矩形 │ │ │ │ ├── ELLIPSE 椭圆/圆 │ │ │ │ ├── LINE 线条 │ │ │ │ ├── POLYGON 多边形 │ │ │ │ ├── STAR 星形 │ │ │ │ ├── BOOLEAN_OPERATION 布尔运算结果 │ │ │ │ └── REGULAR_POLYGON 正多边形 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ EXCLUDE_TYPES (排除类型 - 出现则不合并) │ │ │ │ ├── TEXT 文本元素 (需独立渲染) │ │ │ │ ├── COMPONENT 组件定义 (有独立逻辑) │ │ │ │ └── INSTANCE 组件实例 (可能有交互) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ PNG_REQUIRED_EFFECTS (需要 PNG 格式的效果) │ │ │ │ ├── DROP_SHADOW 外阴影 │ │ │ │ ├── INNER_SHADOW 内阴影 │ │ │ │ ├── LAYER_BLUR 图层模糊 │ │ │ │ └── BACKGROUND_BLUR 背景模糊 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ ├─────────────────────────────────────────────────────────────────────────────┤ │ 默认配置参数 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ DEFAULT_CONFIG = { │ │ maxIconSize: 300, // 最大图标尺寸 (px) │ │ minIconSize: 8, // 最小图标尺寸 (px) │ │ mergeableRatio: 0.6, // 可合并类型最低占比 (60%) │ │ maxDepth: 5, // 最大嵌套深度 │ │ maxChildren: 100, // 最大子节点数 │ │ respectExportSettingsMaxSize: 400 // 尊重设计师导出设置的最大尺寸 │ │ } │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 5.6 导出格式决策树 ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ 导出格式决策逻辑 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────┐ │ │ │ 确认需要导出的节点 │ │ │ │ shouldMerge=true │ │ │ └─────────┬─────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────────────┐ │ │ │ 设计师在 Figma 中设置了导出格式? │ │ │ │ node.exportSettings[0] │ │ │ └────────────────┬───────────────┘ │ │ │ │ │ YES───────────┴───────────NO │ │ │ │ │ │ ▼ │ │ │ ┌─────────────────────┐ │ │ │ │ 使用设计师指定格式 │ │ │ │ │ format = settings │ │ │ │ └─────────────────────┘ │ │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ 节点树中含有图片填充? │ │ │ │ hasImageFill = true │ │ │ └───────────┬────────────┘ │ │ │ │ │ YES──────────┴──────────NO │ │ │ │ │ │ ▼ ▼ │ │ ┌───────────────────┐ ┌─────────────────────────┐ │ │ │ format = "PNG" │ │ 节点树中含有复杂效果? │ │ │ │ │ │ hasComplexEffects=true │ │ │ │ 原因: │ └───────────┬─────────────┘ │ │ │ 图片无法矢量化 │ │ │ │ └───────────────────┘ YES────────┴────────NO │ │ │ │ │ │ ▼ ▼ │ │ ┌───────────────────┐ ┌───────────────────┐ │ │ │ format = "PNG" │ │ format = "SVG" │ │ │ │ │ │ │ │ │ │ 原因: │ │ 原因: │ │ │ │ 阴影/模糊效果 │ │ 纯矢量图形 │ │ │ │ SVG 无法完美渲染 │ │ 可无损缩放 │ │ │ └───────────────────┘ └───────────────────┘ │ │ │ │ ════════════════════════════════════════════════════════════════════════ │ │ │ │ PNG 优先场景: SVG 优先场景: │ │ ┌────────────────────────────┐ ┌────────────────────────────┐ │ │ │ • 包含位图填充 (照片等) │ │ • 纯矢量路径 │ │ │ │ • DROP_SHADOW 外阴影 │ │ • 简单形状组合 │ │ │ │ • INNER_SHADOW 内阴影 │ │ • 无复杂效果 │ │ │ │ • LAYER_BLUR 模糊效果 │ │ • 需要无损缩放 │ │ │ │ • BACKGROUND_BLUR 背景模糊 │ │ • 需要 CSS 动态着色 │ │ │ │ • 复杂渐变 + 效果组合 │ │ • 小文件体积要求 │ │ │ └────────────────────────────┘ └────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 5.7 完整调用链路 ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ 完整调用链路示意图 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 外部调用入口 (parser.ts / server.ts) │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐│ │ │ analyzeNodeTree(node) ││ │ │ ││ │ │ 参数: node: FigmaNode, config?: DetectionConfig ││ │ │ 返回: { processedTree, exportableIcons, summary } ││ │ └────────────────────────────────┬────────────────────────────────────────┘│ │ │ │ │ ┌───────────────────────┴───────────────────────┐ │ │ ▼ ▼ │ │ ┌────────────────────────┐ ┌─────────────────────────────┐│ │ │ processNodeTree() │ │ collectExportableIcons() ││ │ │ │ │ ││ │ │ 递归处理节点树: │ 处理完成后调用 │ 遍历已处理的树: ││ │ │ 1. 先处理子节点 │ ────────────────▶│ 1. 有标记 → 加入列表 ││ │ │ 2. 检查子节点是否都 │ │ 2. 无标记 → 递归子树 ││ │ │ 是图标 │ │ 3. 返回所有可导出图标 ││ │ │ 3. 尝试父节点提升合并 │ │ ││ │ │ 4. 标记 _iconDetection│ │ ││ │ └───────────┬────────────┘ └─────────────────────────────┘│ │ │ │ │ │ 对每个节点调用 │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐│ │ │ detectIcon(node) ││ │ │ ││ │ │ 核心检测函数,执行 9 步检测: ││ │ │ Step 1: exportSettings 检查 ││ │ │ Step 2: 类型检查 (容器/可合并) ││ │ │ Step 3: 尺寸检查 (8-300px) ││ │ │ Step 4-8: 使用 collectNodeStats() 检查树属性 ││ │ │ Step 9: 确定导出格式 (SVG/PNG) ││ │ └────────────────────────────────┬────────────────────────────────────────┘│ │ │ │ │ ┌────────────────────┴────────────────────┐ │ │ ▼ ▼ │ │ ┌─────────────────────────────┐ ┌────────────────────────────────┐ │ │ │ collectNodeStats(node) │ │ 辅助函数: │ │ │ │ │ │ │ │ │ │ 单次遍历收集 7 项统计: │ │ • isContainerType() │ │ │ │ O(n) 复杂度 │ │ • isMergeableType() │ │ │ │ │ │ • isExcludeType() │ │ │ │ 替代了原来 6 个递归函数 │ │ • hasImageFill() │ │ │ │ 性能提升 ~28% │ │ • hasComplexEffects() │ │ │ │ │ │ • getNodeSize() │ │ │ └─────────────────────────────┘ └────────────────────────────────┘ │ │ │ │ ════════════════════════════════════════════════════════════════════════ │ │ │ │ 返回结果结构: │ │ ┌─────────────────────────────────────────────────────────────────────────┐│ │ │ { ││ │ │ processedTree: FigmaNode & { _iconDetection?: IconDetectionResult }, ││ │ │ exportableIcons: IconDetectionResult[], ││ │ │ summary: { ││ │ │ totalIcons: number, // 可导出图标总数 ││ │ │ svgCount: number, // SVG 格式数量 ││ │ │ pngCount: number // PNG 格式数量 ││ │ │ } ││ │ │ } ││ │ └─────────────────────────────────────────────────────────────────────────┘│ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- ## 六、原实现计划 (归档) > 以下为早期设计阶段的计划,实际实现已在第五章详细描述。 <details> <summary>点击展开归档内容</summary> ### 第一阶段: 基础检测 1. 扩展 `SVG_NODE_TYPES` 包含 `RECTANGLE` 2. 实现 `shouldMergeAsIcon()` 检测函数 3. 修改 `processSVGNodesBottomUp()` 使用新检测逻辑 ### 第二阶段: 智能合并 1. 实现 `analyzeChildrenTypes()` 分析子元素类型分布 2. 实现 `calculateMergeScore()` 计算合并得分 3. 支持部分合并 (保留需要独立的子元素) ### 第三阶段: 双格式导出 1. 同时生成 SVG 和 PNG 导出信息 2. 优化导出文件命名 3. 添加导出统计信息 </details> --- ## 七、参考资料 1. **阿里 Imgcook** - https://www.imgcook.com/ - https://www.alibabacloud.com/blog/imgcook-intelligent-code-generation-from-design-drafts-with-a-100%25-accuracy-rate_598093 2. **美团 UI2DSL** - https://tech.meituan.com/2021/03/25/ui2dsl-dsl2code.html 3. **UILM (浙江大学)** - 论文: https://arxiv.org/abs/2206.13389 - 代码: https://github.com/zju-d3/UILM 4. **EGFE (ICSE 2024)** - 论文: https://arxiv.org/abs/2309.09867 - 代码: https://github.com/test2975/EGFE 5. **Figma 官方文档** - Flatten layers: https://help.figma.com/hc/en-us/articles/30101373312279-Flatten-layers - Export formats: https://help.figma.com/hc/en-us/articles/13402894554519-Export-formats-and-settings --- _文档创建时间: 2025-12-05_ _最后更新: 2025-12-06 (添加完整实现链路分析)_ _作者: Claude Code_

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/1yhy/Figma-Context-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server