Image Edit
基于原始图片编辑图片,也可通过可选的遮罩(Mask)指定编辑区域。使用 multipart/form-data 上传文件。
POST /v1/images/edits
gpt-image-2 图像编辑
- Curl
- Python
- TypeScript
- Java
- Go
- PHP
- Ruby
- C#
curl https://gw.opentoken.io/v1/images/edits \
-H "Authorization: Bearer $OPEN_TOKEN_KEY" \
-F image="@/path/to/original.png" \
-F mask="@/path/to/mask.png" \
-F model="gpt-image-2" \
-F prompt="在天空中添加一道彩虹" \
-F size="1024x1024" \
-F quality="high" \
-F output_format="png" \
-F n=1
import os
import requests
with open("original.png", "rb") as image:
files = {"image": image}
response = requests.post(
"https://gw.opentoken.io/v1/images/edits",
headers={"Authorization": f"Bearer {os.environ['OPEN_TOKEN_KEY']}"},
files=files,
data={
"model": "gpt-image-2",
"prompt": "在天空中添加一道彩虹",
"size": "1024x1024",
"quality": "high",
"output_format": "png",
"n": 1,
},
)
response.raise_for_status()
print(response.json())
import { readFile } from "node:fs/promises";
const apiKey = process.env.OPEN_TOKEN_KEY;
if (!apiKey) {
throw new Error("OPEN_TOKEN_KEY is not set");
}
const form = new FormData();
form.append("image", new Blob([await readFile("original.png")], { type: "image/png" }), "original.png");
form.append("model", "gpt-image-2");
form.append("prompt", "在天空中添加一道彩虹");
form.append("size", "1024x1024");
form.append("quality", "high");
form.append("output_format", "png");
form.append("n", "1");
const response = await fetch("https://gw.opentoken.io/v1/images/edits", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: form,
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
}
console.log(await response.json());
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
public class ImageEdit {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("OPEN_TOKEN_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalStateException("OPEN_TOKEN_KEY is not set");
}
String boundary = "----OpenToken" + UUID.randomUUID();
byte[] image = Files.readAllBytes(Path.of("original.png"));
String prefix = "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"image\"; filename=\"original.png\"\r\n"
+ "Content-Type: image/png\r\n\r\n";
String fields = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n"
+ "--" + boundary + "\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\n在天空中添加一道彩虹\r\n"
+ "--" + boundary + "\r\nContent-Disposition: form-data; name=\"size\"\r\n\r\n1024x1024\r\n"
+ "--" + boundary + "\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nhigh\r\n"
+ "--" + boundary + "\r\nContent-Disposition: form-data; name=\"output_format\"\r\n\r\npng\r\n"
+ "--" + boundary + "\r\nContent-Disposition: form-data; name=\"n\"\r\n\r\n1\r\n";
byte[] body = concat((fields + prefix).getBytes(), image,
("\r\n--" + boundary + "--\r\n").getBytes());
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://gw.opentoken.io/v1/images/edits"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException("Request failed: " + response.statusCode() + " " + response.body());
}
System.out.println(response.body());
}
private static byte[] concat(byte[]... arrays) {
int length = 0;
for (byte[] array : arrays) length += array.length;
byte[] result = new byte[length];
int offset = 0;
for (byte[] array : arrays) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
}
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
apiKey := os.Getenv("OPEN_TOKEN_KEY")
if apiKey == "" {
panic("OPEN_TOKEN_KEY is not set")
}
var body bytes.Buffer
writer := multipart.NewWriter(&body)
image, err := os.Open("original.png")
if err != nil { panic(err) }
defer image.Close()
part, err := writer.CreateFormFile("image", "original.png")
if err != nil { panic(err) }
if _, err = io.Copy(part, image); err != nil { panic(err) }
_ = writer.WriteField("model", "gpt-image-2")
_ = writer.WriteField("prompt", "在天空中添加一道彩虹")
_ = writer.WriteField("size", "1024x1024")
_ = writer.WriteField("quality", "high")
_ = writer.WriteField("output_format", "png")
_ = writer.WriteField("n", "1")
if err = writer.Close(); err != nil { panic(err) }
request, err := http.NewRequest(http.MethodPost,
"https://gw.opentoken.io/v1/images/edits", &body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+apiKey)
request.Header.Set("Content-Type", writer.FormDataContentType())
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode >= 300 {
panic(fmt.Sprintf("request failed: %s: %s", response.Status, responseBody))
}
fmt.Println(string(responseBody))
}
<?php
$apiKey = getenv('OPEN_TOKEN_KEY');
if (!$apiKey) {
throw new RuntimeException('OPEN_TOKEN_KEY is not set');
}
$ch = curl_init('https://gw.opentoken.io/v1/images/edits');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
CURLOPT_POSTFIELDS => [
'image' => new CURLFile('original.png', 'image/png', 'original.png'),
'model' => 'gpt-image-2',
'prompt' => '在天空中添加一道彩虹',
'size' => '1024x1024',
'quality' => 'high',
'output_format' => 'png',
'n' => '1',
],
]);
$body = curl_exec($ch);
if ($body === false) {
throw new RuntimeException(curl_error($ch));
}
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException("Request failed: $statusCode $body");
}
echo $body, PHP_EOL;
require 'json'
require 'net/http'
require 'securerandom'
require 'uri'
api_key = ENV.fetch('OPEN_TOKEN_KEY')
uri = URI('https://gw.opentoken.io/v1/images/edits')
boundary = "OpenToken#{SecureRandom.hex(8)}"
body = []
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n"
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\n在天空中添加一道彩虹\r\n"
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"size\"\r\n\r\n1024x1024\r\n"
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nhigh\r\n"
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"output_format\"\r\n\r\npng\r\n"
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"n\"\r\n\r\n1\r\n"
image = File.binread('original.png')
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"image\"; filename=\"original.png\"\r\nContent-Type: image/png\r\n\r\n"
body << image
body << "\r\n--#{boundary}--\r\n"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request['Authorization'] = "Bearer #{api_key}"
request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
request.body = body.join
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
raise "Request failed: #{response.code} #{response.body}"
end
puts response.body
using System.Net.Http.Headers;
var apiKey = Environment.GetEnvironmentVariable("OPEN_TOKEN_KEY");
if (string.IsNullOrWhiteSpace(apiKey))
{
throw new InvalidOperationException("OPEN_TOKEN_KEY is not set");
}
using var client = new HttpClient();
using var form = new MultipartFormDataContent();
await using var image = File.OpenRead("original.png");
using var imageContent = new StreamContent(image);
imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
form.Add(imageContent, "image", "original.png");
form.Add(new StringContent("gpt-image-2"), "model");
form.Add(new StringContent("在天空中添加一道彩虹"), "prompt");
form.Add(new StringContent("1024x1024"), "size");
form.Add(new StringContent("high"), "quality");
form.Add(new StringContent("png"), "output_format");
form.Add(new StringContent("1"), "n");
using var request = new HttpRequestMessage(
HttpMethod.Post, "https://gw.opentoken.io/v1/images/edits");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
request.Content = form;
using var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
Console.WriteLine(body);
将 /path/to/original.png 和 /path/to/mask.png 替换为本地图片路径。
mask 可以省略;使用时,遮罩图片应为带透明通道的 PNG。size 是输出尺寸,quality 是图片生成质量档位。
Python FormData 示例
import os
import requests
with open("original.png", "rb") as image, open("mask.png", "rb") as mask:
response = requests.post(
"https://gw.opentoken.io/v1/images/edits",
headers={"Authorization": f"Bearer {os.environ['OPEN_TOKEN_KEY']}"},
files={
"image": image,
"mask": mask,
},
data={
"model": "gpt-image-2",
"prompt": "在天空中添加一道彩虹",
"size": "1024x1024",
"quality": "high",
"output_format": "png",
"n": 1,
},
)
response.raise_for_status()
image_b64 = response.json()["data"][0]["b64_json"]
print(f"编辑后图片 Base64 长度: {len(image_b64)}")
请求参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| model | string | 是 | 模型 ID,本页示例使用 gpt-image-2 |
| image | file | 是 | 原始图片文件,支持 PNG、JPEG、WEBP 格式 |
| mask | file | 否 | 遮罩图片。透明区域表示需编辑的部分 |
| prompt | string | 是 | 描述编辑内容的文本 |
| size | string | 否 | 输出尺寸:1024x1024、1536x1024 或 1024x1536 |
| quality | string | 否 | 图片生成质量:low / medium / high / auto |
| output_format | string | 否 | 输出图片格式:png / jpeg / webp |
| n | integer | 否 | 生成数量 |
响应示例
{
"created": 1717800000,
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAA...",
"revised_prompt": "在原图天空中添加一道彩虹"
}
]
}