live
MODEL IDbytedance:seedream@5.5

Seedream 5.5

ByteDance
by

Seedream 5.5 is ByteDance's image generation and editing model, focused on visual realism, aesthetics and editing consistency. It supports text-to-image and reference-guided editing with up to 10 input images, keeps unedited areas stable across multi-turn edits, and outputs transparent images from a prompt or from references. It can also split an image into a base image and up to 99 editable layers, returning the text, position, font and style of each text layer, which suits poster, packaging and marketing design workflows.

Seedream 5.5

Layer decomposition

How to split one image into a base image and transparent layers with Seedream 5.5, from layer positions and split size to reading the text on text layers.

Introduction

A finished design is one flat bitmap. Moving the headline or translating the copy means regenerating the whole image and hoping the rest survives, or rebuilding it by hand in a design tool.

settings.layerDecomposition turns that bitmap back into parts. Seedream 5.5 takes one image and returns an opaque base image plus transparent PNG layers, each with its position on the canvas, its place in the stack, and a short name and description.

Each part arrives as its own file, so an animator can move the dog without touching the girl, and a layout can push the kite higher without regenerating the page.

This guide covers the request, what each layer carries, putting the layers back together, setting how finely the image splits, steering the split with a prompt and reading the text on text layers.

The request

A decomposition request is one reference image and one setting. The prompt is optional.

Try in Playground
import { createClient } from '@runware/sdk'

const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY })
await client.connect()

const [result] = await client.run({
  model: 'bytedance:seedream@5.5',
  inputs: {
    referenceImages: [
      'https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-kite.jpg'
    ]
  },
  settings: {
    layerDecomposition: true,
    layerDecompositionMaxImages: 6
  },
  outputFormat: 'PNG'
})
import asyncio
import os

from runware import Runware


async def main():
    async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client:
        results = await client.run({
            "model": "bytedance:seedream@5.5",
            "inputs": {
                "referenceImages": [
                    "https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-kite.jpg"
                ]
            },
            "settings": {
                "layerDecomposition": True,
                "layerDecompositionMaxImages": 6
            },
            "outputFormat": "PNG"
        })


asyncio.run(main())
curl https://api.runware.ai/v1 \
  -H "Authorization: Bearer $RUNWARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "taskType": "imageInference",
      "taskUUID": "4c8e1f2a-7b3d-4e9a-a1c6-5d2f8b0e9c37",
      "model": "bytedance:seedream@5.5",
      "inputs": {
        "referenceImages": [
          "https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-kite.jpg"
        ]
      },
      "settings": {
        "layerDecomposition": true,
        "layerDecompositionMaxImages": 6
      },
      "outputFormat": "PNG"
    }
  ]'
runware run bytedance:seedream@5.5 \
  inputs.referenceImages.0=https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-kite.jpg \
  settings.layerDecomposition=true \
  settings.layerDecompositionMaxImages=6 \
  outputFormat=PNG
{
  "taskType": "imageInference",
  "taskUUID": "4c8e1f2a-7b3d-4e9a-a1c6-5d2f8b0e9c37",
  "model": "bytedance:seedream@5.5",
  "inputs": {
    "referenceImages": [
      "https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-kite.jpg"
    ]
  },
  "settings": {
    "layerDecomposition": true,
    "layerDecompositionMaxImages": 6
  },
  "outputFormat": "PNG"
}
Response
[
  {
    "taskType": "imageInference",
    "taskUUID": "4c8e1f2a-7b3d-4e9a-a1c6-5d2f8b0e9c37",
    "imageUUID": "9a3f6c2e-1d4b-4e8a-b7c5-0f2e8d6a4b13",
    "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/9a3f6c2e-1d4b-4e8a-b7c5-0f2e8d6a4b13.png",
    "outputs": {
      "layers": [
        {
          "uuid": "819e7e67-b36d-4b20-9a42-7584f8027824",
          "url": "https://im.runware.ai/image/os/a14d18/ws/2/ii/819e7e67-b36d-4b20-9a42-7584f8027824.png",
          "zIndex": 1,
          "boundingBox": {
            "absolute": [0, 115, 1257, 1110],
            "normalized": [0, 56, 614, 542]
          },
          "name": "Red diamond kite with ribbons",
          "description": "A red diamond-shaped kite fitted with red-orange two-tone streamers and a pink kite line."
        },
        {
          "uuid": "14941738-c0ca-4a3f-b764-e0746bc85a34",
          "url": "https://im.runware.ai/image/os/a14d18/ws/2/ii/14941738-c0ca-4a3f-b764-e0746bc85a34.png",
          "zIndex": 2,
          "boundingBox": {
            "absolute": [528, 710, 1178, 1563],
            "normalized": [258, 347, 575, 763]
          },
          "name": "Running little girl holding kite line",
          "description": "A running little girl wearing a yellow hooded coat, a white dress with red floral patterns, and red rain boots, holding the kite line in her hand."
        }
      ]
    }
  }
]
  • Exactly one image goes in inputs.referenceImages, as a PNG or a JPEG holding at least 262,144 pixels, the area of 512 × 512.
  • positivePrompt can be left out, and then the model plans the split on its own. A prompt only steers which parts become layers, as shown in Steering the split with a prompt.
  • width and height are rejected in this mode, and so is settings.background with any value, because the base is always opaque and every layer transparent.
  • resolution takes a tier. Leave it out and the output keeps the size of the source when the source holds between 921,600 and 4,624,220 pixels. A smaller source comes back at 1K and a larger one at 2K, both with the source's aspect ratio.

