> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apimart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Grok Imagine 2.0 Ext 图层与选区编辑

> 使用 segment 获取对象图层和精确遮罩，并通过 region_edit 按多边形、矩形或对象索引编辑指定区域。

<Info>
  `segment` 和 `region_edit` 均使用现有异步图片入口。创建任务后保存 `task_id`，再轮询 [获取任务状态](/cn/api-reference/tasks/status)；不要等待创建请求直接返回图层或图片。
</Info>

<Warning>
  不要把 API Key 写入浏览器包、LocalStorage、URL 或前端日志。浏览器应调用业务 BFF，由服务端持有 Key 并转发请求。
</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.apimart.ai/v1/images/generations \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --header 'Accept: application/json' \
    --header 'Idempotency-Key: 6baf0940-25d6-4ec2-9131-925250840fa7' \
    --data '{
      "model": "grok-imagine-2.0-ext",
      "operation": "segment",
      "source_task_id": "<COMPLETED_SINGLE_IMAGE_TASK_ID>",
      "include_mask_rle": true,
      "cached_only": false
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.apimart.ai/v1/images/generations", {
    method: "POST",
    headers: {
      Authorization: "Bearer <token>",
      "Content-Type": "application/json",
      Accept: "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      model: "grok-imagine-2.0-ext",
      operation: "region_edit",
      image_id: "<SOURCE_IMAGE_ID>",
      prompt: "把选中的跑车改成亮红色，保持其他画面不变",
      selection_regions: [
        {
          outer: {
            points: [0.12, 0.2, 0.48, 0.2, 0.48, 0.61, 0.12, 0.61],
          },
        },
      ],
    }),
  });

  console.log(response.status, await response.json());
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "code": 200,
    "data": [
      {
        "status": "submitted",
        "task_id": "task_..."
      }
    ]
  }
  ```

  ```json 400 theme={null}
  {
    "error": {
      "message": "Invalid selection regions",
      "type": "invalid_selection_regions",
      "param": "selection_regions",
      "code": "invalid_selection_regions"
    }
  }
  ```
</ResponseExample>

## 操作概览

| 操作            | 用途                 | 关键输入                            | 完成结果                             | 计费      |
| ------------- | ------------------ | ------------------------------- | -------------------------------- | ------- |
| `segment`     | 识别对象并获取图层、边界框和精确遮罩 | 已完成单图任务的 `source_task_id`       | `image_id`、`image_url`、`objects` | 免费      |
| `region_edit` | 修改多边形、矩形或对象所在区域    | segment 返回的 `image_id`、编辑提示词和选区 | 新图片 URL 和新 `image_id`            | 按完成任务结算 |

完整链路：

```text theme={null}
已完成的 Grok 单图任务 task_id
  → segment(source_task_id=task_id)
  → image_id + objects[].mask_rle
  → 前端把 mask 转为 selection_regions
  → region_edit(image_id, prompt, selection_regions)
  → 新图片 URL + 新 image_id
