首页 > 其他分享 >canvas实现抠图,画笔,水印等功能

canvas实现抠图,画笔,水印等功能

时间:2024-11-18 17:28:58浏览次数:1  
标签:canvas const target 画笔 ctx 水印 querySelector document

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      html,
      body {
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0;
        margin: 0;
      }
      .canvas-box {
        display: flex;
        position: relative; 
      }
      #canvas {
        box-shadow: 0 0 0 1px #ccc;
      }
      .option {
        padding-left: 12px;
      }
      .item {
        display: flex;
        align-items: center;
        margin-bottom: 6px;
      }
      .btn-box {
        width: 300px;
        display: flex;
        align-items: center;
        justify-content: center;
        flex-wrap: wrap;
      }
      .cursor {
        width: 20px;
        height: 20px;
        position: absolute;
        border-radius: 50%;
        transform: translate(-50%, -50%);
        box-shadow: 0 0 0 2px #000 inset;
        background-color: transparent;
        pointer-events: none;
        opacity: 0;
        z-index: 99;
      }
    </style>
  </head>
  <body>
    <div class="canvas-box">
      <div class="cursor"></div>
      <canvas id="canvas" style="background-color: rgba(0, 0, 0, 0)"></canvas>
    </div>
    <div class="option">
      <div class="item">
        画笔大小(2~50):
        <input type="number" name="pencil" id="" min="2" max="50" value="20" />
      </div>
      <div class="item">
        橡皮大小(2~50):
        <input type="number" name="eraser" id="" min="2" max="50" value="20" />
      </div>
      <div class="item">
        保存图片质量(0.4~1):
        <input
          type="number"
          name="quality"
          id=""
          min="0.4"
          max="1"
          value="0.8"
          step="0.1"
        />
      </div>
      <div class="item">
        保存格式:
        <input type="radio" data-type="imgType" name="png" id="" checked />png
        <input type="radio" data-type="imgType" name="jpeg" id="" />jpeg
        <input type="radio" data-type="imgType" name="webp" id="" />webp
      </div>
      <div class="item">
        画布随上传图片缩放:
        <input type="radio" data-type="imgRule" name="long" checked />长边
        <input type="radio" data-type="imgRule" name="short" />短边
        <input type="radio" data-type="imgRule" name="custom" />自适应
      </div>
      <div class="item">
        水印文案:<input
          type="text"
          name="waterMark"
          value="大吉大利,今晚吃鸡"
        />
      </div>
      <div class="item">
        水印文案颜色:<input type="color" name="waterMarkColor" />
      </div>
      <div class="item">
        水印透明度(0.1~1):
        <input
          type="number"
          name="waterMarkOpacity"
          id=""
          min="0.1"
          max="1"
          value="0.15"
          step="0.01"
        />
      </div>
      <div class="item">
        水印文字大小(10~60):
        <input
          type="number"
          name="fontSize"
          id=""
          min="12"
          max="60"
          value="14"
        />
      </div>
      <div class="item">
        水印旋转角度(-360~360):
        <input
          type="number"
          name="rotate"
          id=""
          min="-360"
          max="360"
          value="-25"
        />
      </div>
      <div class="item">
        水印水平间距(50~500):
        <input type="number" name="x" id="" min="20" max="500" value="100" />
      </div>
      <div class="item">
        水印垂直间距(50~500):
        <input type="number" name="y" id="" min="20" max="500" value="100" />
      </div>
      <div class="item">
        水印X轴偏移量(50~500):
        <input
          type="number"
          name="offsetX"
          id=""
          min="50"
          max="300"
          value="50"
        />
      </div>
      <div class="item">
        水印Y轴偏移量(50~500):
        <input
          type="number"
          name="offsetY"
          id=""
          min="50"
          max="300"
          value="50"
        />
      </div>
      <div class="item">
        水印文案最大宽度(50~500):
        <input
          type="number"
          name="maxWidth"
          id=""
          min="50"
          max="300"
          value="200"
        />
      </div>
      <div class="btn-box">
        <button name="usePencil">使用画笔</button>
        <button name="useEraser">使用橡皮</button>
        <input type="color" name="color" id="" />
        <button name="reset">清空画布</button>
        <button name="undo">撤销</button>
        <button name="redo">恢复</button>
        <button name="save">保存</button>
        <button name="upload">上传图片</button>
        <button name="waterMark">使用水印</button>
        <button name="cutout">抠图</button>
      </div>
    </div>
    <input
      type="file"
      name="file"
      accept="image/jpeg,image/png,image/webp"
      style="display: none"
    />
    <script>
      const container = document.querySelector(".canvas-box");
      const cursorDom = document.querySelector(".cursor");
      const fileDom = document.querySelector('input[type="file"]');
      const canvas = document.querySelector("#canvas");
      const ctx = canvas.getContext("2d", { willReadFrequently: true });
      const cursor = document.querySelector(".cursor");
      const usePencil = document.querySelector('button[name="usePencil"]');
      const useEraser = document.querySelector('button[name="useEraser"]');
      const pencilRange = document.querySelector('input[name="pencil"]');
      const eraserRange = document.querySelector('input[name="eraser"]');
      const qualityRange = document.querySelector('input[name="quality"]');
      const colorPick = document.querySelector('input[name="color"]');
      const cutout = document.querySelector('button[name="cutout"]');
      const waterMarkColorPick = document.querySelector(
        'input[name="waterMarkColor"]'
      );
      const waterMarkOpacityRange = document.querySelector(
        'input[name="waterMarkOpacity"]'
      );
      const imgTypeRadios = document.querySelectorAll(
        'input[data-type="imgType"]'
      );
      const imgRuleRadios = document.querySelectorAll(
        'input[data-type="imgRule"]'
      );
      const fontSizeRange = document.querySelector('input[name="fontSize"]');
      const xRange = document.querySelector('input[name="x"]');
      const yRange = document.querySelector('input[name="y"]');
      const rotateRange = document.querySelector('input[name="rotate"]');
      const offsetRangeX = document.querySelector('input[name="offsetX"]');
      const offsetRangeY = document.querySelector('input[name="offsetY"]');
      const maxWidthRange = document.querySelector('input[name="maxWidth"]');
      const reset = document.querySelector('button[name="reset"]');
      const undo = document.querySelector('button[name="undo"]');
      const redo = document.querySelector('button[name="redo"]');
      const save = document.querySelector('button[name="save"]');
      const upload = document.querySelector('button[name="upload"]');
      const waterMark = document.querySelector('button[name="waterMark"]');

      const canvasDefaultSize = 600;
      canvas.width = canvasDefaultSize;
      canvas.height = canvasDefaultSize;
      let pencil = 20;
      let eraser = 20;
      let quality = 0.8;
      let imgType = "png";
      let imgRule = "long";
      let isPencil = true;
      let isEraser = false;
      let isDrawingLine = false;
      let colors = "#000";
      let historyIdx = 0;
      let history = [];
      let canvasArea = [0, 0, canvas.width, canvas.height];
      let text = "大吉大利,今晚吃鸡";
      let rotate = -25;
      let maxWidth = 200;
      let offsetX = 50;
      let offsetY = 50;
      let gap = [100, 100];
