1f5605331f
抽出编辑器共享类型、画布模型、生成模型和导出模型 补充模型层单测覆盖素材、吸附、生成快照和导出规则 新增前端拆分计划并更新 TRACKING 浏览器回归记录
128 lines
3.5 KiB
TypeScript
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,
|
|
},
|
|
};
|
|
}
|