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

相关推荐
zhougl9961 小时前
html处理Base文件流
linux·前端·html
花花鱼1 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_2 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo3 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡5 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木6 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!6 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷7 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript