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_filestring单张输入图片的 URL。
否subject_protect_flagboolean是否开启主体保护。原文将其作用范围描述为海报翻译阶段;true 开启,false 关闭,默认 false。
否sync_timeoutinteger单位为秒。-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 继续查询,不要重复提交。

字段类型说明
codeinteger业务状态码,0 表示请求处理正常。
messagestring响应消息,失败时返回错误说明。
dataobject / null任务详情,请求失败时可能为 null。

data

字段类型说明
statusinteger-1 未找到;0 已创建;1 执行中;2 失败;9 待查询;10 成功。
resultobject任务标识及处理结果。
progressnumber任务进度,如 0.1、0.85、1。
msgstring任务消息。
predict_elapsednumber预计耗时,原文未明确单位。

data.result

字段类型说明
idstring任务 ID,用于查询,不使用 msg_id。
parametersobject结果参数,包含图层数据。
dataobject底层算法结果,存在时包含耗时、错误信息及媒体信息。
msgstring算法消息,如 success。
msg_idstring算法消息 ID,不是查询任务 ID。
mtlab_resobject底层执行信息,可能包含 ErrorCode、ErrorMsg、error_code、error_msg 等字段。

data.result.parameters.return_json_data

字段类型说明
codeinteger图层数据生成状态,0 表示成功。
cost_timestring处理耗时,单位为秒,例如 "36.222"。
error_messagestring错误说明,成功示例为空字符串。
json_datastring序列化后的图层 JSON,需再次解析为对象。

json_data 解码后的对象

字段类型说明
editModeinteger编辑模式,1 表示启用。
extraInfoobject附加信息。
namestring设计名称。
previewstring预览图 URL。
referenceLineobject参考线配置:lines、lock、show。
subjectstring主题。
templateConfobject[]画布及图层配置列表。
text_graphic_box_infoobject[]文本图形框信息,可包含 cls_id 和 location,不保证为空数组。
versionstring设计数据版本,原文示例为 11.0.0,与接口版本不同。

templateConf 元素

字段类型说明
width / heightnumber画布宽高,单位为像素;不是固定尺寸。
layersobject[]图层列表,字段随图层类型变化。
editorColorstring编辑器颜色。
originId / settingTypestring / null原始 ID、设置类型,示例中可为 null。
thresholdnumber阈值。

layers 元素

不同类型的图层不一定包含下表全部字段。图层数量、画布尺寸和位置均以实际结果为准。

字段类型说明
idstring图层 ID。
layerTypestringbg 背景图层、img 图片图层、text 文本图层。
width / heightnumber图层宽高,单位为像素。
left / topnumber图层位置偏移,单位为像素。
rotatenumber旋转角度,单位为度。
opacitynumber不透明度,1 表示完全不透明。
lock / hidden / shineboolean是否锁定、隐藏、启用光泽效果。
childrenarray子图层。
urlstring图片图层资源 URL。
cutoutMaskURL / maskURLstring抠图掩模或图层遮罩 URL,可为空字符串。
stretchOptionnumber0 不拉伸,1 按比例拉伸。
naturalWidth / naturalHeightnumber图片自然宽高,单位为像素。
imageTransformobject图像变换,包括 offsetX、offsetY、rotate、scale、flipH、flipV、widthRatio、heightRatio。
maskTypenumber遮罩类型,0 表示无遮罩。
parentId / titlestring父图层 ID、标题。
materialIdsobject素材 ID 信息。
backgroundColorstring背景颜色,例如 #FFFFFFFF。
combine_flagboolean是否合并图层。
boxnumber[]边界框数据,原文示例为四个数值。

文本图层补充字段

当 layerType = "text" 时,可读取以下文本样式字段。

字段类型说明
textstring文本内容。
fontFamily / fontUrl / fontZipUrlstring字体名称、字体文件 URL、字体压缩包 URL。
fontSizenumber字号。
colorstring文本颜色。
bold / italic / underline / lineThroughboolean粗体、斜体、下划线、删除线。
textAlign / writingModestring对齐和书写模式,示例为 left、horizontal-tb。
letterSpacing / lineSpacingnumber字间距和行间距。
textBackground / textGlow / textShadow / textStrokearray文本背景、发光、阴影和描边配置。
textGradientobject文本渐变配置,包含 angle 和 colors。

图层数据解析

  1. 将 HTTP 响应解析为 JSON,确认 code = 0 且 data.status = 10。
  2. 读取 data.result.parameters.return_json_data,检查其 code;非零时读取 error_message。
  3. 将其中的 json_data 字符串再次解析为 JSON 对象。
  4. 遍历 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
}

通用的错误代码与信息

错误码错误信息说明
11001PARAMS_INVALID参数无效。
11002PROCESS_ERROR处理错误。

其他错误详见 API 错误码。

SDK 调用示例

以下示例均调用本篇专用接口,直接发送 image_file、subject_protect_flag、sync_timeout。按对应文档引入 SDK 后,替换 AK、SK 和图片 URL。请求体已内嵌,无需额外 JSON 文件。

示例提交一次并打印响应。签名和发送使用相同的 URL、方法及请求体;异步任务按上文继续查询。

Python

Python 签名 SDK 接入文档

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

Go 签名 SDK 接入文档

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 签名 SDK 接入文档

<?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

Java 签名 SDK 接入文档

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();
        }
    }
}