使用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

相关推荐
weixin_382395233 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__3 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.3 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~4 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华5 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子7 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅8 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni8 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学9 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端