```

<Note>
  `source_task_id` 和 `image_id` 不可互换。`segment` 使用来源任务 ID；`region_edit` 使用图片资产 ID。编辑后重新获取图层时，将本次 `region_edit` 的 `task_id` 作为新的 `source_task_id`。
</Note>

## 请求头

<ParamField header="Authorization" type="string" required>
  使用 Bearer Token：`Bearer <APIMART_API_KEY>`。
</ParamField>

<ParamField header="Content-Type" type="string" required>
  固定为 `application/json`。
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  可选。付费的 `region_edit` 强烈建议使用。支持 1–191 个可见 ASCII 字符，推荐 UUID。

  每次新的逻辑操作使用一个新 Key。网络失败重试同一次请求时，必须复用原 Key 和完全相同的请求体。
</ParamField>

同一个 Key 的任务仍在处理时，接口可能返回 `409 idempotency_in_progress`。如果返回 `idempotency_result_indeterminate`，停止自动重提并保留原 Key；尤其不要为结果不确定的付费 `region_edit` 生成新 Key。

## 异步任务流程

创建任务成功返回 HTTP `200`，任务 ID 位于 `data[0].task_id`。随后查询：

```http theme={null}
GET https://api.apimart.ai/v1/tasks/{task_id}?language=zh
Authorization: Bearer <APIMART_API_KEY>
Accept: application/json
```

状态可能为 `pending`、`processing`、`completed` 或 `failed`。建议从 2 秒间隔开始轮询，逐步退避到最多 5 秒，并设置 10 分钟总超时。切换源图或离开页面时，使用 `AbortController` 终止旧轮询。

<Warning>
  任务查询即使返回 HTTP `200`，`data.status` 仍可能是 `failed`。必须根据 `data.status` 判断任务成败，并展示 `data.error`。
</Warning>

## 获取图层：`segment`

### 请求参数

<ParamField body="model" type="string" default="grok-imagine-2.0-ext" required>
  固定为 `grok-imagine-2.0-ext`。
</ParamField>

<ParamField body="operation" type="string" default="segment" required>
  获取图层时固定为 `segment`。
</ParamField>

<ParamField body="source_task_id" type="string" required>
  当前用户已完成的 Grok 单图任务 ID。来源任务必须成功、属于图片任务，且最终结果仅包含一张图片。

  不要同时发送 `image_id` 或 `image_index`；服务端会从来源任务解析对应资产。
</ParamField>

<ParamField body="include_mask_rle" type="boolean" default="true">
  是否返回 COCO compressed RLE。需要精确描边、画笔或多边形编辑时应显式设为 `true`。
</ParamField>

<ParamField body="cache_only" type="boolean" default="false">
  仅查询分段缓存，未命中时不回源。不能与 `refresh=true` 同时使用。
</ParamField>

<ParamField body="cached_only" type="boolean" default="false">
  传递给上游的缓存提示，不代表本地一定命中缓存。
</ParamField>

<ParamField body="refresh" type="boolean" default="false">
  强制跳过缓存重新获取。不要用于普通编辑器流程，也不能与 `cache_only=true` 同时使用。
</ParamField>

`segment` 不需要 `prompt`，也不要发送 `billing_model_name`、`n`、`size` 或 `response_format`。

### 请求示例

<Tabs>
  <Tab title="实时获取">
    ```json theme={null}
    {
      "model": "grok-imagine-2.0-ext",
      "operation": "segment",
      "source_task_id": "<COMPLETED_SINGLE_IMAGE_TASK_ID>",
      "include_mask_rle": true,
      "cached_only": false
    }
    ```
  </Tab>

  <Tab title="仅探测缓存">
    ```json theme={null}
    {
      "model": "grok-imagine-2.0-ext",
      "operation": "segment",
      "source_task_id": "<COMPLETED_SINGLE_IMAGE_TASK_ID>",
      "include_mask_rle": true,
      "cache_only": true
    }
    ```
  </Tab>
</Tabs>

使用 `cache_only=true` 时，缓存未命中仍是成功任务。根据 `cache_status` 判断：`hit` 可直接显示图层，`miss` 表示尚无缓存。不要用 `cached` 字段判断是否命中。

### 完成响应

`segment` 的 `data.result` 直接是分段结果，不包在 `images` 中：

```json theme={null}
{
  "code": 200,
  "data": {
    "id": "task_...",
    "status": "completed",
    "progress": 100,
    "cost": 0,
    "credits_cost": 0,
    "result": {
      "created": 1786784507,
      "source_task_id": "task_...",
      "image_id": "6b78a7c3-5f9e-4a3d-a928-9e9b42113a3f",
      "image_url": "https://.../source.jpg",
      "cached": false,
      "from_cache": true,
      "cache_status": "hit",
      "objects": [
        {
          "index": 0,
          "name": "red sports car",
          "box_xyxy": [38.1, 689.8, 945.8, 1065.4],
          "score": 0.9765625,
          "mask_size": [1792, 1008],
          "mask_url": "",
          "mask_rle": {
            "size": [1792, 1008],
            "counts": "..."
          }
        }
      ]
    }
  }
}
```

| 字段                    | 说明                                    |
| --------------------- | ------------------------------------- |
| `result.image_id`     | 来源图片的资产 ID，后续 `region_edit` 使用此值      |
| `result.image_url`    | 与 `image_id` 对齐的公网 HTTP(S) 图片 URL     |
| `objects[].index`     | 服务端原始对象索引；使用 `object_indices` 时必须原样保留 |
| `objects[].box_xyxy`  | mask 像素坐标 `[x1,y1,x2,y2]`             |
| `objects[].score`     | 识别置信度，可能为 `null`                      |
| `objects[].mask_size` | `[height,width]`，不可写死图片尺寸             |
| `objects[].mask_rle`  | COCO compressed RLE，精确描边的首选数据         |
| `objects[].mask_url`  | 可选的遮罩图片 URL，可能为空                      |

没有有效 `mask_rle` 或 `mask_url` 的对象只能用于矩形近似编辑，不能标记为精确图层。

## 解码 `mask_rle`

`mask_rle.counts` 是 COCO 压缩计数字符串，不是 Base64，也不是 zlib。数据按列优先顺序展开；第一个 run 表示背景像素数，此后在背景和前景之间交替。

以下 TypeScript 将结果转换为浏览器常用的行优先二值数组：

```ts theme={null}
export interface CocoRLE {
  size: [height: number, width: number];
  counts: string;
}

