- 新增 /api/extend 路由,集成阿里云Dashscope图像拓展API - 在系统设置'本地AI设置'中添加图像拓展API配置(API密钥、X/Y轴放大倍数) - 在图片标注组件中添加'图像拓展'按钮(橙色,Expand图标) - 在聊天对话图片上添加'图像拓展'按钮,支持动态占位卡片 - 更新ImageProcessingLoading组件支持extend类型(橙色主题) - 处理后的图片上传到S3,保存到数据库(modelId: extend-1.0) - 同步到我的相册和出图记录 - 添加完整的API调用文档
14 KiB
14 KiB
🎨 图像拓展功能 - API调用文档
📋 功能概述
图像拓展(Image Out-painting)功能使用阿里云 Dashscope API,可以将图片智能扩展,自动填充边缘区域,实现图片尺寸的放大。
🔧 API配置
环境变量
# 阿里云 Dashscope API Key(用于图像拓展)
GENERATE_BG_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
说明: 此配置与背景生成功能共用同一个 API Key。
🌐 API端点
1. 提交拓展任务
端点: https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting
方法: POST
请求头:
Content-Type: application/json
Authorization: Bearer {GENERATE_BG_API_KEY}
X-DashScope-Async: enable
说明: X-DashScope-Async: enable 是必需的,用于启用异步模式。
2. 查询任务状态
端点: https://dashscope.aliyuncs.com/api/v1/tasks/{taskId}
方法: GET
请求头:
Authorization: Bearer {GENERATE_BG_API_KEY}
📤 请求参数
请求体结构
{
"model": "image-out-painting",
"input": {
"image_url": "https://example.com/image.jpg" // 方式1:公网URL
// 或
"image_base64": "iVBORw0KGgoAAAANSUhEUgAA..." // 方式2:Base64编码(推荐)
},
"parameters": {
"x_scale": 1.5, // X轴放大倍数(1.0-2.0)
"y_scale": 1.5, // Y轴放大倍数(1.0-2.0)
"best_quality": false, // 是否使用最佳质量(true/false)
"limit_image_size": true, // 是否限制图片大小
"add_watermark": false // 是否添加水印(建议false)
}
}
参数说明
| 参数 | 类型 | 必填 | 说明 | 默认值 |
|---|---|---|---|---|
model |
string | 是 | 固定值 "image-out-painting" |
- |
input.image_url |
string | 否* | 图片的公网URL(需可访问) | - |
input.image_base64 |
string | 否* | 图片的Base64编码 | - |
parameters.x_scale |
number | 否 | X轴放大倍数(1.0-2.0) | 1.5 |
parameters.y_scale |
number | 否 | Y轴放大倍数(1.0-2.0) | 1.5 |
parameters.best_quality |
boolean | 否 | 是否使用最佳质量 | false |
parameters.limit_image_size |
boolean | 否 | 是否限制图片大小 | true |
parameters.add_watermark |
boolean | 否 | 是否添加水印 | false |
注意: input.image_url 和 input.image_base64 二选一,推荐使用 image_base64。
📥 响应格式
提交任务响应
成功响应:
{
"output": {
"task_id": "task-abc123def456",
"task_status": "PENDING"
}
}
失败响应:
{
"code": "InvalidParameter",
"message": "image_base64 is required"
}
查询任务状态响应
处理中:
{
"output": {
"task_id": "task-abc123def456",
"task_status": "RUNNING"
}
}
成功:
{
"output": {
"task_id": "task-abc123def456",
"task_status": "SUCCEEDED",
"output_image_url": "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/..."
}
}
失败:
{
"output": {
"task_id": "task-abc123def456",
"task_status": "FAILED",
"message": "任务处理失败",
"code": "InternalError"
}
}
🔄 任务状态
| 状态 | 说明 | 处理方式 |
|---|---|---|
PENDING |
等待处理 | 继续轮询 |
RUNNING |
处理中 | 继续轮询 |
SUCCEEDED |
成功 | 获取结果URL |
FAILED |
失败 | 抛出错误 |
💻 代码实现示例
TypeScript/Node.js 实现
import { NextRequest, NextResponse } from "next/server";
import sharp from "sharp";
/**
* 图像拓展核心实现
* @param imageBase64 - Base64编码的图片数据
* @param apiKey - Dashscope API Key
* @returns 拓展后的图片URL
*/
async function extendImage(imageBase64: string, apiKey: string): Promise<string> {
// 1. 提交拓展任务
const requestBody = {
model: "image-out-painting",
input: {
image_base64: imageBase64,
},
parameters: {
x_scale: 1.5,
y_scale: 1.5,
best_quality: false,
limit_image_size: true,
add_watermark: false,
},
};
const response = await fetch(
"https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-DashScope-Async": "enable",
},
body: JSON.stringify(requestBody),
}
);
const initialResult = await response.json();
if (initialResult.code) {
throw new Error(
`图像拓展API错误: ${initialResult.message || JSON.stringify(initialResult)}`
);
}
// 2. 获取任务ID
const taskId = initialResult.output.task_id;
const taskStatusUrl = `https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`;
// 3. 等待3秒后开始轮询
await new Promise((resolve) => setTimeout(resolve, 3000));
// 4. 轮询获取结果
let attempts = 0;
const maxAttempts = 60; // 最多轮询60次
const pollInterval = 2000; // 每2秒轮询一次
while (attempts < maxAttempts) {
const statusResponse = await fetch(taskStatusUrl, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
});
const result = await statusResponse.json();
if (result.output?.task_status === "SUCCEEDED") {
return result.output.output_image_url;
} else if (result.output?.task_status === "FAILED") {
throw new Error(
`图像拓展任务失败: ${result.output.message || result.output.code || "未知错误"}`
);
}
attempts++;
if (attempts < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, pollInterval));
}
}
throw new Error(`图像拓展任务超时(等待${maxAttempts * 2 + 3}秒)`);
}
/**
* 完整的图像拓展流程(包含图片上传到S3)
*/
export async function POST(request: NextRequest) {
try {
// 1. 获取用户会话
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 });
}
// 2. 获取API配置
const apiKey = process.env.GENERATE_BG_API_KEY;
if (!apiKey) {
return NextResponse.json(
{ error: "图像拓展API配置未设置" },
{ status: 400 }
);
}
// 3. 获取请求体(Base64图片)
const contentType = request.headers.get("content-type") || "";
let imageBase64: string;
if (contentType.includes("application/json")) {
const body = await request.json();
imageBase64 = body.image_base64;
} else if (contentType.includes("multipart/form-data")) {
const formData = await request.formData();
const file = formData.get("file") as File;
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
imageBase64 = buffer.toString("base64");
} else {
return NextResponse.json(
{ error: "不支持的Content-Type" },
{ status: 400 }
);
}
if (!imageBase64) {
return NextResponse.json(
{ error: "缺少图片数据" },
{ status: 400 }
);
}
// 4. 调用图像拓展API
const extendedImageUrl = await extendImage(imageBase64, apiKey);
// 5. 下载拓展后的图片
const imageResponse = await fetch(extendedImageUrl);
if (!imageResponse.ok) {
throw new Error(`下载拓展图片失败: ${imageResponse.status}`);
}
const arrayBuffer = await imageResponse.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
// 6. 上传到S3(可选)
// const { uploadFile } = await import("@/lib/storage/service");
// const thumbnailBuffer = await createThumbnail(buffer, "image/jpeg");
// const [thumbnailResult, originalResult] = await Promise.all([
// uploadFile(`extend-thumb-${Date.now()}.jpg`, thumbnailBuffer),
// uploadFile(`extend-orig-${Date.now()}.jpg`, buffer),
// ]);
// 7. 保存到数据库(可选)
// await db.insert(generatedImage).values({
// userId: session.user.id,
// thumbnailUrl: thumbnailResult.url,
// originalUrl: originalResult.url,
// prompt: "图像拓展处理",
// modelId: "extend-1.0",
// });
// 8. 返回结果
return NextResponse.json({
success: true,
original_url: extendedImageUrl, // 或 originalResult.url
thumbnail_url: extendedImageUrl, // 或 thumbnailResult.url
// base64: buffer.toString("base64"), // 可选:返回Base64
});
} catch (error) {
console.error("[Extend API] 错误:", error);
return NextResponse.json(
{
error: "图像拓展失败",
detail: error instanceof Error ? error.message : String(error),
},
{ status: 500 }
);
}
}
🔄 使用公网URL方式(备选方案)
如果不想使用Base64,可以先将图片上传到S3/MinIO,然后使用公网URL:
// 1. 上传图片到S3/MinIO获取公网URL
const imageUrl = await uploadToS3(imageBuffer);
// 2. 使用URL调用API
const requestBody = {
model: "image-out-painting",
input: {
image_url: imageUrl, // 使用URL而不是Base64
},
parameters: {
x_scale: 1.5,
y_scale: 1.5,
best_quality: false,
limit_image_size: true,
add_watermark: false,
},
};
注意: 公网URL必须可访问,且不能是localhost或内网地址。
⚙️ 配置参数说明
放大倍数
- x_scale / y_scale: 1.0 - 2.0
1.0: 不放大1.5: 放大1.5倍(推荐)2.0: 放大2倍(最大)
质量设置
- best_quality:
true: 最佳质量,处理时间更长false: 标准质量,处理时间较短(推荐)
水印设置
- add_watermark:
true: 添加水印(不推荐)false: 不添加水印(推荐)
⏱️ 性能指标
| 指标 | 值 | 说明 |
|---|---|---|
| 单张处理时间 | 5-15秒 | 取决于图片大小和质量设置 |
| 最大图片大小 | 4MB | 原始图片大小限制 |
| 支持格式 | JPG, PNG, WebP | 常见图片格式 |
| 并发限制 | 5个/秒 | 根据账户类型 |
| 轮询间隔 | 2秒 | 建议值 |
| 最大轮询次数 | 60次 | 总计约120秒超时 |
🛡️ 错误处理
常见错误
| 错误码 | 说明 | 解决方案 |
|---|---|---|
InvalidParameter |
参数错误 | 检查图片格式和参数值 |
Arrearage |
配额不足 | 充值或升级账户 |
Throttling.RateQuota |
请求过快 | 降低请求频率 |
InternalError |
内部错误 | 稍后重试 |
submit algo service error |
API服务问题 | 稍后重试 |
重试机制
建议实现3次重试,每次重试间隔递增:
async function extendImageWithRetry(
imageBase64: string,
apiKey: string,
maxRetries = 3
): Promise<string> {
let lastError: Error;
for (let retry = 0; retry < maxRetries; retry++) {
try {
return await extendImage(imageBase64, apiKey);
} catch (error) {
lastError = error as Error;
console.error(`第${retry + 1}次拓展尝试失败:`, error);
if (retry < maxRetries - 1) {
const waitTime = (retry + 1) * 5; // 5秒、10秒
await new Promise((resolve) => setTimeout(resolve, waitTime * 1000));
}
}
}
throw new Error(`图像拓展失败(已重试${maxRetries}次): ${lastError.message}`);
}
📊 完整流程示例
用户上传图片
↓
转换为Base64(或上传到S3获取URL)
↓
提交拓展任务到Dashscope API
↓
获取task_id
↓
等待3秒(API初始化)
↓
轮询任务状态(每2秒一次,最多60次)
↓
任务完成,获取output_image_url
↓
下载拓展后的图片
↓
上传到S3(可选)
↓
保存到数据库(可选)
↓
返回结果给前端
🔍 测试方法
1. 使用curl测试
# 准备Base64图片
IMAGE_BASE64=$(base64 -i test.jpg)
# 提交任务
curl -X POST \
https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $GENERATE_BG_API_KEY" \
-H "X-DashScope-Async: enable" \
-d "{
\"model\": \"image-out-painting\",
\"input\": {
\"image_base64\": \"$IMAGE_BASE64\"
},
\"parameters\": {
\"x_scale\": 1.5,
\"y_scale\": 1.5,
\"best_quality\": false,
\"limit_image_size\": true,
\"add_watermark\": false
}
}"
# 查询任务状态(替换task_id)
curl -X GET \
https://dashscope.aliyuncs.com/api/v1/tasks/{task_id} \
-H "Authorization: Bearer $GENERATE_BG_API_KEY"
2. 使用Node.js测试脚本
参考 第三方源码参考/test-image-extend-final.js
📝 注意事项
- API Key安全: 不要在客户端代码中暴露API Key,应在服务端调用
- 图片大小: 建议先压缩图片到4MB以下再调用API
- 异步处理: 必须使用异步模式(
X-DashScope-Async: enable) - 轮询超时: 建议设置合理的超时时间(120秒)
- 错误处理: 实现重试机制以提高成功率
- 配额限制: 注意API调用配额,避免超出限制
🔗 相关资源
✅ 总结
图像拓展功能使用阿里云Dashscope API,支持Base64和URL两种输入方式,采用异步模式处理,需要轮询获取结果。建议使用Base64方式,简化流程,提高可靠性。
关键点:
- ✅ 使用
GENERATE_BG_API_KEY环境变量 - ✅ 必须设置
X-DashScope-Async: enable - ✅ 推荐使用Base64输入方式
- ✅ 实现轮询和重试机制
- ✅ 处理时间约5-15秒/张