|
|
@@ -513,82 +513,97 @@ export default () => {
|
|
|
filename: string,
|
|
|
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) {
|
|
|
- imageUrl = URL.createObjectURL(data)
|
|
|
- needRevoke = true
|
|
|
+ const tolerance = options?.tolerance ?? 15
|
|
|
+
|
|
|
+ // ----- 辅助函数:将输入统一转换为 { blob, mime } -----
|
|
|
+ async function getBlobAndMime(input: string | Blob): Promise<{ blob: Blob; mime: string }> {
|
|
|
+ // 1. 已经是 Blob
|
|
|
+ if (input instanceof Blob) {
|
|
|
+ return { blob: input, mime: input.type }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 处理字符串
|
|
|
+ if (input.startsWith('data:')) {
|
|
|
+ // data URL → 通过 fetch 获取 Blob(自动获得正确的 MIME 类型)
|
|
|
+ const response = await fetch(input)
|
|
|
+ const blob = await response.blob()
|
|
|
+ return { blob, mime: blob.type }
|
|
|
+ } else {
|
|
|
+ // 纯 base64 字符串 → 按原逻辑默认当作 PNG
|
|
|
+ const binary = atob(input)
|
|
|
+ const bytes = new Uint8Array(binary.length)
|
|
|
+ for (let i = 0; i < binary.length; i++) {
|
|
|
+ bytes[i] = binary.charCodeAt(i)
|
|
|
+ }
|
|
|
+ // 默认 MIME 为 image/png(与原函数行为一致)
|
|
|
+ const blob = new Blob([bytes], { type: 'image/png' })
|
|
|
+ return { blob, mime: 'image/png' }
|
|
|
+ }
|
|
|
}
|
|
|
- else {
|
|
|
- throw new Error('Unsupported data type: expected string or Blob')
|
|
|
+
|
|
|
+ // 获取统一的 blob 和实际 MIME 类型
|
|
|
+ const { blob, mime } = await getBlobAndMime(data)
|
|
|
+
|
|
|
+ // ----- 非 PNG 格式:直接返回原始文件(不处理透明)-----
|
|
|
+ if (mime !== 'image/png') {
|
|
|
+ return new File([blob], filename, { type: mime })
|
|
|
}
|
|
|
+
|
|
|
+ // ----- PNG 格式:执行白色变透明处理 -----
|
|
|
+ // 1. 创建对象 URL 用于加载图片
|
|
|
+ const imageUrl = URL.createObjectURL(blob)
|
|
|
+ let needRevoke = true
|
|
|
+
|
|
|
// 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'
|
|
|
- }
|
|
|
+ // Blob URL 不需要设置 crossOrigin
|
|
|
image.src = imageUrl
|
|
|
})
|
|
|
+
|
|
|
const canvas = document.createElement('canvas')
|
|
|
try {
|
|
|
- // 3. 绘制到 Canvas
|
|
|
canvas.width = img.width
|
|
|
canvas.height = img.height
|
|
|
const ctx = canvas.getContext('2d')!
|
|
|
ctx.drawImage(img, 0, 0)
|
|
|
-
|
|
|
- // 4. 获取像素数据
|
|
|
+
|
|
|
+ // 3. 获取像素数据,将接近白色的像素设为透明
|
|
|
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
|
|
|
+ dataArray[i + 3] = 0 // 完全透明
|
|
|
}
|
|
|
- // 其他所有像素(包括浅蓝、灰色、黑色等)保持原样,不修改颜色和透明度
|
|
|
}
|
|
|
-
|
|
|
- // 6. 将修改后的像素放回 Canvas
|
|
|
+
|
|
|
ctx.putImageData(imageData, 0, 0)
|
|
|
-
|
|
|
-
|
|
|
- }
|
|
|
- finally {
|
|
|
+ } finally {
|
|
|
if (needRevoke) {
|
|
|
URL.revokeObjectURL(imageUrl)
|
|
|
}
|
|
|
}
|
|
|
- // 7. 导出为 PNG Blob
|
|
|
+
|
|
|
+ // 4. 导出为 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' })
|
|
|
}
|
|
|
|