export interface BinaryMask {
  width: number;
  height: number;
  data: Uint8Array; // data[y * width + x]
}

function decodeCompressedCounts(counts: string): number[] {
  const runs: number[] = [];
  let cursor = 0;

  while (cursor < counts.length) {
    let value = 0;
    let shift = 0;
    let more = true;

    while (more) {
      if (cursor >= counts.length) throw new Error("截断的 COCO RLE counts");
      const current = counts.charCodeAt(cursor++) - 48;
      value |= (current & 0x1f) << shift;
      more = (current & 0x20) !== 0;
      shift += 5;
      if (!more && (current & 0x10) !== 0) value |= -1 << shift;
    }

    if (runs.length > 2) value += runs[runs.length - 2] ?? 0;
    if (value < 0) throw new Error(`非法 COCO RLE run: ${value}`);
    runs.push(value);
  }

  return runs;
}

export function decodeCocoRLE(rle: CocoRLE): BinaryMask {
  const [height, width] = rle.size;
  if (!Number.isInteger(height) || !Number.isInteger(width) || height <= 0 || width <= 0) {
    throw new Error(`非法 mask size: ${JSON.stringify(rle.size)}`);
  }

  const pixelCount = width * height;
  if (!Number.isSafeInteger(pixelCount) || pixelCount > 32_000_000) {
    throw new Error(`mask 像素数超出前端安全上限: ${pixelCount}`);
  }
  if (typeof rle.counts !== "string" || rle.counts.length === 0) {
    throw new Error("缺少 COCO RLE counts");
  }

  const data = new Uint8Array(pixelCount);
  const runs = decodeCompressedCounts(rle.counts);
  let position = 0;
  let foreground = false;

  for (const run of runs) {
    if (position + run > data.length) throw new Error("COCO RLE 超出 mask_size");
    if (foreground) {
      for (let offset = 0; offset < run; offset++) {
        const columnMajorIndex = position + offset;
        const y = columnMajorIndex % height;
        const x = (columnMajorIndex - y) / height;
        data[y * width + x] = 1;
      }
    }
    position += run;
    foreground = !foreground;
  }

  if (position !== data.length) {
    throw new Error(`COCO RLE 只覆盖 ${position}/${data.length} 个像素`);
  }

  return { width, height, data };
}
```

大尺寸遮罩建议在 Web Worker 中解码，避免阻塞主线程。不要把完整 `mask_rle.counts` 写入日志、埋点、URL 或错误上报。

### 转换为精确选区

`region_edit` 使用归一化多边形：

```ts theme={null}
export interface SelectionBoundary {
  points: number[]; // [x0,y0,x1,y1,...]，坐标范围 0～1
}