let isCutout=false
      const setCursorSize = (size) => {
        cursor.style.width = size + "px";
        cursor.style.height = size + "px";
      };
      cutout.onclick=(e)=>{
        isCutout=!isCutout
        if(isCutout){
          cutout.innerHTML='取消抠图'
        }else{
          cutout.innerHTML='抠图'
        }

      }
      pencilRange.oninput = (e) => {
        if (!e.target.value) e.target.value = 20;
        pencil = e.target.valueAsNumber;
        isPencil && setCursorSize(pencil);
      };

      eraserRange.oninput = (e) => {
        if (!e.target.value) e.target.value = 20;
        eraser = e.target.valueAsNumber;
        isEraser && setCursorSize(eraser);
      };

      qualityRange.oninput = (e) => {
        if (!e.target.value) e.target.value = 0.8;
        quality = e.target.valueAsNumber;
      };

      fontSizeRange.oninput = (e) => {
        if (!e.target.value) e.target.value = 14;
        ctx.font = `500 ${e.target.valueAsNumber}px sans-serif`;
      };

      xRange.oninput = (e) => {
        if (!e.target.value) e.target.value = 100;
        gap[0] = e.target.valueAsNumber;
      };

      yRange.oninput = (e) => {
        if (!e.target.value) e.target.value = 100;
        gap[1] = e.target.valueAsNumber;
      };

      rotateRange.oninput = (e) => {
        if (Number.isNaN(e.target.valueAsNumber)) e.target.value = -25;
        rotate = e.target.valueAsNumber;
      };

      offsetRangeX.oninput = (e) => {
        if (Number.isNaN(e.target.valueAsNumber)) e.target.value = 50;
        offsetX = e.target.valueAsNumber;
      };

      offsetRangeY.oninput = (e) => {
        if (Number.isNaN(e.target.valueAsNumber)) e.target.value = 50;
        offsetY = e.target.valueAsNumber;
      };

      maxWidthRange.oninput = (e) => {
        if (Number.isNaN(e.target.valueAsNumber)) e.target.value = 200;
        maxWidth = e.target.valueAsNumber;
      };

      waterMarkOpacityRange.oninput = (e) => {
        const [r, g, b] = hex2Rgb(waterMarkColorPick.value);
        ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${waterMarkOpacityRange.valueAsNumber})`;
      };

      fileDom.oninput = (e) => {
        const fileReader = new FileReader();
        fileReader.readAsDataURL(e.target.files[0]);
        fileReader.onload = (e) => {
          const img = document.createElement("img");
          img.src = e.target.result;
          img.onload = () => {
            let wh = [img.width, img.height];
            let canvasWh = [canvasDefaultSize, canvasDefaultSize];
            if (imgRule === "long") {
              const ratio = canvasDefaultSize / Math.max(...wh);
              wh = [img.width * ratio, img.height * ratio];
            } else if (imgRule === "short") {
              const ratio = canvasDefaultSize / Math.min(...wh);
              wh = [img.width * ratio, img.height * ratio];
            } else {
              canvasWh = [img.width, img.height];
            }
            ctx.clearRect(...canvasArea);
            canvasArea = [0, 0, ...canvasWh];
            canvas.width = canvasWh[0];
            canvas.height = canvasWh[1];
            ctx.drawImage(img, 0, 0, ...wh);
          };
        };
      };

      imgTypeRadios.forEach((el) => {
        el.onclick = () => {
          imgTypeRadios.forEach((el2) => (el2.checked = false));
          el.checked = true;
          imgType = el.name;
        };
      });

      imgRuleRadios.forEach((el) => {
        el.onclick = () => {
          imgRuleRadios.forEach((el2) => (el2.checked = false));
          el.checked = true;
          imgRule = el.name;
        };
      });

      colorPick.oninput = (e) => (colors = e.target.value);

      waterMarkColorPick.oninput = (e) => {
        const [r, g, b] = hex2Rgb(e.target.value);
        ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${waterMarkOpacityRange.valueAsNumber})`;
      };

      // 撤销
      undo.onclick = () => {
        historyIdx--;
        if (historyIdx <= -1) {
          historyIdx = -1;
          ctx.clearRect(...canvasArea);
        } else {
          ctx.putImageData(history[historyIdx], 0, 0);
        }
      };

      // 恢复
      redo.onclick = () => {
        historyIdx++;
        if (historyIdx > history.length - 1) historyIdx = history.length - 1;
        else ctx.putImageData(history[historyIdx], 0, 0);
      };

      save.onclick = () => {
        const a = document.createElement("a");
        a.href = canvas.toDataURL(`image/${imgType}`, quality);
        a.download = `save_${Date.now()}`;
        document.body.append(a);
        a.click();
        a.remove();
      };

      upload.onclick = () => {
        fileDom.value = "";
        fileDom.click();
      };

      usePencil.onclick = () => {
        isPencil = true;
        isEraser = false;
        setCursorSize(pencil);
      };

      useEraser.onclick = () => {
        isPencil = false;
        isEraser = true;
        setCursorSize(eraser);
      };

      reset.onclick = () => {
        history = [];
        historyIdx = -1;
        ctx.clearRect(...canvasArea);
        canvas.width = canvasDefaultSize;
        canvas.height = canvasDefaultSize;
        canvasArea = [0, 0, canvas.width, canvas.height];
        const [r, g, b] = hex2Rgb(waterMarkColorPick.value);
        waterMarkOpacityRange.oninput();
        ctx.font = `500 ${fontSizeRange.valueAsNumber}px sans-serif`;
      };

      waterMark.onclick = () => {
        let [x, y] = gap;
        let [, , w, h] = canvasArea;
        const xLine = Math.ceil((w - offsetX) / x);
        const yLine = Math.ceil((h - offsetY) / y);
        waterMarkOpacityRange.oninput();
        ctx.font = `500 ${fontSizeRange.valueAsNumber}px sans-serif`;
        for (let i = 0; i <= xLine; i++) {
          const x0 = x * i + offsetX;
          for (let j = 0; j <= yLine; j++) {
            drawWaterMark(x0, y * j + offsetY);
          }
        }
      };
      const drawWaterMark = (x, y) => {
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate((rotate / 180) * Math.PI);
        ctx.fillText(text, -ctx.measureText(text).width / 2, 0, maxWidth);
        ctx.restore();
      };

      canvas.onmousedown = (e) => {
        // 只允许左键
        if (e.button) return;
        if (!isPencil && !isEraser) return false;
        isDrawingLine = true;
        ctx.beginPath();
        ctx.globalCompositeOperation = isEraser
          ? "destination-out"
          : "source-over";
        ctx.strokeStyle = isEraser ? "#fff" : colors;
        if(isCutout){
          ctx.globalCompositeOperation = 'destination-out';
          ctx.strokeStyle=undefined
        }
        
        ctx.lineCap = "round";
        ctx.lineJoin = "round";
        ctx.lineWidth = isEraser ? eraser : pencil;
        ctx.moveTo(e.offsetX, e.offsetY);
        ctx.lineTo(e.offsetX, e.offsetY);
        ctx.stroke();
        canvas.onmousemove = composeCanvasMousemove;
      };

      canvas.onmouseup = () => {
        if (!isPencil && !isEraser) return false;
        isDrawingLine = false;
        canvas.onmousemove = null;
        // 使用了橡皮时,必须存在历史记录,否则不做任何事
        if (isEraser && !history.length) return false;
        addOneHistory(lineToAlpha());
      };

      container.onmouseenter = (e) => {
        if (!container.contains(e.toElement)) return;
        if (isPencil || isEraser) {
          // 离开画布时,若正在使用划线功能,重新开启一个路径
          if (isDrawingLine) {
            ctx.beginPath();
            ctx.moveTo(e.offsetX, e.offsetY);
            ctx.lineTo(e.offsetX, e.offsetY);
            ctx.stroke();
          }
          canvas.style.cursor = "none";
          cursorDom.style.opacity = "1";
          cursorDom.style.left = e.offsetX + "px";
          cursorDom.style.top = e.offsetY + "px";
          cursorDom.style.boxShadow = `0 0 0 2px ${
            isPencil ? colors : "#ccc"
          } inset`;
        } else canvas.style.cursor = "default";
      };

      container.onmousemove = (e) => {
        if (isPencil || isEraser) {
          cursorDom.style.opacity = "1";
          cursorDom.style.left = e.offsetX + "px";
          cursorDom.style.top = e.offsetY + "px";
          return false;
        }
      };

      container.onmouseout = (e) => {
        if (!container.contains(e.fromElement)) return;
        cursorDom.style.opacity = "0";
      };

      const composeCanvasMousemove = (e) => {
        ctx.lineTo(e.offsetX, e.offsetY);
        ctx.stroke();
      };

      const lineToAlpha = () => {
        const imageData = ctx.getImageData(...canvasArea);
        return imageData;
      };

      // 添加一项历史记录
      const addOneHistory = (json) => {
        if (history.length - 1 !== historyIdx) {
          history = history.slice(0, historyIdx + 1);
        }
        historyIdx = history.push(json) - 1;
      };

      // hex 2 rgb
      const hex2Rgb = (hex) => {
        const red = hex.substring(1, 3);
        const green = hex.substring(3, 5);
        const blue = hex.substring(5, 7);
        return [parseInt(red, 16), parseInt(green, 16), parseInt(blue, 16)];
      };

      // 鼠标抬起时,发现不处于画布内,调用画笔结束
      const handleLeaveCanvas = (e) => {
        if (!isDrawingLine) return;
        if (!container.contains(e.target)) canvas.onmouseup();
      };
      window.addEventListener("mouseup", handleLeaveCanvas);

      setCursorSize(pencil);
    </script>
  </body>
</html>

 

标签:canvas,const,target,画笔,ctx,水印,querySelector,document
From: https://www.cnblogs.com/ypSharing/p/18553242

相关文章

  • MutationObserver 防止用户篡改水印
    MutationObserver应用于水印制作时,之所以能够有效防止用户篡改数据,主要归因于其对DOM(文档对象模型)元素变化的强大监视能力。以下是对此现象的详细解释:一、MutationObserver的工作原理MutationObserver是一个监视DOM变动的接口,它能够监听DOM树的变化,并在检测到变动时执行回调函数......
  • canvas绘制文本
    练习一下canvas对文本的基本应用<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Title</title></head><body><div><labelfor="name">......
  • 数据可视化Canvas
    邂逅CanvasCanvas优缺点初体验CanvasCanvasGrid和坐标空间绘制矩形(Rectangle认识路径路径-绘制直线路径-绘制三角形(Triangle)路径-绘制圆弧(Arc)、圆(Circle)......
  • unigui可以实现水印纹的效果
    效果 procedureTMainForm.Set_watermark(xparent:TWinControl;s:string);varhf:TUniHTMLFrame;beginhf:=TUniHTMLFrame.Create(xparent);hf.Parent:=xparent;withhfdobeginHeight:=0;Width:=0;HTML.Clear;HTML.Add('<......
  • 如何批量打水印?六个电脑屏幕水印批量设置方法分享!步骤既简单,又快速!
    如何批量打水印?你是否曾经为需要在大量文件或屏幕上添加水印而感到烦恼?电脑屏幕水印,作为版权保护和信息安全的重要手段,重要性不言而喻。然而,手动逐个添加水印不仅耗时费力,还容易出错。那么,有没有一种方法能够批量、快速地为电脑屏幕或文件添加水印呢?答案是肯定的。本文,将......
  • [js] 突发奇想, 使用canvas绘制一个动态的扫描仪
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title&g......
  • HitPaw Watermark Remover(专业去除水印软件)v2.3.0.8中文版
    使用HitPawWatermarkRemover,您可以从视频和图像中删除水印,从而从您的历史记录中删除日期、建筑物、人物等。支持的源格式为JPEG、JPG、PNG、WebP和BMP。多种去除方法加载图像后,您可以放大和缩小,直到找到要擦除的图章。HitPawWatermarkRemover为您提供了多种选项供......
  • 【Canvas与艺术】黄蓝白八尖风车图案
    【成图】120*120的png图标大小图:【代码】<!DOCTYPEhtml><htmllang="utf-8"><metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/><head><title>632.黄蓝白八尖风车图案</title>......
  • 基于遗传优化的SVD水印嵌入提取算法matlab仿真
    1.程序功能描述基于遗传优化的的SVD水印嵌入提取算法。对比遗传优化前后SVD水印提取性能,并分析不同干扰情况下水印提取效果。2.测试软件版本以及运行结果展示MATLAB2022a版本运行SVD GA优化SVD 性能对比: 3.核心程序%遍历遗传算法返回的各代最优个体(从......
  • SynthID Text 现已发布|在 AI 生成文本中应用不可见水印的新技术
    你是否难以分辨一段文本是由人类撰写的,还是AI生成的?识别AI生成内容对于提升信息可信度、解决归因错误以及抑制错误信息至关重要。今天,GoogleDeepMind和HuggingFace很共同宣布,在Transformersv4.46.0版本中,我们正式推出了SynthIDText技术。这项技术能够通过使用log......