Просмотр исходного кода

Merge branch 'beta' of https://git.cocorobo.cn/jack/PPT into beta

lsc 1 месяц назад
Родитель
Сommit
6364d6fb58
1 измененных файлов с 118 добавлено и 70 удалено
  1. 118 70
      src/hooks/useImport.ts

+ 118 - 70
src/hooks/useImport.ts

@@ -421,19 +421,14 @@ export default () => {
     throw new Error('Unsupported data type')
   }
 
-
+  /*
   const makeWhiteTransparent = async (
     data: string | Blob,
     filename: string,
     options?: { tolerance?: number }
   ): Promise<File> => {
-    // const tolerance = options?.tolerance ?? 30 // 容差值,控制哪些颜色被视为白色
-
-    // 容差:颜色到白色的欧几里得距离阈值
-    const distThreshold = options?.tolerance ?? 50 // 50 是经验值,可根据效果调整
-
-    // 是否启用边缘恢复(去除白边)
-    const removeWhiteMatte = true // 可改为配置项
+    const tolerance = options?.tolerance ?? 30 // 容差值,控制哪些颜色被视为白色
+    const distThreshold = options?.tolerance ?? 50;
 
     // 1. 将输入数据统一为 Blob 或可直接用于加载的 URL
     let imageUrl: string
@@ -472,74 +467,127 @@ export default () => {
     // 4. 获取像素数据并处理
     const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
     const dataArray = imageData.data
-    /*
-      for (let i = 0; i < dataArray.length; i += 4) {
-        const r = dataArray[i]
-        const g = dataArray[i + 1]
-        const b = dataArray[i + 2]
-    
-        // 判断颜色是否接近白色(RGB 都大于 255 - tolerance)
-        if (r > 255 - tolerance && g > 255 - tolerance && b > 255 - tolerance) {
-          dataArray[i + 3] = 0 // 设置 Alpha 为 0(完全透明)
-        }
-      }
-  */
-
 
     for (let i = 0; i < dataArray.length; i += 4) {
       const r = dataArray[i]
       const g = dataArray[i + 1]
       const b = dataArray[i + 2]
-      const a = dataArray[i + 3] // 当前 alpha(可能是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 <= distThreshold) {
-        // 完全视为背景,设为全透明
-        dataArray[i + 3] = 0
-      }
-      else if (removeWhiteMatte && a === 255) {
-        // 尝试恢复边缘(仅针对完全不透明但受背景影响的像素)
-        // 估计背景混合度:用最小通道值近似计算 alpha
-        const minChannel = Math.min(r, g, b)
-        const bgWeight = 1 - minChannel / 255 // 背景混合权重
-        if (bgWeight > 0.01 && bgWeight < 0.99) {
-          // 存在混合,反推前景色
-          const alpha = 1 - bgWeight // 前景透明度
-          // 反推公式:前景色 = (当前颜色 - 背景色*(1-alpha)) / alpha
-          const newR = (r - 255 * (1 - alpha)) / alpha
-          const newG = (g - 255 * (1 - alpha)) / alpha
-          const newB = (b - 255 * (1 - alpha)) / alpha
-
-          // 将计算结果写回,并设置alpha
-          dataArray[i] = Math.max(0, Math.min(255, newR))
-          dataArray[i + 1] = Math.max(0, Math.min(255, newG))
-          dataArray[i + 2] = Math.max(0, Math.min(255, newB))
-          dataArray[i + 3] = Math.round(alpha * 255)
-        }
+      // 判断颜色是否接近白色(RGB 都大于 255 - tolerance)
+      if (r > 255 - tolerance && g > 255 - tolerance && b > 255 - tolerance) {
+        dataArray[i + 3] = 0 // 设置 Alpha 为 0(完全透明)
       }
+    }
 
-      // 5. 将修改后的像素放回 Canvas
-      ctx.putImageData(imageData, 0, 0)
+    // 5. 将修改后的像素放回 Canvas
+    ctx.putImageData(imageData, 0, 0)
 
-      // 6. 将 Canvas 转换为 PNG Blob
-      const outputBlob = await new Promise<Blob>((resolve) =>
-        canvas.toBlob((blob) => resolve(blob!), 'image/png')
-      )
+    // 6. 将 Canvas 转换为 PNG Blob
+    const outputBlob = await new Promise<Blob>((resolve) =>
+      canvas.toBlob((blob) => resolve(blob!), 'image/png')
+    )
 
-      // 7. 清理对象 URL(如果之前创建过)
-      if (typeof data !== 'string') {
-        URL.revokeObjectURL(imageUrl)
-      }
+    // 7. 清理对象 URL(如果之前创建过)
+    if (typeof data !== 'string') {
+      URL.revokeObjectURL(imageUrl)
+    }
 
+    // 8. 返回 File 对象
+    return new File([outputBlob], filename, { type: 'image/png' })
+  }
+  */
+
+  /**
+ * 将图片中的白色背景变为透明
+ * @param data 图片数据(Base64字符串 或 Blob)
+ * @param filename 输出文件名
+ * @param options 可选配置
+ * @param options.tolerance 颜色距离容差(默认50,值越大越多的浅色被变透明)
+ * @param options.removeMatte 是否去除白色边缘(默认true,可改善白边)
+ * @returns 处理后的 PNG 格式 File 对象
+ */
+  const makeWhiteTransparent = async (
+    data: string | Blob,
+    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;
+    } else {
+      throw new Error('Unsupported data type: expected string or Blob');
+    }
+  
+    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' })
+      return new File([outputBlob], filename, { type: 'image/png' });
+    } finally {
+      if (needRevoke) {
+        URL.revokeObjectURL(imageUrl);
+      }
     }
-  }
+  };
+
   /**
      * 上传 File 到 S3,返回公开访问的 URL
      */
@@ -1636,13 +1684,13 @@ export default () => {
 
               const firstCell = el.data[0][0]
               const border = firstCell.borders.top ||
-                  firstCell.borders.bottom ||
-                  el.borders.top ||
-                  el.borders.bottom ||
-                  firstCell.borders.left ||
-                  firstCell.borders.right ||
-                  el.borders.left ||
-                  el.borders.right
+                firstCell.borders.bottom ||
+                el.borders.top ||
+                el.borders.bottom ||
+                firstCell.borders.left ||
+                firstCell.borders.right ||
+                el.borders.left ||
+                el.borders.right
               const borderWidth = border?.borderWidth || 0
               const borderStyle = border?.borderType || 'solid'
               const borderColor = border?.borderColor || '#eeece1'