完善生图5.0的功能

This commit is contained in:
andy committed 2026-08-25 14:06:04 +08:00
1 parent 88a4268d40
commit 678d213f40
31 files changed
+3281 -107

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
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<Blob>((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;
}