使用canvas模拟 美图AI画质消除中 选中区域上传(第一次正式使用canvas小记)

(其实就是记录橡皮擦功能擦过的位置啦 哈哈哈哈!)

首先贴上全部代码

CSS:

css 复制代码
 canvas {
        position: absolute;
        top: 0;
        left: 0;
      }

      #colorLayer {
        mix-blend-mode: multiply;
      }

      button {
        position: relative;
        top: 300px;
        padding: 10px;
        margin: 5px;
      }

HTML:

ini 复制代码
    <canvas id="bottomCanvas"></canvas>
    <canvas id="topCanvas"></canvas>
    <canvas id="colorLayer" style="pointer-events: none"></canvas>
    <canvas id="maskCanvas" style="display: none"></canvas>
    <canvas id="previewLayer" style="pointer-events: none"></canvas>

    <button onclick="setMode('smear', 'add')">涂抹/增加模式</button>
    <button onclick="setMode('rect', 'add')">框选/增加模式</button>
    <button onclick="setMode('free', 'add')">圈选/增加模式</button>
    <button onclick="setMode('smear', 'erase')">涂抹/橡皮擦模式</button>
    <button onclick="setMode('rect', 'erase')">框选/橡皮擦模式</button>
    <button onclick="setMode('free', 'erase')">圈选/橡皮擦模式</button>
    <button onclick="getErasedRegion()">获取被擦除区域</button>
    <button onclick="clearSelection()">清空选区</button>

JS:

