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

相关推荐
yinzhiqing10 分钟前
ubuntu24设置拼音输入法,解决chrome不能输入中文
前端·数据库·chrome
PythonPioneer17 分钟前
每日Html 4.24
前端·html
小小小小宇19 分钟前
H5秒开且不影响版本更新
前端
我有一棵树23 分钟前
元素滚动和内容垂直居中同时存在,完美的 html 元素垂直居中的方法flex + margin: auto
前端·html
excel43 分钟前
webpack 运行时模版 第 四 节 /lib/RuntimeTemplate.js
前端
好_快1 小时前
Lodash源码阅读-createSet
前端·javascript·源码阅读
好_快1 小时前
Lodash源码阅读-remove
前端·javascript·源码阅读
excel1 小时前
webpack 运行时模版 第 五 节 /lib/RuntimeTemplate.js
前端
好_快1 小时前
Lodash源码阅读-arrayIncludes
前端·javascript·源码阅读
好_快1 小时前
Lodash源码阅读-arrayIncludesWith
前端·javascript·源码阅读