feat: 男性发型追加波浪形(wave=7)

接口2/5 male hair_style 增至 1..7,贴图 man_wave.png 纳入四套纹理目录,同步测试页与接口文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
xsl
2026-08-06 21:35:28 +08:00
co-authored by Cursor
parent 688472706d
commit 5ba42bea67
10 changed files with 23 additions and 20 deletions
+9 -9
View File
@@ -726,15 +726,15 @@ async def face_measure_v2(
--- ---
- **gender**(必填):`male` / `female`。决定返回的贴图集合(female 7 张 / male 6 张)。 - **gender**(必填):`male` / `female`。决定返回的贴图集合(female 7 张 / male 7 张)。
非法或缺失返回 `1004`。 非法或缺失返回 `1004`。
- **hair_style**(必填):发型序号,**逗号分隔多选**(如 `1,2,3`),最多不超过该性别的预设数量。 - **hair_style**(必填):发型序号,**逗号分隔多选**(如 `1,2,3`),最多不超过该性别的预设数量。
`female`1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave, 6=bigflower, 7=clasicalflower `female`1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave, 6=bigflower, 7=clasicalflower
`male`1=ellipse, 2=inverse_arc, 3=m, 4=straight, 5=heart, 6=Softpetal。越界/非法返回 `1007`。 `male`1=ellipse, 2=inverse_arc, 3=m, 4=straight, 5=heart, 6=Softpetal, 7=wave。越界/非法返回 `1007`。
- **beauty_enabled**:本期保留但不生效。 - **beauty_enabled**:本期保留但不生效。
`hairline_type` 取值:`ellipse` / `flower` / `heart` / `straight` / `wave` / `bigflower` / `clasicalflower`female), `hairline_type` 取值:`ellipse` / `flower` / `heart` / `straight` / `wave` / `bigflower` / `clasicalflower`female),
`ellipse` / `m` / `straight` / `inverse_arc` / `heart` / `Softpetal`male)。 `ellipse` / `m` / `straight` / `inverse_arc` / `heart` / `Softpetal` / `wave`male)。
""", """,
responses={ responses={
200: { 200: {
@@ -773,7 +773,7 @@ async def hair_grow(
image_url: Optional[str] = Form(default=None, description="图片 URL"), image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"), image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"), gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填),如 1,2,3。female:1-7 male:1-6"), hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填),如 1,2,3。female:1-7 male:1-7"),
beauty_enabled: bool = Form(default=False, description="是否开启美颜(本期不生效)"), beauty_enabled: bool = Form(default=False, description="是否开启美颜(本期不生效)"),
use_mask: bool = Form(default=True, description="是否启用 inpaint 遮罩(测试对比用)。false 时用干净原图生成(空遮罩,不烧模板线)"), use_mask: bool = Form(default=True, description="是否启用 inpaint 遮罩(测试对比用)。false 时用干净原图生成(空遮罩,不烧模板线)"),
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词,会替换工作流节点60的文本"), prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词,会替换工作流节点60的文本"),
@@ -783,7 +783,7 @@ async def hair_grow(
return err(1004, "gender 必填且只能为 male / female") return err(1004, "gender 必填且只能为 male / female")
# 2. hair_style 必填校验(解析逗号分隔,越界 → 1007) # 2. hair_style 必填校验(解析逗号分隔,越界 → 1007)
max_styles = {"female": 7, "male": 6}[gender] max_styles = {"female": 7, "male": 7}[gender]
hair_styles = _parse_hair_styles(hair_style, max_styles) hair_styles = _parse_hair_styles(hair_style, max_styles)
if hair_styles is None: if hair_styles is None:
return err(1007, f"hair_style 必填且为 1..{max_styles} 的整数(逗号分隔),收到 {hair_style!r}") return err(1007, f"hair_style 必填且为 1..{max_styles} 的整数(逗号分隔),收到 {hair_style!r}")
@@ -1157,10 +1157,10 @@ async def face_features(
--- ---
**入参**(同接口2:先选性别,再多选发型): **入参**(同接口2:先选性别,再多选发型):
- 必填 `gender``male`/`female`),决定发型集合(female 7 / male 6)。 - 必填 `gender``male`/`female`),决定发型集合(female 7 / male 7)。
- 必填 `hair_style`(发型序号,逗号分隔如 `1,2,3`),决定返回哪些发际线类型。缺失/越界/非法返回 `1007`。 - 必填 `hair_style`(发型序号,逗号分隔如 `1,2,3`),决定返回哪些发际线类型。缺失/越界/非法返回 `1007`。
`female`1=ellipse,2=flower,3=heart,4=straight,5=wave,6=bigflower,7=clasicalflower `female`1=ellipse,2=flower,3=heart,4=straight,5=wave,6=bigflower,7=clasicalflower
`male`1=ellipse,2=inverse_arc,3=m,4=straight,5=heart,6=Softpetal。 `male`1=ellipse,2=inverse_arc,3=m,4=straight,5=heart,6=Softpetal,7=wave
- 可选 `use_mask` / `prompt`:同接口2 的生发控制参数。 - 可选 `use_mask` / `prompt`:同接口2 的生发控制参数。
注:生发黑模板固定取 `hairline_texture_black/`middle 档),即三档叠图分别用各自贴图、但生发目标固定 middle。 注:生发黑模板固定取 `hairline_texture_black/`middle 档),即三档叠图分别用各自贴图、但生发目标固定 middle。
- 可选 `generate_grow_image`(默认 `true`):是否生成生发效果图(ComfyUI 生发,全流程最耗时)。 - 可选 `generate_grow_image`(默认 `true`):是否生成生发效果图(ComfyUI 生发,全流程最耗时)。
@@ -1240,7 +1240,7 @@ async def hairline_generate(
image_url: Optional[str] = Form(default=None, description="图片 URL"), image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"), image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"), gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填,如 1,2,3)。female:1-7 male:1-6"), hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填,如 1,2,3)。female:1-7 male:1-7"),
use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"), use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"),
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"), prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"),
generate_grow_image: bool = Form(default=True, description="是否生成生发效果图(ComfyUI 生发,最耗时)。默认 true 出图;false 时跳过生发,各发型 grown_image 恒为 null,仅返回三档发际线叠图与中心点"), generate_grow_image: bool = Form(default=True, description="是否生成生发效果图(ComfyUI 生发,最耗时)。默认 true 出图;false 时跳过生发,各发型 grown_image 恒为 null,仅返回三档发际线叠图与中心点"),
@@ -1249,7 +1249,7 @@ async def hairline_generate(
return err(1004, "gender 必填且只能为 male / female") return err(1004, "gender 必填且只能为 male / female")
# hair_style 必填(同接口2):解析逗号分隔,缺失/越界/非法 → 1007 # hair_style 必填(同接口2):解析逗号分隔,缺失/越界/非法 → 1007
max_styles = {"female": 7, "male": 6}[gender] max_styles = {"female": 7, "male": 7}[gender]
hair_styles = _parse_hair_styles(hair_style, max_styles) hair_styles = _parse_hair_styles(hair_style, max_styles)
if hair_styles is None: if hair_styles is None:
return err(1007, f"hair_style 必填且为 1..{max_styles} 的整数(逗号分隔),收到 {hair_style!r}") return err(1007, f"hair_style 必填且为 1..{max_styles} 的整数(逗号分隔),收到 {hair_style!r}")
+1 -1
View File
@@ -68,7 +68,7 @@
`face_ext.obj` 的 UV 把发际线贴图渲染到额头(预览)。生发:黑贴图渲染遮罩 → 调本机 **ComfyUI 8182** `face_ext.obj` 的 UV 把发际线贴图渲染到额头(预览)。生发:黑贴图渲染遮罩 → 调本机 **ComfyUI 8182**
`add_hair.json`(Flux-2) 出图。**关键坑**obj 是重排序,需 `INDEX_MAP_468` 把 MP 序→OBJ 序。 `add_hair.json`(Flux-2) 出图。**关键坑**obj 是重排序,需 `INDEX_MAP_468` 把 MP 序→OBJ 序。
返回 `results[].image_base64` + `grown_image_base64` 返回 `results[].image_base64` + `grown_image_base64`
- `hair_style` 映射:female 1=ellipse 2=flower 3=heart 4=straight 5=wave 6=bigflower 7=clasicalflowermale 1=ellipse 2=inverse_arc 3=m 4=straight 5=heart 6=Softpetal。female 1..5 走「换发型(change_hair)」+Flux-2 重绘管线;female 6/7 与 male 全部走原生发(ComfyUI add_hair)管线。 - `hair_style` 映射:female 1=ellipse 2=flower 3=heart 4=straight 5=wave 6=bigflower 7=clasicalflowermale 1=ellipse 2=inverse_arc 3=m 4=straight 5=heart 6=Softpetal 7=wave。female 1..5 走「换发型(change_hair)」+Flux-2 重绘管线;female 6/7 与 male 全部走原生发(ComfyUI add_hair)管线。
### 接口7 C端生发 v2 `/api/v1/hair/grow-v2`worker)—— 接口2同款,add_hair2 工作流 ### 接口7 C端生发 v2 `/api/v1/hair/grow-v2`worker)—— 接口2同款,add_hair2 工作流
- **做什么**:与接口 2 完全一致(正面照 + `gender` + `hair_style` 逗号分隔多选 → N 组预览+生发图)。 - **做什么**:与接口 2 完全一致(正面照 + `gender` + `hair_style` 逗号分隔多选 → N 组预览+生发图)。
+5 -5
View File
@@ -265,7 +265,7 @@
| 参数 | 类型 | 必填 | 说明 | | 参数 | 类型 | 必填 | 说明 |
|------|------|------|------| |------|------|------|------|
| gender | string | **是** | 性别:`male` / `female`。决定使用的发际线贴图集合 | | gender | string | **是** | 性别:`male` / `female`。决定使用的发际线贴图集合 |
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),最多不超过该性别的预设数。female1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave, 6=bigflower, 7=clasicalflowermale1=ellipse, 2=inverse_arc, 3=m, 4=straight, 5=heart, 6=Softpetal。越界/非法返回 `1007` | | hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),最多不超过该性别的预设数。female1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave, 6=bigflower, 7=clasicalflowermale1=ellipse, 2=inverse_arc, 3=m, 4=straight, 5=heart, 6=Softpetal, 7=wave。越界/非法返回 `1007` |
| beauty_enabled | bool | 否 | 生发图是否带美颜效果,默认 false(当前阶段不生效) | | beauty_enabled | bool | 否 | 生发图是否带美颜效果,默认 false(当前阶段不生效) |
| use_mask | bool | 否 | 是否启用 inpaint 遮罩,默认 `true``false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比 | | use_mask | bool | 否 | 是否启用 inpaint 遮罩,默认 `true``false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比 |
| prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 | | prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 |
@@ -278,7 +278,7 @@
|------|------|------| |------|------|------|
| image_url | string | 发际线曲线**透明 PNG** URL(仅白色发际线曲线,透明底,**不含人物**,需前端叠加原图显示) | | image_url | string | 发际线曲线**透明 PNG** URL(仅白色发际线曲线,透明底,**不含人物**,需前端叠加原图显示) |
| grown_image_url | string | **生发后图片** URLComfyUI/Flux「植发 3 个月」效果图,完整人像照片) | | grown_image_url | string | **生发后图片** URLComfyUI/Flux「植发 3 个月」效果图,完整人像照片) |
| hairline_type | string | 发际线类型 key`ellipse`/`flower`/`heart`/`straight`/`wave`/`bigflower`/`clasicalflower`female),`ellipse`/`m`/`straight`/`inverse_arc`/`heart`/`Softpetal`male | | hairline_type | string | 发际线类型 key`ellipse`/`flower`/`heart`/`straight`/`wave`/`bigflower`/`clasicalflower`female),`ellipse`/`m`/`straight`/`inverse_arc`/`heart`/`Softpetal`/`wave`male |
| order | int | 排序序号(当前阶段固定 `1..N`,按贴图顺序,暂不计算合适度) | | order | int | 排序序号(当前阶段固定 `1..N`,按贴图顺序,暂不计算合适度) |
> ⚠️ 生发图由本机 ComfyUIFlux-2,端口 8182)生成,**一次请求生成指定发型的 1 张、同步返回**。 > ⚠️ 生发图由本机 ComfyUIFlux-2,端口 8182)生成,**一次请求生成指定发型的 1 张、同步返回**。
@@ -405,8 +405,8 @@
| 参数 | 类型 | 必填 | 说明 | | 参数 | 类型 | 必填 | 说明 |
|------|------|------|------| |------|------|------|------|
| gender | string | **是** | 性别:`male` / `female`。决定发型集合(female 7 / male 6)。缺失/非法返回 `1004` | | gender | string | **是** | 性别:`male` / `female`。决定发型集合(female 7 / male 7)。缺失/非法返回 `1004` |
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),决定返回哪些发际线类型。female1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave, 6=bigflower, 7=clasicalflowermale1=ellipse, 2=inverse_arc, 3=m, 4=straight, 5=heart, 6=Softpetal。缺失/越界/非法返回 `1007` | | hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),决定返回哪些发际线类型。female1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave, 6=bigflower, 7=clasicalflowermale1=ellipse, 2=inverse_arc, 3=m, 4=straight, 5=heart, 6=Softpetal, 7=wave。缺失/越界/非法返回 `1007` |
| use_mask | bool | 否 | 生发是否启用 inpaint 遮罩,默认 `true``false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比 | | use_mask | bool | 否 | 生发是否启用 inpaint 遮罩,默认 `true``false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比 |
| prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 | | prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 |
| generate_grow_image | bool | 否 | 是否生成生发效果图(ComfyUI 生发,全流程最耗时),默认 `true`。传 `false` 时跳过生发,各发型 `grown_image_*` 恒为 `null`,仅返回三档发际线叠图与中心点,可大幅降低耗时 | | generate_grow_image | bool | 否 | 是否生成生发效果图(ComfyUI 生发,全流程最耗时),默认 `true`。传 `false` 时跳过生发,各发型 `grown_image_*` 恒为 `null`,仅返回三档发际线叠图与中心点,可大幅降低耗时 |
@@ -427,7 +427,7 @@
| 字段 | 类型 | 说明 | | 字段 | 类型 | 说明 |
|------|------|------| |------|------|------|
| hairline_type | string | 发际线类型 key`ellipse`/`flower`/`heart`/`straight`/`wave`/`bigflower`/`clasicalflower`female),`ellipse`/`m`/`straight`/`inverse_arc`/`heart`/`Softpetal`male | | hairline_type | string | 发际线类型 key`ellipse`/`flower`/`heart`/`straight`/`wave`/`bigflower`/`clasicalflower`female),`ellipse`/`m`/`straight`/`inverse_arc`/`heart`/`Softpetal`/`wave`male |
| image_middle_url | string | middle 档发际线曲线**透明 PNG** URL(仅曲线,透明底,**不含人物**,需叠加原图显示) | | image_middle_url | string | middle 档发际线曲线**透明 PNG** URL(仅曲线,透明底,**不含人物**,需叠加原图显示) |
| image_high_url | string | high 档发际线曲线**透明 PNG** URL(同上,high 档曲线) | | image_high_url | string | high 档发际线曲线**透明 PNG** URL(同上,high 档曲线) |
| image_low_url | string | low 档发际线曲线**透明 PNG** URL(同上,low 档曲线) | | image_low_url | string | low 档发际线曲线**透明 PNG** URL(同上,low 档曲线) |
+3 -2
View File
@@ -46,7 +46,8 @@ _FEMALE_KEY_TO_CHANG = {
_HAIRSTYLE_ORDER = { _HAIRSTYLE_ORDER = {
"female": ["ellipse", "flower", "heart", "straight", "wave", "female": ["ellipse", "flower", "heart", "straight", "wave",
"bigflower", "clasicalflower"], # 1..7 "bigflower", "clasicalflower"], # 1..7
"male": ["ellipse", "inverse_arc", "m", "straight", "heart", "Softpetal"], # 1..6 "male": ["ellipse", "inverse_arc", "m", "straight", "heart", "Softpetal",
"wave"], # 1..7
} }
_REPO = os.path.dirname(os.path.dirname(__file__)) _REPO = os.path.dirname(os.path.dirname(__file__))
@@ -219,7 +220,7 @@ def generate_grow_results(image_bgr: np.ndarray, gender: str, use_mask: bool = T
workflow_path: str | None = None): workflow_path: str | None = None):
"""指定发际线类型:发际线透明叠图(白线 RGBA) + 生发图(ComfyUI)。 """指定发际线类型:发际线透明叠图(白线 RGBA) + 生发图(ComfyUI)。
hair_styles1-indexed 列表):指定生成哪几张发际线(按贴图排序)。female: 1..7male: 1..6 hair_styles1-indexed 列表):指定生成哪几张发际线(按贴图排序)。female: 1..7male: 1..7
为 None 时生成全部(兼容旧调用)。 为 None 时生成全部(兼容旧调用)。
use_mask(默认 True):是否启用 inpaint 遮罩,用于测试对比(同接口3)。 use_mask(默认 True):是否启用 inpaint 遮罩,用于测试对比(同接口3)。
False 时用**干净原图 + 空遮罩**送 ComfyUI(不烧黑色模板线)。 False 时用**干净原图 + 空遮罩**送 ComfyUI(不烧黑色模板线)。
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