ini 复制代码
      const minWidth = 300
      const minHeight = 300
      const addColor = '#555'
      const eraseColor = 'rgba(255,255,255,0.5)'
      const brushSize = 20

      const bottomCanvas = document.getElementById('bottomCanvas')
      const topCanvas = document.getElementById('topCanvas')
      const colorLayer = document.getElementById('colorLayer')
      const maskCanvas = document.getElementById('maskCanvas')
      const previewCanvas = document.getElementById('previewLayer')

      const img = new Image()
      img.onload = () => {
        const imgWidth = Math.max(img.width, minWidth)
        const imgHeight = Math.max(img.height, minHeight)

        ;[
          bottomCanvas,
          topCanvas,
          colorLayer,
          maskCanvas,
          previewCanvas,
        ].forEach((canvas) => {
          canvas.width = imgWidth
          canvas.height = imgHeight
        })

        const ctxBottom = bottomCanvas.getContext('2d')
        const ctxTop = topCanvas.getContext('2d')
        const ctxColor = colorLayer.getContext('2d')
        const ctxMask = maskCanvas.getContext('2d')
        const ctxPreview = previewCanvas.getContext('2d')

        ctxMask.fillStyle = 'black'
        ctxMask.fillRect(0, 0, imgWidth, imgHeight)
        ctxBottom.drawImage(img, 0, 0, imgWidth, imgHeight)
        ctxTop.drawImage(img, 0, 0, imgWidth, imgHeight)

        setupInteraction(imgWidth, imgHeight)
      }
      
      img.src = './assets/caseImg.png'

      function setupInteraction(width, height) {
        let currentMode = 'smear-add'
        let isDrawing = false
        let isSelecting = false
        let lastX = 0,
          lastY = 0
        let startX = 0,
          startY = 0
        let freePathPoints = []
        let endX = 0,
          endY = 0

        const eventHandler = {
          start: (e) => {
            const pos = getPosition(e)
            const [tool, action] = currentMode.split('-')

            if (action === 'add') {
              if (tool === 'smear') {
                isDrawing = true
                ;[lastX, lastY] = [pos.x, pos.y]
                drawAdd(pos)
              } else {
                isSelecting = true
                startX = pos.x
                startY = pos.y
                if (tool === 'free') freePathPoints = [pos]
              }
            } else if (action === 'erase') {
              if (tool === 'smear') {
                isDrawing = true
                ;[lastX, lastY] = [pos.x, pos.y]
                drawErase(pos)
              } else {
                isSelecting = true
                startX = pos.x
                startY = pos.y
                if (tool === 'free') freePathPoints = [pos]
              }
            }
          },
          move: (e) => {
            if (!isDrawing && !isSelecting) return
            const pos = getPosition(e)
            const [tool, action] = currentMode.split('-')

            if (action === 'add') {
              if (tool === 'smear' && isDrawing) {
                drawAdd(pos)
              } else if (isSelecting) {
                if (tool === 'rect') {
                  drawRectPreview(pos)
                } else if (tool === 'free') {
                  freePathPoints.push(pos)
                  drawFreePreview()
                }
              }
            } else if (action === 'erase') {
              if (tool === 'smear' && isDrawing) {
                drawErase(pos)
              } else if (isSelecting) {
                if (tool === 'rect') {
                  drawRectPreview(pos)
                } else if (tool === 'free') {
                  freePathPoints.push(pos)
                  drawFreePreview()
                }
              }
            }
          },
          end: (e) => {
            const [tool, action] = currentMode.split('-')

            if (action === 'add' && tool === 'smear') {
              isDrawing = false
            } else if (action === 'erase' && tool === 'smear') {
              isDrawing = false
            } else if (isSelecting) {
              const pos = getPosition(e)
              endX = pos.x
              endY = pos.y
              handleSelectionEnd()
              isSelecting = false
            }
          },
        }

        topCanvas.addEventListener('mousedown', eventHandler.start)
        topCanvas.addEventListener('mousemove', eventHandler.move)
        topCanvas.addEventListener('mouseup', eventHandler.end)
        topCanvas.addEventListener('mouseleave', eventHandler.end)

        topCanvas.addEventListener('touchstart', (e) => {
          e.preventDefault()
          eventHandler.start(e.touches[0])
        })
        topCanvas.addEventListener('touchmove', (e) => {
          e.preventDefault()
          eventHandler.move(e.touches[0])
        })
        topCanvas.addEventListener('touchend', (e) => {
          eventHandler.end(e.changedTouches[0])
        })

        function getPosition(e) {
          const rect = topCanvas.getBoundingClientRect()
          return {
            x: (e.clientX - rect.left) * (width / rect.width),
            y: (e.clientY - rect.top) * (height / rect.height),
          }
        }

        function drawAdd(pos) {
          const ctxTop = topCanvas.getContext('2d')
          const ctxColor = colorLayer.getContext('2d')
          const ctxMask = maskCanvas.getContext('2d')
          ctxTop.lineCap = ctxColor.lineCap = ctxMask.lineCap = 'round'
          ctxTop.lineJoin = ctxColor.lineJoin = ctxMask.lineJoin = 'round'

          ctxTop.globalCompositeOperation = 'destination-out'
          ctxTop.lineWidth = brushSize
          ctxTop.beginPath()
          ctxTop.moveTo(lastX, lastY)
          ctxTop.lineTo(pos.x, pos.y)
          ctxTop.stroke()

          ctxColor.strokeStyle = addColor
          ctxColor.lineWidth = brushSize
          ctxColor.beginPath()
          ctxColor.moveTo(lastX, lastY)
          ctxColor.lineTo(pos.x, pos.y)
          ctxColor.stroke()

          ctxMask.strokeStyle = 'white'
          ctxMask.lineWidth = brushSize
          ctxMask.beginPath()
          ctxMask.moveTo(lastX, lastY)
          ctxMask.lineTo(pos.x, pos.y)
          ctxMask.stroke()
          ;[lastX, lastY] = [pos.x, pos.y]
        }

        function drawErase(pos) {
          const ctxTop = topCanvas.getContext('2d')
          const ctxColor = colorLayer.getContext('2d')
          const ctxMask = maskCanvas.getContext('2d')
          ctxTop.lineCap = ctxColor.lineCap = ctxMask.lineCap = 'round'
          ctxTop.lineJoin = ctxColor.lineJoin = ctxMask.lineJoin = 'round'

          ctxTop.save()
          ctxTop.beginPath()
          ctxTop.moveTo(lastX, lastY)
          ctxTop.lineTo(pos.x, pos.y)
          ctxTop.lineWidth = brushSize
          ctxTop.stroke()
          ctxTop.clip()
          ctxTop.drawImage(bottomCanvas, 0, 0)
          ctxTop.restore()

          ctxMask.beginPath()
          ctxMask.moveTo(lastX, lastY)
          ctxMask.lineTo(pos.x, pos.y)
          ctxMask.lineWidth = brushSize
          ctxMask.strokeStyle = 'black'
          ctxMask.stroke()

          ctxColor.beginPath()
          ctxColor.moveTo(lastX, lastY)
          ctxColor.lineTo(pos.x, pos.y)
          ctxColor.lineWidth = brushSize
          ctxColor.strokeStyle = eraseColor
          ctxColor.stroke()
          ;[lastX, lastY] = [pos.x, pos.y]
        }

        function drawRectPreview(pos) {
          const ctxPreview = previewCanvas.getContext('2d')
          ctxPreview.clearRect(0, 0, width, height)
          ctxPreview.setLineDash([5, 5])
          ctxPreview.strokeStyle = addColor
          ctxPreview.lineWidth = 2

          const x = Math.min(startX, pos.x)
          const y = Math.min(startY, pos.y)
          const w = Math.abs(pos.x - startX)
          const h = Math.abs(pos.y - startY)
          ctxPreview.strokeRect(x, y, w, h)
          ctxPreview.setLineDash([])
        }

        function drawFreePreview() {
          const ctxPreview = previewCanvas.getContext('2d')
          ctxPreview.clearRect(0, 0, width, height)
          ctxPreview.beginPath()
          freePathPoints.forEach((p, i) => {
            if (i === 0) ctxPreview.moveTo(p.x, p.y)
            else ctxPreview.lineTo(p.x, p.y)
          })
          ctxPreview.strokeStyle = addColor
          ctxPreview.lineWidth = 2
          ctxPreview.stroke()
        }

        function handleSelectionEnd() {
          const ctxPreview = previewCanvas.getContext('2d')
          const ctxTop = topCanvas.getContext('2d')
          const ctxColor = colorLayer.getContext('2d')
          const ctxMask = maskCanvas.getContext('2d')
          ctxPreview.clearRect(0, 0, width, height)

          const [tool, action] = currentMode.split('-')

          if (tool === 'rect') {
            const x = Math.min(startX, endX)
            const y = Math.min(startY, endY)
            const w = Math.abs(endX - startX)
            const h = Math.abs(endY - startY)

            if (action === 'add') {
              ctxTop.globalCompositeOperation = 'destination-out'
              ctxTop.fillRect(x, y, w, h)
              ctxMask.fillStyle = 'white'
              ctxMask.fillRect(x, y, w, h)
              ctxColor.fillStyle = addColor
              ctxColor.fillRect(x, y, w, h)
            } else if (action === 'erase') {
              ctxTop.drawImage(bottomCanvas, x, y, w, h, x, y, w, h)
              ctxMask.fillStyle = 'black'
              ctxMask.fillRect(x, y, w, h)
              ctxColor.clearRect(x, y, w, h)
            }
          } else if (tool === 'free' && freePathPoints.length > 2) {
            if (action === 'add') {
              ctxTop.globalCompositeOperation = 'destination-out'
              ctxTop.beginPath()
              freePathPoints.forEach((p, i) => {
                if (i === 0) ctxTop.moveTo(p.x, p.y)
                else ctxTop.lineTo(p.x, p.y)
              })
              ctxTop.closePath()
              ctxTop.fill()

              ctxMask.fillStyle = 'white'
              ctxMask.beginPath()
              freePathPoints.forEach((p, i) => {
                if (i === 0) ctxMask.moveTo(p.x, p.y)
                else ctxMask.lineTo(p.x, p.y)
              })
              ctxMask.closePath()
              ctxMask.fill()

              ctxColor.fillStyle = addColor
              ctxColor.beginPath()
              freePathPoints.forEach((p, i) => {
                if (i === 0) ctxColor.moveTo(p.x, p.y)
                else ctxColor.lineTo(p.x, p.y)
              })
              ctxColor.closePath()
              ctxColor.fill()
            } else if (action === 'erase') {
              ctxTop.save()
              ctxTop.beginPath()
              freePathPoints.forEach((p, i) => {
                if (i === 0) ctxTop.moveTo(p.x, p.y)
                else ctxTop.lineTo(p.x, p.y)
              })
              ctxTop.closePath()
              ctxTop.clip()
              ctxTop.globalCompositeOperation = 'destination-out'
              ctxTop.fillRect(0, 0, width, height)
              ctxTop.drawImage(bottomCanvas, 0, 0)
              ctxTop.restore()

              ctxMask.save()
              ctxMask.beginPath()
              freePathPoints.forEach((p, i) => {
                if (i === 0) ctxMask.moveTo(p.x, p.y)
                else ctxMask.lineTo(p.x, p.y)
              })
              ctxMask.closePath()
              ctxMask.clip()
              ctxMask.clearRect(0, 0, width, height)
              ctxMask.fillStyle = 'black'
              ctxMask.fillRect(0, 0, width, height)
              ctxMask.restore()

              ctxColor.save()
              ctxColor.beginPath()
              freePathPoints.forEach((p, i) => {
                if (i === 0) ctxColor.moveTo(p.x, p.y)
                else ctxColor.lineTo(p.x, p.y)
              })
              ctxColor.closePath()
              ctxColor.clip()
              ctxColor.clearRect(0, 0, width, height)
              ctxColor.restore()
            }
          }

          freePathPoints = []
        }

        function setMode(tool, action) {
          currentMode = `${tool}-${action}`
        }

        function getErasedRegion() {
          const tempCanvas = document.createElement('canvas')
          tempCanvas.width = width
          tempCanvas.height = height
          const tempCtx = tempCanvas.getContext('2d')

          tempCtx.drawImage(bottomCanvas, 0, 0)
          tempCtx.globalCompositeOperation = 'destination-in'
          tempCtx.drawImage(maskCanvas, 0, 0)
          tempCtx.globalCompositeOperation = 'destination-over'
          tempCtx.fillStyle = '#808080'
          tempCtx.fillRect(0, 0, width, height)

          const link = document.createElement('a')
          link.download = 'erased-area.png'
          link.href = tempCanvas.toDataURL()
          link.click()
        }

        function clearSelection() {
          const ctxTop = topCanvas.getContext('2d')
          const ctxColor = colorLayer.getContext('2d')
          const ctxMask = maskCanvas.getContext('2d')
          const ctxPreview = previewCanvas.getContext('2d')

          ctxTop.clearRect(0, 0, width, height)
          ctxTop.drawImage(bottomCanvas, 0, 0)
          ctxColor.clearRect(0, 0, width, height)
          ctxMask.clearRect(0, 0, width, height)
          ctxMask.fillStyle = 'black'
          ctxMask.fillRect(0, 0, width, height)
          ctxPreview.clearRect(0, 0, width, height)

          currentMode = 'smear-add'
          isDrawing = false
          isSelecting = false
          lastX = lastY = startX = startY = endX = endY = 0
          freePathPoints = []
        }
      }

