Whee: Image Layering
Description
Separate a single image into layers and return resource URLs, positions, dimensions, and styles for background, image, and text layers for client-side parsing and editing.
Version
1.0
Image Requirements
Each request supports one image. Provide an accessible image URL in image_file.
Request URL
https://openapi.meitu.com/whee/business/image_layering.jsonHTTP Method
POST
Content-Type: application/json
Authentication
Use an AccessKey and SecretKey with access to this capability and sign the request with the signing SDK. See API Request Signing.
Request Parameters
Send a JSON object with the following fields at the root level.
| Required | Parameter | Type | Description |
|---|---|---|---|
| Yes | image_file | string | URL of a single input image. |
| No | subject_protect_flag | boolean | Enable subject protection. The source describes its scope as the poster translation stage. true: enabled; false: disabled. Default: false. |
| No | sync_timeout | integer | Seconds. -1: process asynchronously and immediately return a task identifier. 0–60: wait synchronously for the specified period, returning the result if ready or a task identifier for subsequent queries. |
Request Example
The following submits asynchronously. Replace the image URL:
{
"image_file": "https://example.com/input.jpg",
"subject_protect_flag": false,
"sync_timeout": -1
}To wait synchronously, set sync_timeout to an integer from 0–60, such as 30. You may still need to query the task after the wait ends.
Response Fields
Check code, then data.status. Read the layer result when the status is 10. When it is 9, query using the task ID instead of resubmitting.
| Field | Type | Description |
|---|---|---|
| code | integer | Business status code; 0 indicates normal request processing. |
| message | string | Response message; describes the error on failure. |
| data | object / null | Task details; may be null on request failure. |
data
| Field | Type | Description |
|---|---|---|
| status | integer | -1: not found; 0: created; 1: running; 2: failed; 9: query required; 10: succeeded. |
| result | object | Task identifier and processing result. |
| progress | number | Task progress, such as 0.1, 0.85, or 1. |
| msg | string | Task message. |
| predict_elapsed | number | Estimated processing time; the source does not specify units. |
data.result
| Field | Type | Description |
|---|---|---|
| id | string | Task ID for queries. Do not use msg_id. |
| parameters | object | Result parameters containing layer data. |
| data | object | Underlying algorithm result, when present, including timings, errors, and media information. |
| msg | string | Algorithm message, such as success. |
| msg_id | string | Algorithm message ID, not the task ID used for queries. |
| mtlab_res | object | Underlying execution details, which may include ErrorCode, ErrorMsg, error_code, and error_msg. |
data.result.parameters.return_json_data
| Field | Type | Description |
|---|---|---|
| code | integer | Layer-data generation status; 0 indicates success. |
| cost_time | string | Processing time in seconds, represented as a string, such as "36.222". |
| error_message | string | Error description; an empty string in the success example. |
| json_data | string | Serialized layer JSON; parse this string again to obtain an object. |
Decoded json_data Object
| Field | Type | Description |
|---|---|---|
| editMode | integer | Editing mode; 1 means enabled. |
| extraInfo | object | Additional information. |
| name | string | Design name. |
| preview | string | Preview image URL. |
| referenceLine | object | Guide settings: lines, lock, and show. |
| subject | string | Subject. |
| templateConf | object[] | Canvas and layer configurations. |
| text_graphic_box_info | object[] | Text/graphic region information; may contain cls_id and location and is not necessarily empty. |
| version | string | Design-data version; 11.0.0 in the source example, distinct from the API version. |
templateConf Element
| Field | Type | Description |
|---|---|---|
| width / height | number | Canvas width/height in pixels; these are not fixed dimensions. |
| layers | object[] | Layer list; fields vary by layer type. |
| editorColor | string | Editor color. |
| originId / settingType | string / null | Original ID and setting type; may be null in the example. |
| threshold | number | Threshold. |
layers Element
Not every layer type contains all fields below. Layer counts, canvas dimensions, and positions depend on the actual result.
| Field | Type | Description |
|---|---|---|
| id | string | Layer ID. |
| layerType | string | bg: background; img: image; text: text. |
| width / height | number | Layer dimensions in pixels. |
| left / top | number | Layer position offsets in pixels. |
| rotate | number | Rotation in degrees. |
| opacity | number | Opacity; 1 means fully opaque. |
| lock / hidden / shine | boolean | Whether the layer is locked, hidden, or uses a shine effect. |
| children | array | Child layers. |
| url | string | Image layer resource URL. |
| cutoutMaskURL / maskURL | string | Cutout or layer mask URL; may be an empty string. |
| stretchOption | number | 0: no stretching; 1: proportional stretching. |
| naturalWidth / naturalHeight | number | Natural image dimensions in pixels. |
| imageTransform | object | Image transform with offsetX, offsetY, rotate, scale, flipH, flipV, widthRatio, and heightRatio. |
| maskType | number | Mask type; 0 indicates no mask. |
| parentId / title | string | Parent layer ID and title. |
| materialIds | object | Material ID information. |
| backgroundColor | string | Background color, such as #FFFFFFFF. |
| combine_flag | boolean | Whether layers are combined. |
| box | number[] | Bounding-box data; the source example contains four numbers. |
Additional Text Layer Fields
For layerType = "text", the following text styling fields may be present.
| Field | Type | Description |
|---|---|---|
| text | string | Text content. |
| fontFamily / fontUrl / fontZipUrl | string | Font family, font file URL, and font archive URL. |
| fontSize | number | Font size. |
| color | string | Text color. |
| bold / italic / underline / lineThrough | boolean | Bold, italic, underline, and strikethrough. |
| textAlign / writingMode | string | Alignment and writing mode; example values are left and horizontal-tb. |
| letterSpacing / lineSpacing | number | Letter and line spacing. |
| textBackground / textGlow / textShadow / textStroke | array | Text background, glow, shadow, and stroke settings. |
| textGradient | object | Text gradient settings containing angle and colors. |
Parsing Layer Data
- Parse the HTTP response as JSON and confirm
code = 0anddata.status = 10. - Read
data.result.parameters.return_json_dataand check itscode. If it is nonzero, readerror_message. - Parse its
json_datastring again as JSON. - Iterate through
templateConf[].layers[]. Readurlfor image layers andtextand font styles for text layers. Use position and transform fields such asleft,top,width,height, androtate.
Response Examples
The following is condensed from the source example, retaining its result hierarchy and three layer types: background, image, and text. URLs and IDs are placeholders; some additional fields are omitted. The example does not specify a fixed layer count and is not a live result from this work.
Success Example
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
}
}Decoded json_data Example
{
"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"
}When to Query
Setting sync_timeout = -1 submits asynchronously. A query is also needed if synchronous waiting ends before processing finishes. data.status = 9 means a query is required, not that processing failed.
Save the returned task identifier. The source result uses data.result.id for the task ID; do not use the algorithm's msg_id. See Task Query.
Error Example
HTTP 400. The error message may vary with the cause.
{
"code": 11002,
"message": "Request failed. Please retry.",
"data": null
}Common Error Codes
| Code | Message | Description |
|---|---|---|
| 11001 | PARAMS_INVALID | Invalid parameters. |
| 11002 | PROCESS_ERROR | Processing error. |
See API Error Codes for other errors.
SDK Examples
Each example calls this API's dedicated endpoint and directly sends image_file, subject_protect_flag, and sync_timeout. Set up the SDK for your language, then replace the AK, SK, and image URL. The request body is embedded; no separate JSON file is required.
Each example submits once and prints the response. Sign and send the same URL, method, and body, then query asynchronous tasks as described above.
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();
}
}
}