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; }