outputFormat sets the format of the base image only. The layers are always PNG, since they carry an alpha channel.

What each layer carries

The base image comes back where every image result does, in imageURL. The layers come back in outputs.layers, one entry per layer with its file and where it belongs.

FieldWhat it holds
imageURLThe base image, opaque, at the bottom of the stack
outputs.layers[].urlOne part of the image as a PNG with an alpha channel
outputs.layers[].zIndexThe stacking order. The base sits at 0, and a higher number draws on top
outputs.layers[].boundingBox.absoluteThe layer's box on the base image in pixels, as [left, top, right, bottom]
outputs.layers[].boundingBox.normalizedThe same box on a 0 to 1000 scale
outputs.layers[].nameA short label written by the model
outputs.layers[].descriptionA sentence describing what the layer shows

Each layer file holds only its own box, at its own resolution. A part that covers about a megapixel or more comes back at the size of its box, and a smaller one comes back enlarged: the dog's box above is 507 × 405 pixels, and its PNG is 1146 × 915. Scale each layer to the size of its boundingBox.absolute before placing it, and use normalized to place it on a canvas of a different size.

name and description are written by the model for each image. They help match a layer to a role in a template, such as finding the headline or the product, but they are free text rather than a fixed vocabulary, so match them loosely. Their language is not fixed either. A layer that merges several parts, which happens more at a low maximum, can get a description partly in Chinese, so translate or clean the text before showing it to a user.

Putting the layers back together

Draw the base, then each layer scaled to its box and placed at the box's left and top, taken from boundingBox.absolute, in zIndex order. The stack rebuilds the source.

A cream dog-treat tin labeled GOOD BOY BITES, a terracotta bowl embossed GOOD BOY full of bone-shaped biscuits and a red rubber ball on a pale mint backdrop with a potted plant and a small pumpkin behind, with the box of each layer drawn over it

Photoreal product packaging mockup for a dog-treat brand on a pale mint seamless studio background. A round cream-colored metal tin with a domed lid stands slightly left of center, its printed label reading "GOOD BOY BITES" in bold navy sans-serif with "Peanut butter & pumpkin" in smaller type underneath. To its right, a small terracotta ceramic dog bowl filled with bone-shaped biscuits. In the front left, a red rubber ball. Soft even studio light from the upper left, soft natural shadows under each object, three-quarter eye-level view, crisp detail.

The same tin, bowl and ball on the mint backdrop, rebuilt by drawing each layer over the base image at the size and position of its box
Source and layer boxesBase plus layers

With the response array in tasks, sharp does the whole stack in one call:

import sharp from 'sharp'

const download = async (url: string) => Buffer.from(await (await fetch(url)).arrayBuffer())

const [result] = tasks
const layers = [...result.outputs.layers].sort((a, b) => a.zIndex - b.zIndex)

const rebuilt = await sharp(await download(result.imageURL))
  .composite(await Promise.all(layers.map(async (layer) => {
    const [left, top, right, bottom] = layer.boundingBox.absolute
    const input = await sharp(await download(layer.url))
      .resize(right - left, bottom - top, { fit: 'fill' })
      .toBuffer()

    return { input, left, top }
  })))
  .png()
  .toBuffer()

Change a layer's left and top before compositing to move it, or skip a layer to take it out of the picture. The base fills in what the layers covered, so taking a part out leaves backdrop rather than a hole.

Setting the number of layers