主要功能实现思路

HTML:

我呢使用了五个canvas来实现

这里的可视化层为了和其他层区分出来 使用了mix-blend-mode:为multiply的色彩混合模式

xml 复制代码
<canvas id="bottomCanvas"></canvas>      <!-- 图片层 -->
<canvas id="topCanvas"></canvas>         <!-- 操作层 -->
<canvas id="colorLayer"></canvas>        <!-- 选区可视化层 -->
<canvas id="maskCanvas"></canvas>        <!-- 掩模层(隐藏) -->
<canvas id="previewLayer"></canvas>      <!-- 选区预览层 -->

JS:

截至到 drawAdd 这个函数前面 就是一些初始化和拖拽功能

绘制功能实现

1、涂抹模式绘制

ini 复制代码
  // 增加选区
  function drawAdd(pos) {
    const ctxTop = topCanvas.getContext('2d');
    const ctxColor = colorLayer.getContext('2d');
    const ctxMask = maskCanvas.getContext('2d');
    
    // 设置线条样式
    ctxTop.lineCap = ctxColor.lineCap = ctxMask.lineCap = 'round';
    ctxTop.lineJoin = ctxColor.lineJoin = ctxMask.lineJoin = 'round';
    
    // 在上层画布擦除(使用destination-out合成模式)
    ctxTop.globalCompositeOperation = 'destination-out';
    ctxTop.lineWidth = brushSize;
    ctxTop.beginPath();
    ctxTop.moveTo(lastX, lastY);
    ctxTop.lineTo(pos.x, pos.y);
    ctxTop.stroke();
    
    // 在颜色层绘制选区可视化
    ctxColor.strokeStyle = addColor;
    ctxColor.lineWidth = brushSize;
    ctxColor.beginPath();
    ctxColor.moveTo(lastX, lastY);
    ctxColor.lineTo(pos.x, pos.y);
    ctxColor.stroke();
    
    // 更新掩模(白色表示擦除区域)
    ctxMask.strokeStyle = 'white';
    ctxMask.lineWidth = brushSize;
    ctxMask.beginPath();
    ctxMask.moveTo(lastX, lastY);
    ctxMask.lineTo(pos.x, pos.y);
    ctxMask.stroke();
    
    // 记录位置
    [lastX, lastY] = [pos.x, pos.y];
  }

  // 擦除选区(橡皮擦)
  function drawErase(pos) {
    const ctxTop = topCanvas.getContext('2d');
    const ctxColor = colorLayer.getContext('2d');
    const ctxMask = maskCanvas.getContext('2d');
    
    // 设置线条样式
    ctxTop.lineCap = ctxColor.lineCap = ctxMask.lineCap = 'round';
    ctxTop.lineJoin = ctxColor.lineJoin = ctxMask.lineJoin = 'round';
    
    // 从底层恢复图像
    ctxTop.save();
    ctxTop.beginPath();
    ctxTop.moveTo(lastX, lastY);
    ctxTop.lineTo(pos.x, pos.y);
    ctxTop.lineWidth = brushSize;
    ctxTop.stroke();
    ctxTop.clip();
    ctxTop.drawImage(bottomCanvas, 0, 0);
    ctxTop.restore();
    
    // 更新掩模(黑色表示恢复区域)
    ctxMask.beginPath();
    ctxMask.moveTo(lastX, lastY);
    ctxMask.lineTo(pos.x, pos.y);
    ctxMask.lineWidth = brushSize;
    ctxMask.strokeStyle = 'black';
    ctxMask.stroke();
    
    // 更新颜色层
    ctxColor.beginPath();
    ctxColor.moveTo(lastX, lastY);
    ctxColor.lineTo(pos.x, pos.y);
    ctxColor.lineWidth = brushSize;
    ctxColor.strokeStyle = eraseColor;
    ctxColor.stroke();
    
    // 记录位置
    [lastX, lastY] = [pos.x, pos.y];
  }

