Files
AI_spine/providers/image-edits.mjs
T
2026-09-20 22:22:09 +08:00

48 lines
2.7 KiB
JavaScript

import path from 'node:path';
import {imageFormat} from '../lib/images.mjs';
import {requestJson, downloadImage} from './http.mjs';
export function validateEditInputs(stage, images, mask) {
if (!images.length) throw new Error('图片编辑至少需要一张输入图');
if (images.length > 1 && !stage.capabilities.multiple_images) throw new Error('尚未确认此提供方支持多图;请先验证再启用 capabilities.multiple_images');
if (mask) {
if (!stage.capabilities.mask) throw new Error('尚未确认此提供方支持编辑掩码;不能静默忽略 --mask');
if (stage.maskEditable !== 'transparent') throw new Error('当前掩码适配仅支持透明像素为可编辑区域');
if (mask.extension !== 'png' || mask.width !== images[0].width || mask.height !== images[0].height) {
throw new Error('编辑 mask 必须为与第一张输入图同尺寸的 PNG');
}
if (![4, 6].includes(mask.bytes[25])) throw new Error('编辑 mask 必须包含 alpha 通道(透明处允许编辑),不能直接传 SAM 黑白掩码');
}
}
export async function editImage(stage, {images, mask, prompt}, options) {
validateEditInputs(stage, images, mask);
return requestJson(stage, 'images/edits', () => {
const form = new FormData();
form.append('model', stage.model);
form.append('prompt', prompt);
for (const [name, value] of Object.entries(stage.params)) {
if (value !== null && value !== undefined) form.append(name, typeof value === 'object' ? JSON.stringify(value) : String(value));
}
for (const image of images) form.append(stage.imageField, new Blob([image.bytes], {type: image.mime}), path.basename(image.file));
if (mask) form.append('mask', new Blob([mask.bytes], {type: mask.mime}), path.basename(mask.file));
return {method: 'POST', body: form};
}, options);
}
export async function decodeEditedImages(payload, stage, {fetchImpl = fetch} = {}) {
const items = payload.data || payload.images || payload.output;
if (!Array.isArray(items) || !items.length) throw new Error('图片编辑响应没有图片列表');
const decoded = [];
for (const item of items) {
let bytes;
// Some relays return both fields, with an empty b64_json for URL output.
// Only a nonempty inline value takes precedence over the download URL.
if (typeof item.b64_json === 'string' && item.b64_json.trim()) bytes = Buffer.from(item.b64_json.trim(), 'base64');
else if (typeof item.url === 'string' && item.url.trim()) bytes = await downloadImage(item.url.trim(), stage, fetchImpl);
else throw new Error('图片编辑响应缺少 b64_json/url');
decoded.push({bytes, ...imageFormat(bytes)});
}
return decoded;
}