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.json

HTTP 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.

RequiredParameterTypeDescription
Yesimage_filestringURL of a single input image.
Nosubject_protect_flagbooleanEnable subject protection. The source describes its scope as the poster translation stage. true: enabled; false: disabled. Default: false.
Nosync_timeoutintegerSeconds. -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.

FieldTypeDescription
codeintegerBusiness status code; 0 indicates normal request processing.
messagestringResponse message; describes the error on failure.
dataobject / nullTask details; may be null on request failure.

data

FieldTypeDescription
statusinteger-1: not found; 0: created; 1: running; 2: failed; 9: query required; 10: succeeded.
resultobjectTask identifier and processing result.
progressnumberTask progress, such as 0.1, 0.85, or 1.
msgstringTask message.
predict_elapsednumberEstimated processing time; the source does not specify units.

data.result

FieldTypeDescription
idstringTask ID for queries. Do not use msg_id.
parametersobjectResult parameters containing layer data.
dataobjectUnderlying algorithm result, when present, including timings, errors, and media information.
msgstringAlgorithm message, such as success.
msg_idstringAlgorithm message ID, not the task ID used for queries.
mtlab_resobjectUnderlying execution details, which may include ErrorCode, ErrorMsg, error_code, and error_msg.

data.result.parameters.return_json_data

FieldTypeDescription
codeintegerLayer-data generation status; 0 indicates success.
cost_timestringProcessing time in seconds, represented as a string, such as "36.222".
error_messagestringError description; an empty string in the success example.
json_datastringSerialized layer JSON; parse this string again to obtain an object.

Decoded json_data Object

FieldTypeDescription
editModeintegerEditing mode; 1 means enabled.
extraInfoobjectAdditional information.
namestringDesign name.
previewstringPreview image URL.
referenceLineobjectGuide settings: lines, lock, and show.
subjectstringSubject.
templateConfobject[]Canvas and layer configurations.
text_graphic_box_infoobject[]Text/graphic region information; may contain cls_id and location and is not necessarily empty.
versionstringDesign-data version; 11.0.0 in the source example, distinct from the API version.

templateConf Element

FieldTypeDescription
width / heightnumberCanvas width/height in pixels; these are not fixed dimensions.
layersobject[]Layer list; fields vary by layer type.
editorColorstringEditor color.
originId / settingTypestring / nullOriginal ID and setting type; may be null in the example.
thresholdnumberThreshold.

layers Element

Not every layer type contains all fields below. Layer counts, canvas dimensions, and positions depend on the actual result.

FieldTypeDescription
idstringLayer ID.
layerTypestringbg: background; img: image; text: text.
width / heightnumberLayer dimensions in pixels.
left / topnumberLayer position offsets in pixels.
rotatenumberRotation in degrees.
opacitynumberOpacity; 1 means fully opaque.
lock / hidden / shinebooleanWhether the layer is locked, hidden, or uses a shine effect.
childrenarrayChild layers.
urlstringImage layer resource URL.
cutoutMaskURL / maskURLstringCutout or layer mask URL; may be an empty string.
stretchOptionnumber0: no stretching; 1: proportional stretching.
naturalWidth / naturalHeightnumberNatural image dimensions in pixels.
imageTransformobjectImage transform with offsetX, offsetY, rotate, scale, flipH, flipV, widthRatio, and heightRatio.
maskTypenumberMask type; 0 indicates no mask.
parentId / titlestringParent layer ID and title.
materialIdsobjectMaterial ID information.
backgroundColorstringBackground color, such as #FFFFFFFF.
combine_flagbooleanWhether layers are combined.
boxnumber[]Bounding-box data; the source example contains four numbers.

Additional Text Layer Fields

For layerType = "text", the following text styling fields may be present.

FieldTypeDescription
textstringText content.
fontFamily / fontUrl / fontZipUrlstringFont family, font file URL, and font archive URL.
fontSizenumberFont size.
colorstringText color.
bold / italic / underline / lineThroughbooleanBold, italic, underline, and strikethrough.
textAlign / writingModestringAlignment and writing mode; example values are left and horizontal-tb.
letterSpacing / lineSpacingnumberLetter and line spacing.
textBackground / textGlow / textShadow / textStrokearrayText background, glow, shadow, and stroke settings.
textGradientobjectText gradient settings containing angle and colors.

Parsing Layer Data

  1. Parse the HTTP response as JSON and confirm code = 0 and data.status = 10.
  2. Read data.result.parameters.return_json_data and check its code. If it is nonzero, read error_message.
  3. Parse its json_data string again as JSON.
  4. Iterate through templateConf[].layers[]. Read url for image layers and text and font styles for text layers. Use position and transform fields such as left, top, width, height, and rotate.

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

CodeMessageDescription
11001PARAMS_INVALIDInvalid parameters.
11002PROCESS_ERRORProcessing 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

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