2、选区预览功能

ini 复制代码
  // 矩形选区预览
  function drawRectPreview(pos) {
    const ctxPreview = previewCanvas.getContext('2d');
    ctxPreview.clearRect(0, 0, width, height);
    ctxPreview.setLineDash([5, 5]);  // 虚线样式
    ctxPreview.strokeStyle = addColor;
    ctxPreview.lineWidth = 2;
    
    // 计算矩形位置和尺寸
    const x = Math.min(startX, pos.x);
    const y = Math.min(startY, pos.y);
    const w = Math.abs(pos.x - startX);
    const h = Math.abs(pos.y - startY);
    
    ctxPreview.strokeRect(x, y, w, h);
    ctxPreview.setLineDash([]);  // 重置虚线
  }

  // 自由选区预览
  function drawFreePreview() {
    const ctxPreview = previewCanvas.getContext('2d');
    ctxPreview.clearRect(0, 0, width, height);
    ctxPreview.beginPath();
    
    // 绘制所有路径点
    freePathPoints.forEach((p, i) => {
      if (i === 0) ctxPreview.moveTo(p.x, p.y);
      else ctxPreview.lineTo(p.x, p.y);
    });
    
    ctxPreview.strokeStyle = addColor;
    ctxPreview.lineWidth = 2;
    ctxPreview.stroke();
  }

