Whee:图片分层
描述
对单张图片进行分层处理,返回背景、图片和文本等图层的资源地址、位置、尺寸及样式信息,供客户端解析和编辑。
版本
1.0
图片要求
每次请求仅支持一张图片,通过 image_file 传入可访问的图片 URL。
调用 URL
https://openapi.meitu.com/whee/business/image_layering.json调用方法
POST
Content-Type: application/json
权限
使用已开通该能力的 AccessKey 和 SecretKey,通过签名 SDK 生成请求签名。详见开放平台接口签名。
请求参数
请求体为 JSON 对象,字段直接放在根级。
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 是 | image_file | string | 单张输入图片的 URL。 |
| 否 | subject_protect_flag | boolean | 是否开启主体保护。原文将其作用范围描述为海报翻译阶段;true 开启,false 关闭,默认 false。 |
| 否 | sync_timeout | integer | 单位为秒。-1:异步处理,立即返回任务标识;0–60:同步等待指定时间,期间完成则返回结果,否则返回任务标识供后续查询。 |
输入值示例
以下使用异步方式,替换图片 URL 后提交:
{
"image_file": "https://example.com/input.jpg",
"subject_protect_flag": false,
"sync_timeout": -1
}如需同步等待,将 sync_timeout 改为 0–60 范围内的整数,例如 30。同步等待结束后仍可能需要查询任务。
返回值说明
先检查 code,再检查 data.status。状态为 10 时读取图层结果;状态为 9 时使用任务 ID 继续查询,不要重复提交。
| 字段 | 类型 | 说明 |
|---|---|---|
| code | integer | 业务状态码,0 表示请求处理正常。 |
| message | string | 响应消息,失败时返回错误说明。 |
| data | object / null | 任务详情,请求失败时可能为 null。 |
data
| 字段 | 类型 | 说明 |
|---|---|---|
| status | integer | -1 未找到;0 已创建;1 执行中;2 失败;9 待查询;10 成功。 |
| result | object | 任务标识及处理结果。 |
| progress | number | 任务进度,如 0.1、0.85、1。 |
| msg | string | 任务消息。 |
| predict_elapsed | number | 预计耗时,原文未明确单位。 |
data.result
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 任务 ID,用于查询,不使用 msg_id。 |
| parameters | object | 结果参数,包含图层数据。 |
| data | object | 底层算法结果,存在时包含耗时、错误信息及媒体信息。 |
| msg | string | 算法消息,如 success。 |
| msg_id | string | 算法消息 ID,不是查询任务 ID。 |
| mtlab_res | object | 底层执行信息,可能包含 ErrorCode、ErrorMsg、error_code、error_msg 等字段。 |
data.result.parameters.return_json_data
| 字段 | 类型 | 说明 |
|---|---|---|
| code | integer | 图层数据生成状态,0 表示成功。 |
| cost_time | string | 处理耗时,单位为秒,例如 "36.222"。 |
| error_message | string | 错误说明,成功示例为空字符串。 |
| json_data | string | 序列化后的图层 JSON,需再次解析为对象。 |
json_data 解码后的对象
| 字段 | 类型 | 说明 |
|---|---|---|
| editMode | integer | 编辑模式,1 表示启用。 |
| extraInfo | object | 附加信息。 |
| name | string | 设计名称。 |
| preview | string | 预览图 URL。 |
| referenceLine | object | 参考线配置:lines、lock、show。 |
| subject | string | 主题。 |
| templateConf | object[] | 画布及图层配置列表。 |
| text_graphic_box_info | object[] | 文本图形框信息,可包含 cls_id 和 location,不保证为空数组。 |
| version | string | 设计数据版本,原文示例为 11.0.0,与接口版本不同。 |
templateConf 元素
| 字段 | 类型 | 说明 |
|---|---|---|
| width / height | number | 画布宽高,单位为像素;不是固定尺寸。 |
| layers | object[] | 图层列表,字段随图层类型变化。 |
| editorColor | string | 编辑器颜色。 |
| originId / settingType | string / null | 原始 ID、设置类型,示例中可为 null。 |
| threshold | number | 阈值。 |
layers 元素
不同类型的图层不一定包含下表全部字段。图层数量、画布尺寸和位置均以实际结果为准。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 图层 ID。 |
| layerType | string | bg 背景图层、img 图片图层、text 文本图层。 |
| width / height | number | 图层宽高,单位为像素。 |
| left / top | number | 图层位置偏移,单位为像素。 |
| rotate | number | 旋转角度,单位为度。 |
| opacity | number | 不透明度,1 表示完全不透明。 |
| lock / hidden / shine | boolean | 是否锁定、隐藏、启用光泽效果。 |
| children | array | 子图层。 |
| url | string | 图片图层资源 URL。 |
| cutoutMaskURL / maskURL | string | 抠图掩模或图层遮罩 URL,可为空字符串。 |
| stretchOption | number | 0 不拉伸,1 按比例拉伸。 |
| naturalWidth / naturalHeight | number | 图片自然宽高,单位为像素。 |
| imageTransform | object | 图像变换,包括 offsetX、offsetY、rotate、scale、flipH、flipV、widthRatio、heightRatio。 |
| maskType | number | 遮罩类型,0 表示无遮罩。 |
| parentId / title | string | 父图层 ID、标题。 |
| materialIds | object | 素材 ID 信息。 |
| backgroundColor | string | 背景颜色,例如 #FFFFFFFF。 |
| combine_flag | boolean | 是否合并图层。 |
| box | number[] | 边界框数据,原文示例为四个数值。 |
文本图层补充字段
当 layerType = "text" 时,可读取以下文本样式字段。
| 字段 | 类型 | 说明 |
|---|---|---|
| text | string | 文本内容。 |
| fontFamily / fontUrl / fontZipUrl | string | 字体名称、字体文件 URL、字体压缩包 URL。 |
| fontSize | number | 字号。 |
| color | string | 文本颜色。 |
| bold / italic / underline / lineThrough | boolean | 粗体、斜体、下划线、删除线。 |
| textAlign / writingMode | string | 对齐和书写模式,示例为 left、horizontal-tb。 |
| letterSpacing / lineSpacing | number | 字间距和行间距。 |
| textBackground / textGlow / textShadow / textStroke | array | 文本背景、发光、阴影和描边配置。 |
| textGradient | object | 文本渐变配置,包含 angle 和 colors。 |
图层数据解析
- 将 HTTP 响应解析为 JSON,确认
code = 0且data.status = 10。 - 读取
data.result.parameters.return_json_data,检查其code;非零时读取error_message。 - 将其中的
json_data字符串再次解析为 JSON 对象。 - 遍历
templateConf[].layers[]。图片图层读取url,文本图层读取text和字体样式;结合left、top、width、height、rotate等字段使用图层数据。
返回值示例
以下根据原文示例精简,保留结果层级和背景、图片、文本三类图层;地址和 ID 为占位值,省略部分附加字段,不代表固定图层数量。示例不是本次实际调用结果。
请求成功返回示例
HTTP 200。
{
"code": 0,
"data": {
"status": 10,
"result": {
"id": "example-task-id",
"parameters": {
"return_json_data": {
"code": 0,
"cost_time": "36.222",
"error_message": "",
"json_data": "{\"editMode\":1,\"extraInfo\":{},\"name\":\"\",\"preview\":\"https://example.com/preview.jpg\",\"referenceLine\":{\"lines\":[],\"lock\":false,\"show\":true},\"subject\":\"\",\"templateConf\":[{\"editorColor\":\"\",\"height\":1024,\"width\":768,\"originId\":null,\"settingType\":null,\"threshold\":0,\"layers\":[{\"id\":\"layer_000_background\",\"layerType\":\"bg\",\"width\":768,\"height\":1024,\"left\":0,\"top\":0,\"rotate\":0,\"opacity\":1,\"lock\":false,\"hidden\":false,\"children\":[],\"url\":\"https://example.com/background.png\",\"cutoutMaskURL\":\"\",\"maskURL\":\"\",\"stretchOption\":0,\"naturalWidth\":768,\"naturalHeight\":1024,\"imageTransform\":{\"offsetX\":0,\"offsetY\":0,\"rotate\":0,\"scale\":1,\"flipH\":false,\"flipV\":false,\"widthRatio\":1,\"heightRatio\":1},\"maskType\":0,\"parentId\":\"\",\"title\":\"\",\"materialIds\":{},\"backgroundColor\":\"#FFFFFFFF\",\"combine_flag\":false},{\"id\":\"layer_001_img\",\"layerType\":\"img\",\"width\":585,\"height\":742,\"left\":103,\"top\":282,\"rotate\":0,\"opacity\":1,\"lock\":false,\"hidden\":false,\"children\":[],\"url\":\"https://example.com/foreground.png\",\"stretchOption\":1,\"box\":[103,282,688,1024],\"combine_flag\":false},{\"id\":\"example-text-layer\",\"layerType\":\"text\",\"text\":\"Example\",\"fontFamily\":\"Poppins-Regular\",\"fontSize\":21,\"fontUrl\":\"https://example.com/font.ttf\",\"color\":\"#FBDBAE\",\"width\":102,\"height\":28,\"left\":315,\"top\":487,\"rotate\":0,\"opacity\":1,\"bold\":false,\"italic\":false,\"textAlign\":\"left\",\"writingMode\":\"horizontal-tb\",\"children\":[],\"combine_flag\":true}]}],\"text_graphic_box_info\":[{\"cls_id\":\"4\",\"location\":[543,478,675,610]}],\"version\":\"11.0.0\"}"
}
}
},
"progress": 1,
"predict_elapsed": 10000
}
}json_data 解码示例
{
"editMode": 1,
"extraInfo": {},
"name": "",
"preview": "https://example.com/preview.jpg",
"referenceLine": {
"lines": [],
"lock": false,
"show": true
},
"subject": "",
"templateConf": [
{
"editorColor": "",
"height": 1024,
"width": 768,
"originId": null,
"settingType": null,
"threshold": 0,
"layers": [
{
"id": "layer_000_background",
"layerType": "bg",
"width": 768,
"height": 1024,
"left": 0,
"top": 0,
"rotate": 0,
"opacity": 1,
"lock": false,
"hidden": false,
"children": [],
"url": "https://example.com/background.png",
"cutoutMaskURL": "",
"maskURL": "",
"stretchOption": 0,
"naturalWidth": 768,
"naturalHeight": 1024,
"imageTransform": {
"offsetX": 0,
"offsetY": 0,
"rotate": 0,
"scale": 1,
"flipH": false,
"flipV": false,
"widthRatio": 1,
"heightRatio": 1
},
"maskType": 0,
"parentId": "",
"title": "",
"materialIds": {},
"backgroundColor": "#FFFFFFFF",
"combine_flag": false
},
{
"id": "layer_001_img",
"layerType": "img",
"width": 585,
"height": 742,
"left": 103,
"top": 282,
"rotate": 0,
"opacity": 1,
"lock": false,
"hidden": false,
"children": [],
"url": "https://example.com/foreground.png",
"stretchOption": 1,
"box": [
103,
282,
688,
1024
],
"combine_flag": false
},
{
"id": "example-text-layer",
"layerType": "text",
"text": "Example",
"fontFamily": "Poppins-Regular",
"fontSize": 21,
"fontUrl": "https://example.com/font.ttf",
"color": "#FBDBAE",
"width": 102,
"height": 28,
"left": 315,
"top": 487,
"rotate": 0,
"opacity": 1,
"bold": false,
"italic": false,
"textAlign": "left",
"writingMode": "horizontal-tb",
"children": [],
"combine_flag": true
}
]
}
],
"text_graphic_box_info": [
{
"cls_id": "4",
"location": [
543,
478,
675,
610
]
}
],
"version": "11.0.0"
}需要查询时
sync_timeout = -1 为异步提交;同步等待结束但任务尚未完成时也需要查询。返回 data.status = 9 表示待查询,不代表处理失败。
保存返回的任务标识。原文结果中的任务 ID 字段为 data.result.id,不要使用算法的 msg_id。查询方式见任务查询接口。
请求失败返回示例
HTTP 400。错误消息可能随具体原因变化。
{
"code": 11002,
"message": "请求失败,请重试",
"data": null
}通用的错误代码与信息
| 错误码 | 错误信息 | 说明 |
|---|---|---|
| 11001 | PARAMS_INVALID | 参数无效。 |
| 11002 | PROCESS_ERROR | 处理错误。 |
其他错误详见 API 错误码。
SDK 调用示例
以下示例均调用本篇专用接口,直接发送 image_file、subject_protect_flag、sync_timeout。按对应文档引入 SDK 后,替换 AK、SK 和图片 URL。请求体已内嵌,无需额外 JSON 文件。
示例提交一次并打印响应。签名和发送使用相同的 URL、方法及请求体;异步任务按上文继续查询。
Python
import json
import requests
from sign_sdk import sign
def main():
key = "YOUR_ACCESS_KEY"
secret = "YOUR_SECRET_KEY"
url = "https://openapi.meitu.com/whee/business/image_layering.json"
payload = {
"image_file": "https://example.com/input.jpg",
"subject_protect_flag": False,
"sync_timeout": -1
}
body = json.dumps(payload, ensure_ascii=True)
headers = {
"Content-Type": "application/json",
sign.HeaderHost: "openapi.meitu.com",
}
signer = sign.Signer(key, secret)
signed_request = signer.sign(url, "POST", headers, body)
with requests.Session() as session:
response = session.send(
signed_request, timeout=(10, 60), verify=True, allow_redirects=False
)
print("Status:", response.status_code)
print("Response:", response.text)
if __name__ == "__main__":
main()
Go
package main
import (
"fmt"
"io"
"net/http"
"time"
"github.com/formula/api/signer"
)
func main() {
body := `{
"image_file": "https://example.com/input.jpg",
"subject_protect_flag": false,
"sync_timeout": -1
}`
url := "https://openapi.meitu.com/whee/business/image_layering.json"
headers := make(http.Header)
headers.Set("Content-Type", "application/json")
headers.Set(signer.HeaderHost, "openapi.meitu.com")
signObj := signer.NewSigner("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY")
req, err := signObj.Sign(url, http.MethodPost, headers, body)
if err != nil {
fmt.Println("Sign request failed:", err)
return
}
client := &http.Client{
Timeout: 60 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Send request failed:", err)
return
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Read response failed:", err)
return
}
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Response:", string(responseBody))
}
PHP
<?php
require_once __DIR__ . '/signer.php';
$body = json_encode([
'image_file' => 'https://example.com/input.jpg',
'subject_protect_flag' => false,
'sync_timeout' => -1
], JSON_UNESCAPED_SLASHES);
$url = 'https://openapi.meitu.com/whee/business/image_layering.json';
$headers = [
'Content-Type' => 'application/json',
'Host' => 'openapi.meitu.com',
];
$signer = new Signer('YOUR_ACCESS_KEY', 'YOUR_SECRET_KEY');
$curl = $signer->sign($url, 'POST', $headers, $body);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 60);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
$response = curl_exec($curl);
if ($response === false) {
echo 'Error: ' . curl_error($curl) . PHP_EOL;
} else {
echo 'Status: ' . curl_getinfo($curl, CURLINFO_HTTP_CODE) . PHP_EOL;
echo 'Response: ' . $response . PHP_EOL;
}
curl_close($curl);
Java
import com.meitu.openai.common.Signer;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
String body =
"{\n" +
" \"image_file\": \"https://example.com/input.jpg\",\n" +
" \"subject_protect_flag\": false,\n" +
" \"sync_timeout\": -1\n" +
"}";
String url = "https://openapi.meitu.com/whee/business/image_layering.json";
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put(Signer.HeaderHost, "openapi.meitu.com");
Signer signer = new Signer("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY");
Map<String, String> signedHeaders = signer.sign(url, "POST", headers, body);
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
try {
connection.setRequestMethod("POST");
connection.setConnectTimeout(10000);
connection.setReadTimeout(60000);
connection.setInstanceFollowRedirects(false);
connection.setDoOutput(true);
for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
connection.setFixedLengthStreamingMode(bodyBytes.length);
try (OutputStream output = connection.getOutputStream()) {
output.write(bodyBytes);
}
int status = connection.getResponseCode();
System.out.println("Status: " + status);
InputStream responseStream = status >= 400
? connection.getErrorStream() : connection.getInputStream();
if (responseStream != null) {
try (InputStream input = responseStream;
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int length;
while ((length = input.read(buffer)) != -1) {
output.write(buffer, 0, length);
}
System.out.println("Response: " + output.toString("UTF-8"));
}
}
} finally {
connection.disconnect();
}
}
}