+2 -1
View File
@@ -134,7 +134,7 @@ const API_BASE = window.location.origin;
let _origUrl = ''; let _origUrl = '';
const TYPE_LABELS = { const TYPE_LABELS = {
ellipse:'椭圆形', flower:'花瓣形', heart:'心形', straight:'直线形', wave:'波浪形', m:'M形', inverse_arc:'倒弧形' ellipse:'椭圆形', flower:'花瓣形', heart:'心形', straight:'直线形', wave:'波浪形', m:'M形', inverse_arc:'倒弧形', Softpetal:'柔和花瓣形', bigflower:'大花瓣型', clasicalflower:'古典花瓣型'
}; };
function $(id) { return document.getElementById(id); } function $(id) { return document.getElementById(id); }
@@ -247,6 +247,7 @@ const HAIR_STYLES = {
{ value:'4', label:'straight 直线形' }, { value:'4', label:'straight 直线形' },
{ value:'5', label:'heart 桃心形' }, { value:'5', label:'heart 桃心形' },
{ value:'6', label:'Softpetal 柔和花瓣形' }, { value:'6', label:'Softpetal 柔和花瓣形' },
{ value:'7', label:'wave 波浪形' },
] ]
}; };
+3 -2
View File
@@ -101,7 +101,7 @@
<div class="file-input"><input type="file" id="imageFile" accept="image/jpeg,image/png,.jpg,.jpeg,.png"></div> <div class="file-input"><input type="file" id="imageFile" accept="image/jpeg,image/png,.jpg,.jpeg,.png"></div>
<div class="form-group"> <div class="form-group">
<label>性别</label> <label>性别</label>
<select id="gender" onchange="onGenderChange()"><option value="female" selected>👩 Female7种)</option><option value="male">👨 Male6种)</option></select> <select id="gender" onchange="onGenderChange()"><option value="female" selected>👩 Female7种)</option><option value="male">👨 Male7种)</option></select>
</div> </div>
<div class="form-group" style="align-items:flex-start"> <div class="form-group" style="align-items:flex-start">
<label style="padding-top:3px">发型 *</label> <label style="padding-top:3px">发型 *</label>
@@ -122,7 +122,7 @@
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button> <button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
<button class="btn btn-outline btn-sm" onclick="clearResults()">清除</button> <button class="btn btn-outline btn-sm" onclick="clearResults()">清除</button>
</div> </div>
<div class="hint">JPG/PNG 正面照 &nbsp;|&nbsp; Female: 椭圆/花瓣/心形/直线/波浪 &nbsp;|&nbsp; Male: 椭圆/倒弧/M形/直线 &nbsp;|&nbsp; 每个发型返回 middle/high/low 三档 + 生发图</div> <div class="hint">JPG/PNG 正面照 &nbsp;|&nbsp; Female: 椭圆/花瓣/心形/直线/波浪/大花瓣/古典花瓣 &nbsp;|&nbsp; Male: 椭圆/倒弧/M形/直线/桃心/柔和花瓣/波浪 &nbsp;|&nbsp; 每个发型返回 middle/high/low 三档 + 生发图</div>
<div id="statusBar" class="status hidden"></div> <div id="statusBar" class="status hidden"></div>
</div> </div>
</div> </div>
@@ -184,6 +184,7 @@ const HAIR_STYLES = {
{ value:'4', label:'4. straight 直线' }, { value:'4', label:'4. straight 直线' },
{ value:'5', label:'5. heart 桃心形' }, { value:'5', label:'5. heart 桃心形' },
{ value:'6', label:'6. Softpetal 柔和花瓣形' }, { value:'6', label:'6. Softpetal 柔和花瓣形' },
{ value:'7', label:'7. wave 波浪形' },
] ]
}; };