export type SeedreamLayerCompositionItem = { url: string; visible: boolean; box: [number, number, number, number]; }; export async function downloadSeedreamLayerComposition(options: { items: SeedreamLayerCompositionItem[]; width?: number; height?: number; fileName: string; }) { if (!options.items.some((item) => item.visible)) throw new Error("请至少显示一个图层后再导出"); const decoded: Array<{ item: SeedreamLayerCompositionItem; bitmap: ImageBitmap }> = []; try { for (const item of options.items) decoded.push({ item, bitmap: await fetchBitmap(item.url) }); const reference = decoded[0].bitmap; const width = positiveDimension(options.width) || reference.width; const height = positiveDimension(options.height) || reference.height; const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const context = canvas.getContext("2d"); if (!context) throw new Error("当前浏览器无法创建合成画布"); context.clearRect(0, 0, width, height); for (const { item, bitmap } of decoded) { if (!item.visible) continue; const [left, top, right, bottom] = item.box; context.drawImage( bitmap, left / 1000 * width, top / 1000 * height, Math.max(1, (right - left) / 1000 * width), Math.max(1, (bottom - top) / 1000 * height) ); } const blob = await canvasBlob(canvas, "image/png"); downloadBlob(blob, options.fileName); } finally { for (const { bitmap } of decoded) bitmap.close(); } } async function fetchBitmap(url: string) { const response = await fetch(sameOriginBinaryURL(url), { credentials: "same-origin" }); if (!response.ok) throw new Error(`读取图层失败(HTTP ${response.status})`); return createImageBitmap(await response.blob()); } function sameOriginBinaryURL(url: string) { if (!url.startsWith("/api/assets/") || !url.includes("/download")) return url; const [path, rawQuery = ""] = url.split("?", 2); const query = new URLSearchParams(rawQuery); query.delete("inline"); const suffix = query.toString(); return suffix ? `${path}?${suffix}` : path; } function canvasBlob(canvas: HTMLCanvasElement, type: string) { return new Promise((resolve, reject) => { canvas.toBlob((blob) => { if (blob) resolve(blob); else reject(new Error("生成合成图片失败")); }, type); }); } function downloadBlob(blob: Blob, fileName: string) { const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = fileName; document.body.appendChild(anchor); anchor.click(); anchor.remove(); window.setTimeout(() => URL.revokeObjectURL(url), 0); } function positiveDimension(value?: number) { return value && Number.isFinite(value) && value > 1 ? Math.round(value) : undefined; }