|
|
@@ -402,18 +402,19 @@ export default () => {
|
|
|
/**
|
|
|
* 将 base64 字符串或 Blob 转换为 File 对象
|
|
|
*/
|
|
|
- const dataToFile = (data: string | Blob, filename: string, mimeType?: string): File => {
|
|
|
+ const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): File => {
|
|
|
if (typeof data === 'string') {
|
|
|
- // 移除可能的 data:image/png;base64, 前缀
|
|
|
- const base64Data = data.includes('base64,') ? data.split('base64,')[1] : data
|
|
|
- const byteCharacters = atob(base64Data)
|
|
|
- const byteNumbers = new Array(byteCharacters.length)
|
|
|
- for (let i = 0; i < byteCharacters.length; i++) {
|
|
|
- byteNumbers[i] = byteCharacters.charCodeAt(i)
|
|
|
+ // 1. 通过 fetch 获取 Blob 数据
|
|
|
+ const response = await fetch(data);
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`Failed to fetch blob: ${response.statusText}`);
|
|
|
}
|
|
|
- const byteArray = new Uint8Array(byteNumbers)
|
|
|
- const blob = new Blob([byteArray], { type: mimeType || 'image/png' })
|
|
|
- return new File([blob], filename, { type: blob.type })
|
|
|
+ const blob = await response.blob();
|
|
|
+
|
|
|
+ // 2. 将 Blob 转换为 File 对象
|
|
|
+ // 如果原 Blob 有 type,会自动保留;否则可手动指定 videoMimeType
|
|
|
+ const file = new File([blob], filename, { type: videoMimeType || blob.type });
|
|
|
+ return file;
|
|
|
}
|
|
|
else if (data instanceof Blob) {
|
|
|
return new File([data], filename, { type: data.type })
|
|
|
@@ -512,11 +513,11 @@ export default () => {
|
|
|
options?: { tolerance?: number }
|
|
|
): Promise<File> => {
|
|
|
const tolerance = options?.tolerance ?? 15; // 只处理非常接近白色的像素
|
|
|
-
|
|
|
+
|
|
|
// 1. 将输入统一转为可加载的 URL
|
|
|
let imageUrl: string;
|
|
|
let needRevoke = false;
|
|
|
-
|
|
|
+
|
|
|
if (typeof data === 'string') {
|
|
|
imageUrl = data.startsWith('data:') ? data : `data:image/png;base64,${data}`;
|
|
|
} else if (data instanceof Blob) {
|
|
|
@@ -525,67 +526,67 @@ export default () => {
|
|
|
} else {
|
|
|
throw new Error('Unsupported data type: expected string or Blob');
|
|
|
}
|
|
|
-
|
|
|
+ // 2. 加载图像
|
|
|
+ const img = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
|
+ const image = new Image();
|
|
|
+ image.onload = () => resolve(image);
|
|
|
+ image.onerror = reject;
|
|
|
+ if (typeof data === 'string' && !data.startsWith('data:')) {
|
|
|
+ image.crossOrigin = 'anonymous';
|
|
|
+ }
|
|
|
+ image.src = imageUrl;
|
|
|
+ });
|
|
|
+ const canvas = document.createElement('canvas');
|
|
|
try {
|
|
|
- // 2. 加载图像
|
|
|
- const img = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
|
- const image = new Image();
|
|
|
- image.onload = () => resolve(image);
|
|
|
- image.onerror = reject;
|
|
|
- if (typeof data === 'string' && !data.startsWith('data:')) {
|
|
|
- image.crossOrigin = 'anonymous';
|
|
|
- }
|
|
|
- image.src = imageUrl;
|
|
|
- });
|
|
|
-
|
|
|
// 3. 绘制到 Canvas
|
|
|
- const canvas = document.createElement('canvas');
|
|
|
canvas.width = img.width;
|
|
|
canvas.height = img.height;
|
|
|
const ctx = canvas.getContext('2d')!;
|
|
|
ctx.drawImage(img, 0, 0);
|
|
|
-
|
|
|
+
|
|
|
// 4. 获取像素数据
|
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
const dataArray = imageData.data;
|
|
|
-
|
|
|
+
|
|
|
// 5. 遍历像素:只将白色背景区域设为透明,其他颜色原样保留
|
|
|
for (let i = 0; i < dataArray.length; i += 4) {
|
|
|
const r = dataArray[i];
|
|
|
const g = dataArray[i + 1];
|
|
|
const b = dataArray[i + 2];
|
|
|
-
|
|
|
+
|
|
|
// 计算与纯白色 (255,255,255) 的欧几里得距离
|
|
|
const dr = r - 255;
|
|
|
const dg = g - 255;
|
|
|
const db = b - 255;
|
|
|
const dist = Math.sqrt(dr * dr + dg * dg + db * db);
|
|
|
-
|
|
|
+
|
|
|
if (dist <= tolerance) {
|
|
|
// 非常接近白色 → 设为全透明
|
|
|
dataArray[i + 3] = 0;
|
|
|
}
|
|
|
// 其他所有像素(包括浅蓝、灰色、黑色等)保持原样,不修改颜色和透明度
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 6. 将修改后的像素放回 Canvas
|
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
-
|
|
|
- // 7. 导出为 PNG Blob
|
|
|
- const outputBlob = await new Promise<Blob>((resolve, reject) => {
|
|
|
- canvas.toBlob((blob) => {
|
|
|
- if (blob) resolve(blob);
|
|
|
- else reject(new Error('Canvas toBlob failed'));
|
|
|
- }, 'image/png');
|
|
|
- });
|
|
|
-
|
|
|
- // 8. 返回 File 对象
|
|
|
- return new File([outputBlob], filename, { type: 'image/png' });
|
|
|
- } finally {
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ finally {
|
|
|
if (needRevoke) {
|
|
|
URL.revokeObjectURL(imageUrl);
|
|
|
}
|
|
|
}
|
|
|
+ // 7. 导出为 PNG Blob
|
|
|
+ const outputBlob = await new Promise<Blob>((resolve, reject) => {
|
|
|
+ canvas.toBlob((blob) => {
|
|
|
+ if (blob) resolve(blob);
|
|
|
+ else reject(new Error('Canvas toBlob failed'));
|
|
|
+ }, 'image/png');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 8. 返回 File 对象
|
|
|
+ return new File([outputBlob], filename, { type: 'image/png' });
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -1383,11 +1384,12 @@ export default () => {
|
|
|
const uploadTask = (async () => {
|
|
|
try {
|
|
|
const file = await makeWhiteTransparent(el.src, `image_${Date.now()}.png`)
|
|
|
- const url = await uploadFileToS3(file)
|
|
|
- element.src = url // 替换为远程 URL
|
|
|
-
|
|
|
- const slidesStore = useSlidesStore()
|
|
|
- slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
+ if (file) {
|
|
|
+ const url = await uploadFileToS3(file)
|
|
|
+ element.src = url // 替换为远程 URL
|
|
|
+ const slidesStore = useSlidesStore()
|
|
|
+ slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
+ }
|
|
|
}
|
|
|
catch (error) {
|
|
|
console.error('Image upload failed:', error)
|
|
|
@@ -1416,12 +1418,13 @@ export default () => {
|
|
|
if (el.picBase64 && typeof el.picBase64 === 'string' && el.picBase64.startsWith('data:')) {
|
|
|
const uploadTask = (async () => {
|
|
|
try {
|
|
|
- const file = makeWhiteTransparent(el.picBase64, `image_${Date.now()}.png`, 'image/png')
|
|
|
- const url = await uploadFileToS3(file)
|
|
|
- element.src = url // 替换为远程 URL
|
|
|
-
|
|
|
- const slidesStore = useSlidesStore()
|
|
|
- slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
+ const file = makeWhiteTransparent(el.picBase64, `image_${Date.now()}.png`)
|
|
|
+ if (file) {
|
|
|
+ const url = await uploadFileToS3(file)
|
|
|
+ element.src = url // 替换为远程 URL
|
|
|
+ const slidesStore = useSlidesStore()
|
|
|
+ slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
+ }
|
|
|
}
|
|
|
catch (error) {
|
|
|
console.error('Image upload failed:', error)
|
|
|
@@ -1451,16 +1454,18 @@ export default () => {
|
|
|
loop: false,
|
|
|
autoplay: false,
|
|
|
}
|
|
|
+ const localData = el.blob || (el.src && typeof el.src === 'string' && el.src.startsWith('data:') ? el.src : null)
|
|
|
|
|
|
- if (el.blob instanceof Blob) {
|
|
|
+ if (localData) {
|
|
|
const uploadTask = (async () => {
|
|
|
try {
|
|
|
- const file = dataToFile(el.blob, `audio_${Date.now()}.mp3`, el.blob.type)
|
|
|
- const url = await uploadFileToS3(file)
|
|
|
- element.src = url
|
|
|
-
|
|
|
- const slidesStore = useSlidesStore()
|
|
|
- slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
+ const file = await dataToFile(localData, `audio_${Date.now()}.mp3`, 'audio/mpeg')
|
|
|
+ if (file) {
|
|
|
+ const url = await uploadFileToS3(file)
|
|
|
+ element.src = url
|
|
|
+ const slidesStore = useSlidesStore()
|
|
|
+ slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
+ }
|
|
|
}
|
|
|
catch (error) {
|
|
|
console.error('Audio upload failed:', error)
|
|
|
@@ -1491,17 +1496,13 @@ export default () => {
|
|
|
const uploadTask = (async () => {
|
|
|
try {
|
|
|
let file: File
|
|
|
- if (localData instanceof Blob) {
|
|
|
- file = dataToFile(localData, `video_${Date.now()}.mp4`, localData.type)
|
|
|
- }
|
|
|
- else {
|
|
|
- file = dataToFile(localData, `video_${Date.now()}.mp4`, 'video/mp4')
|
|
|
+ file = await dataToFile(localData, `video_${Date.now()}.mp4`, 'video/mp4')
|
|
|
+ if (file) {
|
|
|
+ const url = await uploadFileToS3(file)
|
|
|
+ element.src = url
|
|
|
+ const slidesStore = useSlidesStore()
|
|
|
+ slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
}
|
|
|
- const url = await uploadFileToS3(file)
|
|
|
- element.src = url
|
|
|
-
|
|
|
- const slidesStore = useSlidesStore()
|
|
|
- slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
}
|
|
|
catch (error) {
|
|
|
console.error('Video upload failed:', error)
|