export interface SelectionRegion {
  outer: SelectionBoundary;
  holes?: SelectionBoundary[];
}
```

将二值 mask 转为多边形时，建议提取连通区域和孔洞，再用 Douglas–Peucker 简化。每个 ring 至少 3 个不同点，不能自交或面积为 0；每个图层最多保留面积最大的 16 个 region，每个 ring 最多保留 400 个点。

<Warning>
  `mask_size` 的顺序是 `[height,width]`，坐标基于原始 mask，不是 CSS 显示尺寸。使用 `object-fit: contain` 时，需要先扣除留白偏移并按实际绘制区域换算，最后将坐标限制在 `0～1`。
</Warning>

如果前端需要读取源图或 `mask_url` 的像素，图片服务器必须允许当前站点跨域访问，并且要在设置 `src` 前指定 `image.crossOrigin = "anonymous"`，或通过 `fetch` 获取 Blob。否则图片绘制到 Canvas 后会污染画布，无法再读取像素。图层轮廓优先直接解码 `mask_rle`，不依赖跨域读取源图。

## 编辑选区：`region_edit`

### 请求参数

<ParamField body="model" type="string" default="grok-imagine-2.0-ext" required>
  固定为 `grok-imagine-2.0-ext`。
</ParamField>

<ParamField body="operation" type="string" default="region_edit" required>
  编辑选区时固定为 `region_edit`。
</ParamField>

<ParamField body="image_id" type="string" required>
  当前源图的资产 ID。首次编辑使用 segment 响应中的 `result.image_id`；连续编辑使用上次编辑返回的新 `image_id`。
</ParamField>

<ParamField body="prompt" type="string" required>
  描述如何修改选区，去除首尾空格后不能为空。
</ParamField>

<ParamField body="selection_regions" type="array">
  精确多边形选区，推荐用于正式图层和画笔编辑。坐标归一化到 `0～1`，支持 `outer` 和可选 `holes`。
</ParamField>

<ParamField body="boxes" type="number[][]">
  矩形选区，每项为 `[x1,y1,x2,y2]`。可使用归一化坐标；使用像素坐标时必须同时传 `mask_size`。
</ParamField>

<ParamField body="object_indices" type="integer[]">
  segment 返回的原始 `objects[].index`。上游按对象边界框编辑，因此只适合快速联调或矩形近似模式。
</ParamField>

<ParamField body="mask_size" type="integer[]">
  像素 `boxes` 使用的 `[height,width]`。两个值都必须为正整数。
</ParamField>

`selection_regions`、`boxes`、`object_indices` 至少有一个非空。接口允许组合，但建议一次只使用一种，避免重叠选区和语义不清。

<Warning>
  不要发送 `billing_model_name`、`size`、`aspect_ratio`、`source_aspect_ratio`、`source_size` 或 `image_urls`。`n` 只能省略或设为 `1`；`claim_asset` 只能省略或设为 `false`；`response_format` 只能省略或设为 `url`；不支持 Base64 输出和 `stream=true`。
</Warning>

### 三种选区方式

<Tabs>
  <Tab title="精确多边形（推荐）">
    ```json theme={null}
    {
      "model": "grok-imagine-2.0-ext",
      "operation": "region_edit",
      "image_id": "<SOURCE_IMAGE_ID>",
      "prompt": "把选中的跑车改成亮红色，保持其他画面不变",
      "selection_regions": [
        {
          "outer": {
            "points": [0.12, 0.20, 0.48, 0.20, 0.48, 0.61, 0.12, 0.61]
          },
          "holes": [
            {
              "points": [0.30, 0.35, 0.38, 0.35, 0.38, 0.45, 0.30, 0.45]
            }
          ]
        }
      ]
    }
    ```

    `points` 可使用扁平数组，也兼容 `[[x0,y0],[x1,y1],...]`。每项必须是 `0～1` 的有限数字，每个 ring 至少包含 3 对坐标。
  </Tab>

  <Tab title="归一化矩形">
    ```json theme={null}
    {
      "model": "grok-imagine-2.0-ext",
      "operation": "region_edit",
      "image_id": "<SOURCE_IMAGE_ID>",
      "prompt": "把框内车辆改成亮红色",
      "boxes": [[0.04, 0.385, 0.938, 0.594]]
    }
    ```
  </Tab>

  <Tab title="像素矩形">
    ```json theme={null}
    {
      "model": "grok-imagine-2.0-ext",
      "operation": "region_edit",
      "image_id": "<SOURCE_IMAGE_ID>",
      "prompt": "把框内车辆改成亮红色",
      "boxes": [[40, 689.6, 945.9, 1064.4]],
      "mask_size": [1792, 1008]
    }
    ```

    每个 box 必须恰好包含 4 个非负有限数字，并满足 `x2>x1`、`y2>y1`。
  </Tab>

  <Tab title="对象索引">
    ```json theme={null}
    {
      "model": "grok-imagine-2.0-ext",
      "operation": "region_edit",
      "image_id": "<SOURCE_IMAGE_ID>",
      "prompt": "把选中的跑车改成亮红色，保持其他画面不变",
      "object_indices": [0]
    }
    ```

    索引必须来自同一 `image_id` 的 segment 响应。不要使用前端过滤、排序或分组后重新生成的数组下标。
  </Tab>
</Tabs>

### 完成响应

```json theme={null}
{
  "code": 200,
  "data": {
    "id": "task_...",
    "status": "completed",
    "progress": 100,
    "cost": 0.016,
    "credits_cost": 0.16,
    "result": {
      "images": [
        {
          "url": ["https://.../result.jpg"],
          "image_ids": ["<NEW_IMAGE_ID>"],
          "items": [
            {
              "url": "https://.../result.jpg",
              "image_id": "<NEW_IMAGE_ID>",
              "source_image_id": "<SOURCE_IMAGE_ID>",
              "role": "region_edit",
              "prompt": "把选中的跑车改成亮红色，保持其他画面不变"
            }
          ],
          "expires_at": 1787040000
        }
      ]
    }
  }
}
```

优先读取 `result.images[0].items[0]`。兼容旧响应时，可读取同一下标的 `url[0]` 和 `image_ids[0]`，但必须同时拿到可展示的 HTTP(S) URL 和新的 `image_id`。

```ts theme={null}
interface RegionEditResult {
  images?: Array<{
    url?: string[];
    image_ids?: string[];
    items?: Array<{
      url?: string;
      image_id?: string;
    }>;
  }>;
}

