48 lines
2.7 KiB
JavaScript
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;
|
|
}
|