3、选区结束处理

ini 复制代码
  // 选区结束时的处理
  function handleSelectionEnd() {
    const ctxPreview = previewCanvas.getContext('2d');
    const ctxTop = topCanvas.getContext('2d');
    const ctxColor = colorLayer.getContext('2d');
    const ctxMask = maskCanvas.getContext('2d');
    
    // 清除预览
    ctxPreview.clearRect(0, 0, width, height);
    
    const [tool, action] = currentMode.split('-');
    
    // 矩形选区处理
    if (tool === 'rect') {
      const x = Math.min(startX, endX);
      const y = Math.min(startY, endY);
      const w = Math.abs(endX - startX);
      const h = Math.abs(endY - startY);
      
      if (action === 'add') {
        // 增加选区
        ctxTop.globalCompositeOperation = 'destination-out';
        ctxTop.fillRect(x, y, w, h);
        
        ctxMask.fillStyle = 'white';
        ctxMask.fillRect(x, y, w, h);
        
        ctxColor.fillStyle = addColor;
        ctxColor.fillRect(x, y, w, h);
      } else if (action === 'erase') {
        // 擦除操作
        ctxTop.drawImage(bottomCanvas, x, y, w, h, x, y, w, h);
        ctxMask.fillStyle = 'black';
        ctxMask.fillRect(x, y, w, h);
        ctxColor.clearRect(x, y, w, h);
      }
    } 
    // 自由选区处理
    else if (tool === 'free' && freePathPoints.length > 2) {
      if (action === 'add') {
        // 创建闭合路径
        ctxTop.beginPath();
        freePathPoints.forEach((p, i) => {
          if (i === 0) ctxTop.moveTo(p.x, p.y);
          else ctxTop.lineTo(p.x, p.y);
        });
        ctxTop.closePath();
        
        // 擦除选区
        ctxTop.globalCompositeOperation = 'destination-out';
        ctxTop.fill();
        
        // 更新掩模
        ctxMask.fillStyle = 'white';
        ctxMask.beginPath();
        freePathPoints.forEach((p, i) => {
          if (i === 0) ctxMask.moveTo(p.x, p.y);
          else ctxMask.lineTo(p.x, p.y);
        });
        ctxMask.closePath();
        ctxMask.fill();
        
        // 更新颜色层
        ctxColor.fillStyle = addColor;
        ctxColor.beginPath();
        freePathPoints.forEach((p, i) => {
          if (i === 0) ctxColor.moveTo(p.x, p.y);
          else ctxColor.lineTo(p.x, p.y);
        });
        ctxColor.closePath();
        ctxColor.fill();
      } else if (action === 'erase') {
        // 裁剪并恢复原始图像
        ctxTop.save();
        ctxTop.beginPath();
        freePathPoints.forEach((p, i) => {
          if (i === 0) ctxTop.moveTo(p.x, p.y);
          else ctxTop.lineTo(p.x, p.y);
        });
        ctxTop.closePath();
        ctxTop.clip();
        ctxTop.globalCompositeOperation = 'destination-out';
        ctxTop.fillRect(0, 0, width, height);
        ctxTop.drawImage(bottomCanvas, 0, 0);
        ctxTop.restore();
        
        // 更新掩模
        ctxMask.save();
        ctxMask.beginPath();
        freePathPoints.forEach((p, i) => {
          if (i === 0) ctxMask.moveTo(p.x, p.y);
          else ctxMask.lineTo(p.x, p.y);
        });
        ctxMask.closePath();
        ctxMask.clip();
        ctxMask.clearRect(0, 0, width, height);
        ctxMask.fillStyle = 'black';
        ctxMask.fillRect(0, 0, width, height);
        ctxMask.restore();
        
        // 更新颜色层
        ctxColor.save();
        ctxColor.beginPath();
        freePathPoints.forEach((p, i) => {
          if (i === 0) ctxColor.moveTo(p.x, p.y);
          else ctxColor.lineTo(p.x, p.y);
        });
        ctxColor.closePath();
        ctxColor.clip();
        ctxColor.clearRect(0, 0, width, height);
        ctxColor.restore();
      }
    }
    
    // 重置路径点
    freePathPoints = [];
  }