settings.layerDecompositionMaxImages caps the images in the response, the base included, so 4 means a base and at most three layers. Left out, it is 30.

The same social ad split twice, once with a maximum of 4 and once with 16:

A higher maximum gives a finer split. At 4 the model returned three layers and grouped parts that sit near each other: the bowl with the button under it, and the logo with the napkin and the board. At 16 it returned eight, with the headline, the logo, the bowl, two strawberries, the napkin, the board and the button each on its own, and the scattered oats, almonds and blueberries left in the base.

The maximum is a ceiling, not a target, and the model can return fewer layers than it allows. A maximum below the number of parts also splits less cleanly: at 4, the base kept its own copy of the logo, the button and the board.

At a maximum that low, the grouping also changes from run to run, so a retry can come back split differently. Two more runs at 4 kept the headline on its own but put the button with the napkin instead of the bowl, and one of them put the logo with the bowl. With room for every part, three runs of the room in the next section returned the same six layers, and only the wording of name and description changed.

Every image in the response is billed as a separate output image, the base and each layer, so the maximum also caps what a request can cost. Set it to the number of parts your edit needs, plus one for the base.

Steering the split with a prompt

Without a prompt, the model plans the split from the image and the maximum. A prompt that names the parts takes over that plan. The room below was split twice with the same maximum of 10.

Without a prompt the room came back as six layers over an empty room. The prompted split returns one layer for the sofa and a base with the rest of the room, which is the starting point for virtual staging: drop a different sofa onto the base, or restyle the sofa layer and put it back.

Name every part you want as its own layer, and leave the base out of the prompt, since everything you do not name stays there. Ending a prompt on this room with "Leave everything else in the base image" returned, in four runs out of five, an extra opaque layer that covers the whole frame and nearly repeats the base, billed as one more image.

Keep the maximum above the number of parts you name, because a prompt that asks for more layers than the maximum allows loses some of what it names.

A part can also be pointed at by its position instead of its name, which suits an editor where the user drags a box over it. Put the box in the prompt between <bbox> tags, as left, top, right and bottom on the 0 to 1000 scale of boundingBox.normalized:

Separate the regions at <bbox>308 484 438 570</bbox> and <bbox>566 484 694 570</bbox> into two separate layers, one per region.

On this room the two boxes returned each mustard cushion as its own layer, the same split that naming the cushions gave, with the sofa filled in behind them on the base.

Reading text with text recognition

settings.layerDecompositionTextRecognition adds a textRecognition object to every layer the model reads as typeset text: the full string, the box of each line, and runs of characters that share a style. It turns a flat flyer into copy a localization pipeline can translate and set again.

The price line comes back as one layer with two runs, because the color changes partway through it:

Try in Playground
import { createClient } from '@runware/sdk'

const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY })
await client.connect()

const [result] = await client.run({
  model: 'bytedance:seedream@5.5',
  inputs: {
    referenceImages: [
      'https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-flyer.jpg'
    ]
  },
  settings: {
    layerDecomposition: true,
    layerDecompositionMaxImages: 12,
    layerDecompositionTextRecognition: true
  },
  outputFormat: 'PNG'
})
import asyncio
import os

from runware import Runware


async def main():
    async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client:
        results = await client.run({
            "model": "bytedance:seedream@5.5",
            "inputs": {
                "referenceImages": [
                    "https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-flyer.jpg"
                ]
            },
            "settings": {
                "layerDecomposition": True,
                "layerDecompositionMaxImages": 12,
                "layerDecompositionTextRecognition": True
            },
            "outputFormat": "PNG"
        })


asyncio.run(main())
curl https://api.runware.ai/v1 \
  -H "Authorization: Bearer $RUNWARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "taskType": "imageInference",
      "taskUUID": "b6d2a8f4-3e1c-4a97-9c05-7f8e2d1b4a63",
      "model": "bytedance:seedream@5.5",
      "inputs": {
        "referenceImages": [
          "https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-flyer.jpg"
        ]
      },
      "settings": {
        "layerDecomposition": true,
        "layerDecompositionMaxImages": 12,
        "layerDecompositionTextRecognition": true
      },
      "outputFormat": "PNG"
    }
  ]'
