Files
Genarrative/src/components/image-editor/ImageCanvasExportModel.ts
T
kdletters 1f5605331f 拆分图片画布编辑器前端模型
抽出编辑器共享类型、画布模型、生成模型和导出模型

补充模型层单测覆盖素材、吸附、生成快照和导出规则

新增前端拆分计划并更新 TRACKING 浏览器回归记录
2026-06-17 01:53:59 +08:00

128 lines
3.5 KiB
TypeScript

import type {
CanvasAssetExportMetadata,
CanvasLayer,
} from './ImageCanvasEditorTypes';
export function sanitizeExportFilePart(value: string, fallback: string) {
const safeValue = value
.trim()
.replace(/[\\/:*?"<>|]+/gu, ' ')
.replace(/\s+/gu, ' ')
.trim();
return safeValue || fallback;
}
export function formatExportDate(date: Date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}${month}${day}`;
}
export function getLayerExportKey(layer: CanvasLayer) {
return (
layer.assetObjectId ||
layer.objectKey ||
layer.sourceAssetId ||
layer.sourceResourceId ||
layer.src
);
}
export function getImageExtensionFromTypeOrSrc(type: string, src: string) {
if (type.includes('jpeg') || /\.(jpe?g)(?:[?#].*)?$/iu.test(src)) {
return 'jpg';
}
if (type.includes('webp') || /\.webp(?:[?#].*)?$/iu.test(src)) {
return 'webp';
}
if (type.includes('gif') || /\.gif(?:[?#].*)?$/iu.test(src)) {
return 'gif';
}
return 'png';
}
export function dataUrlToBlob(dataUrl: string) {
const [header = '', payload = ''] = dataUrl.split(',');
const mimeMatch = /^data:([^;]+)(;base64)?$/iu.exec(header);
const type = mimeMatch?.[1] ?? 'application/octet-stream';
const isBase64 = Boolean(mimeMatch?.[2]);
const binary = isBase64
? typeof atob === 'function'
? atob(payload)
: Buffer.from(payload, 'base64').toString('binary')
: decodeURIComponent(payload);
const bytes = new Uint8Array(binary.length);
for (let index = 0; index < binary.length; index += 1) {
bytes[index] = binary.charCodeAt(index);
}
return new Blob([bytes], { type });
}
export async function readLayerImageBlob(layer: CanvasLayer) {
if (layer.src.startsWith('data:')) {
return dataUrlToBlob(layer.src);
}
const response = await fetch(layer.src);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.blob();
}
export async function blobToUint8Array(blob: Blob) {
if (typeof blob.arrayBuffer === 'function') {
return new Uint8Array(await blob.arrayBuffer());
}
return new Promise<Uint8Array>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result;
if (result instanceof ArrayBuffer) {
resolve(new Uint8Array(result));
return;
}
reject(new Error('Blob 读取失败'));
};
reader.onerror = () => reject(new Error('Blob 读取失败'));
reader.readAsArrayBuffer(blob);
});
}
export function buildLayerExportMetadata(
layer: CanvasLayer,
file: string | null,
exportError?: string,
): CanvasAssetExportMetadata['layers'][number] {
return {
layerId: layer.id,
title: layer.title,
file,
sourceType: layer.sourceType,
prompt: layer.prompt,
actualPrompt: layer.actualPrompt,
model: layer.model,
provider: layer.provider,
taskId: layer.taskId,
objectKey: layer.objectKey,
assetObjectId: layer.assetObjectId,
sourceResourceId: layer.sourceResourceId,
sourceAssetId: layer.sourceAssetId,
exportError,
canvas: {
x: layer.x,
y: layer.y,
width: layer.width,
height: layer.height,
originalWidth: layer.originalWidth,
originalHeight: layer.originalHeight,
zIndex: layer.zIndex,
groupId: layer.groupId,
hidden: layer.hidden,
locked: layer.locked,
flipX: layer.flipX,
flipY: layer.flipY,
},
};
}