|
@@ -406,16 +406,16 @@ export default () => {
|
|
|
const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): File => {
|
|
const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): File => {
|
|
|
if (typeof data === 'string') {
|
|
if (typeof data === 'string') {
|
|
|
// 1. 通过 fetch 获取 Blob 数据
|
|
// 1. 通过 fetch 获取 Blob 数据
|
|
|
- const response = await fetch(data);
|
|
|
|
|
|
|
+ const response = await fetch(data)
|
|
|
if (!response.ok) {
|
|
if (!response.ok) {
|
|
|
- throw new Error(`Failed to fetch blob: ${response.statusText}`);
|
|
|
|
|
|
|
+ throw new Error(`Failed to fetch blob: ${response.statusText}`)
|
|
|
}
|
|
}
|
|
|
- const blob = await response.blob();
|
|
|
|
|
|
|
+ const blob = await response.blob()
|
|
|
|
|
|
|
|
// 2. 将 Blob 转换为 File 对象
|
|
// 2. 将 Blob 转换为 File 对象
|
|
|
// 如果原 Blob 有 type,会自动保留;否则可手动指定 videoMimeType
|
|
// 如果原 Blob 有 type,会自动保留;否则可手动指定 videoMimeType
|
|
|
- const file = new File([blob], filename, { type: videoMimeType || blob.type });
|
|
|
|
|
- return file;
|
|
|
|
|
|
|
+ const file = new File([blob], filename, { type: videoMimeType || blob.type })
|
|
|
|
|
+ return file
|
|
|
}
|
|
}
|
|
|
else if (data instanceof Blob) {
|
|
else if (data instanceof Blob) {
|
|
|
return new File([data], filename, { type: data.type })
|
|
return new File([data], filename, { type: data.type })
|
|
@@ -513,82 +513,84 @@ export default () => {
|
|
|
filename: string,
|
|
filename: string,
|
|
|
options?: { tolerance?: number }
|
|
options?: { tolerance?: number }
|
|
|
): Promise<File> => {
|
|
): Promise<File> => {
|
|
|
- const tolerance = options?.tolerance ?? 15; // 只处理非常接近白色的像素
|
|
|
|
|
|
|
+ const tolerance = options?.tolerance ?? 15 // 只处理非常接近白色的像素
|
|
|
|
|
|
|
|
// 1. 将输入统一转为可加载的 URL
|
|
// 1. 将输入统一转为可加载的 URL
|
|
|
- let imageUrl: string;
|
|
|
|
|
- let needRevoke = false;
|
|
|
|
|
|
|
+ let imageUrl: string
|
|
|
|
|
+ let needRevoke = false
|
|
|
|
|
|
|
|
if (typeof data === 'string') {
|
|
if (typeof data === 'string') {
|
|
|
- imageUrl = data.startsWith('data:') ? data : `data:image/png;base64,${data}`;
|
|
|
|
|
- } else if (data instanceof Blob) {
|
|
|
|
|
- imageUrl = URL.createObjectURL(data);
|
|
|
|
|
- needRevoke = true;
|
|
|
|
|
- } else {
|
|
|
|
|
- throw new Error('Unsupported data type: expected string or Blob');
|
|
|
|
|
|
|
+ imageUrl = data.startsWith('data:') ? data : `data:image/png;base64,${data}`
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (data instanceof Blob) {
|
|
|
|
|
+ imageUrl = URL.createObjectURL(data)
|
|
|
|
|
+ needRevoke = true
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ throw new Error('Unsupported data type: expected string or Blob')
|
|
|
}
|
|
}
|
|
|
// 2. 加载图像
|
|
// 2. 加载图像
|
|
|
const img = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
const img = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
|
- const image = new Image();
|
|
|
|
|
- image.onload = () => resolve(image);
|
|
|
|
|
- image.onerror = reject;
|
|
|
|
|
|
|
+ const image = new Image()
|
|
|
|
|
+ image.onload = () => resolve(image)
|
|
|
|
|
+ image.onerror = reject
|
|
|
if (typeof data === 'string' && !data.startsWith('data:')) {
|
|
if (typeof data === 'string' && !data.startsWith('data:')) {
|
|
|
- image.crossOrigin = 'anonymous';
|
|
|
|
|
|
|
+ image.crossOrigin = 'anonymous'
|
|
|
}
|
|
}
|
|
|
- image.src = imageUrl;
|
|
|
|
|
- });
|
|
|
|
|
- const canvas = document.createElement('canvas');
|
|
|
|
|
|
|
+ image.src = imageUrl
|
|
|
|
|
+ })
|
|
|
|
|
+ const canvas = document.createElement('canvas')
|
|
|
try {
|
|
try {
|
|
|
// 3. 绘制到 Canvas
|
|
// 3. 绘制到 Canvas
|
|
|
- canvas.width = img.width;
|
|
|
|
|
- canvas.height = img.height;
|
|
|
|
|
- const ctx = canvas.getContext('2d')!;
|
|
|
|
|
- ctx.drawImage(img, 0, 0);
|
|
|
|
|
|
|
+ canvas.width = img.width
|
|
|
|
|
+ canvas.height = img.height
|
|
|
|
|
+ const ctx = canvas.getContext('2d')!
|
|
|
|
|
+ ctx.drawImage(img, 0, 0)
|
|
|
|
|
|
|
|
// 4. 获取像素数据
|
|
// 4. 获取像素数据
|
|
|
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
|
- const dataArray = imageData.data;
|
|
|
|
|
|
|
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
|
|
|
+ const dataArray = imageData.data
|
|
|
|
|
|
|
|
// 5. 遍历像素:只将白色背景区域设为透明,其他颜色原样保留
|
|
// 5. 遍历像素:只将白色背景区域设为透明,其他颜色原样保留
|
|
|
for (let i = 0; i < dataArray.length; i += 4) {
|
|
for (let i = 0; i < dataArray.length; i += 4) {
|
|
|
- const r = dataArray[i];
|
|
|
|
|
- const g = dataArray[i + 1];
|
|
|
|
|
- const b = dataArray[i + 2];
|
|
|
|
|
|
|
+ const r = dataArray[i]
|
|
|
|
|
+ const g = dataArray[i + 1]
|
|
|
|
|
+ const b = dataArray[i + 2]
|
|
|
|
|
|
|
|
// 计算与纯白色 (255,255,255) 的欧几里得距离
|
|
// 计算与纯白色 (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);
|
|
|
|
|
|
|
+ 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) {
|
|
if (dist <= tolerance) {
|
|
|
// 非常接近白色 → 设为全透明
|
|
// 非常接近白色 → 设为全透明
|
|
|
- dataArray[i + 3] = 0;
|
|
|
|
|
|
|
+ dataArray[i + 3] = 0
|
|
|
}
|
|
}
|
|
|
// 其他所有像素(包括浅蓝、灰色、黑色等)保持原样,不修改颜色和透明度
|
|
// 其他所有像素(包括浅蓝、灰色、黑色等)保持原样,不修改颜色和透明度
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 6. 将修改后的像素放回 Canvas
|
|
// 6. 将修改后的像素放回 Canvas
|
|
|
- ctx.putImageData(imageData, 0, 0);
|
|
|
|
|
|
|
+ ctx.putImageData(imageData, 0, 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
finally {
|
|
finally {
|
|
|
if (needRevoke) {
|
|
if (needRevoke) {
|
|
|
- URL.revokeObjectURL(imageUrl);
|
|
|
|
|
|
|
+ URL.revokeObjectURL(imageUrl)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
// 7. 导出为 PNG Blob
|
|
// 7. 导出为 PNG Blob
|
|
|
const outputBlob = await new Promise<Blob>((resolve, reject) => {
|
|
const outputBlob = await new Promise<Blob>((resolve, reject) => {
|
|
|
canvas.toBlob((blob) => {
|
|
canvas.toBlob((blob) => {
|
|
|
- if (blob) resolve(blob);
|
|
|
|
|
- else reject(new Error('Canvas toBlob failed'));
|
|
|
|
|
- }, 'image/png');
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ if (blob) resolve(blob)
|
|
|
|
|
+ else reject(new Error('Canvas toBlob failed'))
|
|
|
|
|
+ }, 'image/png')
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
// 8. 返回 File 对象
|
|
// 8. 返回 File 对象
|
|
|
- return new File([outputBlob], filename, { type: 'image/png' });
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ return new File([outputBlob], filename, { type: 'image/png' })
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 上传 File 到 S3,返回公开访问的 URL
|
|
* 上传 File 到 S3,返回公开访问的 URL
|