runware run bytedance:seedream@5.5 \
  inputs.referenceImages.0=https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-flyer.jpg \
  settings.layerDecomposition=true \
  settings.layerDecompositionMaxImages=12 \
  settings.layerDecompositionTextRecognition=true \
  outputFormat=PNG
{
  "taskType": "imageInference",
  "taskUUID": "b6d2a8f4-3e1c-4a97-9c05-7f8e2d1b4a63",
  "model": "bytedance:seedream@5.5",
  "inputs": {
    "referenceImages": [
      "https://assets.runware.ai/guides/bytedance-seedream-5-5/layer-decomposition/source-flyer.jpg"
    ]
  },
  "settings": {
    "layerDecomposition": true,
    "layerDecompositionMaxImages": 12,
    "layerDecompositionTextRecognition": true
  },
  "outputFormat": "PNG"
}
Response
[
  {
    "taskType": "imageInference",
    "taskUUID": "b6d2a8f4-3e1c-4a97-9c05-7f8e2d1b4a63",
    "imageUUID": "725a544b-0fe7-4099-ac45-71432b56a900",
    "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/725a544b-0fe7-4099-ac45-71432b56a900.png",
    "outputs": {
      "layers": [
        {
          "uuid": "04890230-b75b-4245-b81a-d6445a66dc70",
          "url": "https://im.runware.ai/image/os/a14d18/ws/2/ii/04890230-b75b-4245-b81a-d6445a66dc70.png",
          "zIndex": 3,
          "boundingBox": {
            "absolute": [200, 301, 1334, 685],
            "normalized": [130, 147, 868, 334]
          },
          "name": "Red bold $2 TACOS and white bold ALL NIGHT",
          "description": "Bold red text \"$2 TACOS\" paired with bold white text \"ALL NIGHT\"",
          "textRecognition": {
            "text": "$2 TACOS ALL NIGHT",
            "lines": [
              {
                "runs": [
                  {
                    "text": "$2 TACOS ",
                    "style": {
                      "similarFontFamily": "Indivisible",
                      "foregroundColor": "#EB3234",
                      "bold": true,
                      "italic": false,
                      "underline": false,
                      "strikethrough": false,
                      "shadow": false,
                      "translucent": false
                    }
                  },
                  {
                    "text": "ALL NIGHT",
                    "style": {
                      "similarFontFamily": "Indivisible",
                      "foregroundColor": "#F4F4F2",
                      "bold": true,
                      "italic": false,
                      "underline": false,
                      "strikethrough": false,
                      "shadow": false,
                      "translucent": false
                    }
                  }
                ],
                "boundingBox": {
                  "absolute": [354, 442, 1178, 544],
                  "normalized": [230, 216, 767, 266]
                }
              }
            ]
          }
        }
      ]
    }
  }
]
  • text is the whole layer as plain text, with lines joined by \n, ready to send to translation. The hours line and the web address on this flyer came back as one layer, so its text holds both lines.
  • lines[].boundingBox places each line on the base, in the same absolute and normalized form as a layer's box.
  • runs[] splits a line wherever the style changes, and each style holds the font, the color as #RRGGBB and six flags: bold, italic, underline, strikethrough, shadow and translucent.

To localize the flyer, translate text, then set each line in its box on the base, in a font close to similarFontFamily and the run's foregroundColor. The image layers stay as they are.

similarFontFamily is the closest match from the fonts the model recognizes, not the font the design used, and it reads unknown when the model is not confident. Map it to a font you license before setting type.

The style flags describe the letters themselves. A layer rotated on the canvas does not read as italic, and a solid light color does not read as translucent. Check the flags against the image before reusing them: the web address on this flyer is underlined, and it came back with underline set to false here and on an earlier version of the same flyer.

Only typeset text gets textRecognition. Hand-lettered and artistic text get none, and small or heavily stylized type may get none either, so the brush script on the flyer comes back as an image layer without it. Check for the field before reading it.

Tips

  1. Name the parts you want. Without a prompt the model decides the split. A prompt that lists the parts gets that split instead, and everything it does not name stays in the base.

  2. Size the maximum to the edit. It counts the base, and every image that comes back is billed, so set it to the parts you need plus one.

  3. Read the layer count from the response. The model can return fewer layers than the maximum, so loop over outputs.layers instead of expecting a fixed number.

  4. Scale each layer to its box. A small part comes back enlarged, so resize it to the size of boundingBox.absolute before placing it at the box's left and top.

  5. Check for text recognition first. Only layers read as typeset text carry textRecognition, so test for it before reading it.

  6. Treat the font and the flags as hints. Map similarFontFamily to a licensed font, and check the style flags against the image.

  7. Retry the whole request when it fails. If one layer fails to generate, the request fails, and nothing partial comes back.