function readRegionEditOutput(result: RegionEditResult): {
  url: string;
  imageId: string;
} {
  const group = result.images?.[0];
  const item = group?.items?.[0];

  if (item?.url && item.image_id && /^https?:\/\//i.test(item.url)) {
    return { url: item.url, imageId: item.image_id };
  }

  if (
    group?.url?.length &&
    group.image_ids?.length === group.url.length &&
    /^https?:\/\//i.test(group.url[0]) &&
    group.image_ids[0]
  ) {
    return { url: group.url[0], imageId: group.image_ids[0] };
  }

  throw new Error("选区编辑完成，但结果缺少对齐的 HTTP(S) URL 或 image_id");
}
```

结果 URL 的有效期以 `expires_at` 为准，不要在前端硬编码固定小时数。需要长期展示时应及时下载或转存。

## 连续编辑

一次编辑完成后，应同时更新三个值并清除旧图层缓存：

```ts theme={null}
const output = readRegionEditOutput(task.data.result);

editor.sourceTaskId = task.data.id;   // 下一次 segment 使用
editor.sourceImageId = output.imageId; // 下一次 region_edit 使用
editor.sourceUrl = output.url;         // 编辑器展示
editor.layers = [];
```

* 再次获取图层：`source_task_id = 本次 region_edit 的 task_id`
* 再次编辑：`image_id = 本次 region_edit 返回的新 image_id`
* 不要把 `image_id` 传给 `segment`，也不要继续使用上一张图片的旧 `image_id`

## 错误处理

| HTTP / 状态 | 错误码                         | 常见原因                                           | 建议处理                             |
| --------- | --------------------------- | ---------------------------------------------- | -------------------------------- |
| 400       | `invalid_operation`         | `operation` 缺失或不受支持                            | 只使用 `segment` 或 `region_edit`    |
| 400       | `invalid_source_task`       | segment 来源任务不可用，或传入 `image_id` / `image_index` | 使用当前用户已完成的 Grok 单图任务 ID          |
| 400       | `conflicting_cache_flags`   | 同时使用 `cache_only` 和 `refresh`                  | 二选一                              |
| 400       | `missing_image_id`          | region edit 缺少资产 ID                            | 使用 segment 返回的 `result.image_id` |
| 400       | `empty_prompt`              | 编辑提示词为空                                        | 阻止提交并提示用户输入修改描述                  |
| 400       | `invalid_selection_regions` | 无选区、坐标、点数或 box 非法                              | 在前端校验并标记问题选区                     |
| 400       | `invalid_object_index`      | 对象索引非法                                         | 保留同一图片 segment 返回的原始索引           |
| 400       | `invalid_claim_asset`       | `claim_asset=true`                             | 省略或设为 `false`                    |
| 400       | `invalid_n`                 | `n` 不是 `1`                                     | 省略或设为 `1`                        |
| 400       | `invalid_response_format`   | 请求 Base64 等不支持格式                               | 省略或使用 `url`                      |
| 400       | `invalid_stream`            | 请求流式响应                                         | 移除 `stream`                      |
| 401 / 403 | 鉴权或权限错误                     | Key 无效或模型未开通                                   | 检查服务端 Key 和账户权限                  |
| 402       | 余额不足                        | 付费编辑无法创建                                       | 提示充值后重试                          |
| 409       | 幂等冲突                        | 同一 Key 正在处理、请求体变化或结果不确定                        | 遵循响应码，不要换 Key 自动重提               |
| 429       | 请求过多                        | 触发限流                                           | 按 `Retry-After` 退避               |
| `failed`  | `task_failed`               | 异步任务执行失败                                       | 停止轮询并展示 `data.error.message`     |

部分越界的正整数 `object_indices` 可能在创建后异步失败，因此创建请求成功不代表编辑一定成功。

## 计费说明

* `segment` 免费，完成任务的 `cost` 和 `credits_cost` 均为 `0`，但仍需正常鉴权并满足来源任务条件。
* `region_edit` 为付费操作。以任务完成响应中的 `cost` 和 `credits_cost` 为准，不要在前端硬编码价格。
* 不要发送内部计费字段 `billing_model_name`。

## 前端接入检查

* API Key 只保存在后端或 BFF。
* `segment` 只发送来源 `source_task_id`，不发送 `image_id` 或 `image_index`。
* `region_edit` 使用 segment 返回的 `image_id`，并至少提供一种选区。
* 正式图层编辑使用 `selection_regions`；`object_indices` 仅为矩形近似。
* `mask_size` 始终按 `[height,width]` 解析，并正确处理图片显示区域的缩放和留白。
* 同一次网络重试复用原 `Idempotency-Key`，收费请求结果不确定时不自动换 Key 重提。
* 任务完成后同时校验 URL 和新 `image_id`，连续编辑时清理旧图层和旧轮询。
