完善生图5.0的功能
This commit is contained in:
1 parent
88a4268d40
commit
678d213f40
31 files changed
+3281
-107
No files matched your search
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user