辅助功能函数

ini 复制代码
  // 设置当前模式
  function setMode(tool, action) {
    currentMode = `${tool}-${action}`;
  }

  // 获取被擦除区域
  function getErasedRegion() {
    // 创建临时画布
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = width;
    tempCanvas.height = height;
    const tempCtx = tempCanvas.getContext('2d');
    
    // 绘制原始图像
    tempCtx.drawImage(bottomCanvas, 0, 0);
    
    // 使用掩模提取被擦除部分
    tempCtx.globalCompositeOperation = 'destination-in';
    tempCtx.drawImage(maskCanvas, 0, 0);
    
    // 添加灰色背景
    tempCtx.globalCompositeOperation = 'destination-over';
    tempCtx.fillStyle = '#808080';
    tempCtx.fillRect(0, 0, width, height);
    
    // 下载
    const link = document.createElement('a');
    link.download = 'erased-area.png';
    link.href = tempCanvas.toDataURL();
    link.click();
  }

  // 清空选区
  function clearSelection() {
    const ctxTop = topCanvas.getContext('2d');
    const ctxColor = colorLayer.getContext('2d');
    const ctxMask = maskCanvas.getContext('2d');
    const ctxPreview = previewCanvas.getContext('2d');
    
    // 重置上层画布
    ctxTop.clearRect(0, 0, width, height);
    ctxTop.drawImage(bottomCanvas, 0, 0);
    
    // 重置颜色层
    ctxColor.clearRect(0, 0, width, height);
    
    // 重置掩模
    ctxMask.clearRect(0, 0, width, height);
    ctxMask.fillStyle = 'black';
    ctxMask.fillRect(0, 0, width, height);
    
    // 重置预览层
    ctxPreview.clearRect(0, 0, width, height);
    
    // 重置状态变量
    currentMode = 'smear-add';
    isDrawing = false;
    isSelecting = false;
    lastX = lastY = startX = startY = endX = endY = 0;
    freePathPoints = [];
  }
}

有什么不足的地方 还请希望大佬帮我提出来OvO

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax