JQuery在画布图像上画线并重置线而不影响图像

Posted

技术标签:

【中文标题】JQuery在画布图像上画线并重置线而不影响图像【英文标题】:JQuery draw line on canvas image and reset line without affecting image 【发布时间】:2021-04-24 19:44:04 【问题描述】:

我有这个问题,我从输入字段上传图像并将其绘制到画布中。我有事件侦听器(mousemoveclickkeydown),当用户点击图像的任何部分时,该点就是x1y1 坐标,当他们移动鼠标时,这就是x2y2 每次更新的地方。

问题是总是需要一行,我用一个名为resetLine的函数来管理它,所以当x2y2发生变化时,行被重置所以我们不会结束每次鼠标移动都会出现一堆连续的线条。

问题在于我们正在绘制的图像,我重置线条的方式似乎也重置了图像,我认为这是预期的,所以我必须在每次鼠标移动时重新绘制图像,我认为不是这样做的正确方法,而且您每次移动鼠标时都可以看到图像闪烁。 任何帮助将不胜感激。

编辑:这是我之前制作的一个示例,但图像是使用 javascript 制作的 css 背景 url 图像,但它有很多问题,我不得不开始使用画布绘制背景图像: https://angle-measurement.herokuapp.com

这是我在 codepen 上制作的一个类似的: https://codepen.io/Overflow0x/pen/yLaZxqm,但他们都使用 css 的背景图片,而不是画布。

class APP 
  constructor() 
    this.$imageFileInput = $("#imageFileInput");
    this.$lineWeight = $("#lineWeight");
    this.$lineColor = $("#lineColor");
    this.$reset = $("#reset");
    this.$canvas = $("#canvas");
    this.canvas = document.getElementById("canvas");
    this.lineColor = "";
    this.lineWidth = 3;
    
    this.x1y1 = 
      x: 4,
      y: 8
    ;
    
    this.x2y2 = 
      x: 0,
      y: 0
    ;
    
    this.imageDataUrl = null;
    this.imageWidth = 0;
    this.imageHeight = 0;
    this.ctx = this.canvas.getContext("2d");
    this.y2X2MoveHandlerState = 0;
    // 0 unregistered, 1, registered
  

  setCanvasDimentions() 
    this.ctx.canvas.width = (83 / 100) * window.innerWidth;
    this.ctx.canvas.height = (60 / 100) * window.innerHeight;
  

  registerX1Y1ClickHandler() 
    var self = this;
    self.$canvas.on('click', function(event) 
      //console.log('registerX1Y2ClickHandler event\t', event);
      console.log("click", event)
      self.x1y1 = self.getMousePos(event);
      self.log();
    );
  

  registerSaveLineClickHandler() 
    var self = this;
    $(document).keydown(function(e) 
      console.warn(e);
      if (e.keyCode == 83) 
        // 's' button pressed...
        self.toggleY2X2MoveHandler();
      
    );
  

  registerY2X2MoveHandler(mode = null) 
    this.y2X2MoveHandlerState = 1;
    var self = this;
    self.$canvas.on('mousemove', function(event) 
      console.log("mousemove", event);
      self.x2y2 = self.getMousePos(event);
      self.log();
      self.resetLine();
      self.draw_line();
      self.draw_angle();
    );
  

  toggleY2X2MoveHandler() 
    var self = this;
    if (self.y2X2MoveHandlerState == 0) 
      self.registerY2X2MoveHandler();
     else 
      self.$canvas.off("mousemove");
      self.y2X2MoveHandlerState = 0;
    
  

  registerOnImageChangeHandler() 
    var self = this;
    self.$imageFileInput.on('change', function(event) 
      console.log("image change", event);
      const file = event.target.files[0];
      self.getBase64Promise(file).then(function(result) 
        self.imageDataUrl = result;
        self.drawImage(result);
        //self.ctx.canvas.style.backgroundImage = 'url(\'' + result + '\')';
      ).catch(function(error) 
        console.error("ikh\t", error)
        self.error = error;
      )
    );
  

  registerOnLineWeightChangeHandler() 
    var self = this;
    self.$lineWeight.on('change', function(event) 
      self.lineWidth = event.target.value;
    );
  

  registerOnColorChangeHandler() 
    var self = this;
    self.$lineColor.on('change', function(event) 
      self.lineColor = event.target.value;
    );
  

  registerOnResetHandler() 
    var self = this;
    self.$reset.on('click', function() 
      var cords = 
        x: 0,
        y: 0
      ;
      self.x1y1 = cords;
      self.x2y2 = cords;
      self.resetLine();
    );
  

  angle() 
    var x1y1 = this.getObjectValuesAsArray(this.x1y1);
    var x2y2 = this.getObjectValuesAsArray(this.x2y2);
    var x1 = x1y1[0]
    var y1 = x1y1[1]
    var x2 = x2y2[0]
    var y2 = x2y2[1]
    var dy = y2 - y1;
    var dx = x2 - x1;
    var theta = Math.atan2(dy, dx); // range (-PI, PI]
    theta *= 180 / Math.PI; // rads to degs, range (-180, 180]
    //if (theta < 0) theta = 360 + theta; // range [0, 360)
    theta = Number(theta).toPrecision(4);
    return theta;
  

  getBase64Promise(file) 
    return new Promise(function(resolve, reject) 
      var reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = function() 
        return resolve(reader.result);
      ;
      reader.onerror = function(error) 
        return reject(error);
      ;
    );
  

  getMousePos(event) 
    var rect = this.$canvas.get(0).getBoundingClientRect();
    return 
      x: (event.clientX - rect.left) / (rect.right - rect.left) * this.ctx.canvas.width,
      y: (event.clientY - rect.top) / (rect.bottom - rect.top) * this.ctx.canvas.height
    ;
  

  /*
  This code (getMousePos) takes into account both changing coordinates to canvas space 
  (evt.clientX - rect.left) and scaling when canvas logical size differs 
  from its style size 
  (/ (rect.right - rect.left) * canvas.width see: Canvas width and height in html5).
  */

  getObjectValuesAsArray(obj) 
    return Object.keys(obj).map(function(k) 
      return obj[k]
    );
  

  resetLine() 
    this.ctx.save();
    this.ctx.globalCompositeOperation = 'copy';
    this.ctx.strokeStyle = 'transparent';
    this.ctx.beginPath();
    this.ctx.lineTo(0, 0);
    this.ctx.stroke();
    this.drawImage(this.imageDataUrl);
    this.ctx.restore();
  

  drawImage(result) 
    var self = this;
    console.warn(result);
    var img = $('<img>', 
      src: result
    );
    img.on('load', function() 
      console.warn("jq image\t", img);
      self.imageWidth = img.get(0).naturalWidth;
      self.imageHeight = img.get(0).naturalHeight;
      self.ctx.canvas.width = img.get(0).naturalWidth;
      self.ctx.canvas.height = img.get(0).naturalHeight;
      self.ctx.clearRect(0, 0, self.ctx.canvas.width, self.ctx.canvas.height);
      self.ctx.drawImage(img.get(0), 0, 0, self.ctx.canvas.width, self.ctx.canvas.height);
    );
  

  draw_line() 
    this.ctx.strokeStyle = this.lineColor;
    this.ctx.lineWidth = this.lineWidth;
    var x1y1 = this.getObjectValuesAsArray(this.x1y1);
    var x2y2 = this.getObjectValuesAsArray(this.x2y2);
    //console.log("vectors x1y1\t", x1y1, "\t vectors x2y2", x2y2);
    this.ctx.beginPath();
    this.ctx.moveTo(x1y1[0], x1y1[1]);
    this.ctx.lineTo(x2y2[0], x2y2[1]);
    this.ctx.stroke();
    //this.ctx.strokeRect(vectors[0], vectors[1], vectors[2], vectors[3]);
  

  draw_angle() 
    this.ctx.strokeStyle = this.lineColor;
    this.ctx.lineWidth = this.lineWidth;
    var x2y2 = this.getObjectValuesAsArray(this.x2y2);
    //console.log("vectors x1y1\t", x1y1, "\t vectors x2y2", x2y2);
    this.ctx.font = "18px DejaVuSansMono33-Regular";
    this.ctx.fillStyle = this.lineColor;
    var x2 = x2y2[0];
    var y2 = x2y2[1];
    var dx = x2;
    var dy = y2;
    var width = this.ctx.canvas.width;
    var height = this.ctx.canvas.height;

    console.warn("canvas width and height", width, height);


    this.ctx.fillText(this.angle() + "°", dx, dy);
    //this.ctx.strokeRect(vectors[0], vectors[1], vectors[2], vectors[3]);
  

  log() 
    console.log("x2y2\t", this.x2y2);
    //console.log("ctx", this.ctx)
  

  init() 
    this.setCanvasDimentions();
    this.registerX1Y1ClickHandler();
    this.registerY2X2MoveHandler();
    this.registerOnImageChangeHandler();
    this.registerOnColorChangeHandler();
    this.registerOnLineWeightChangeHandler();
    this.registerSaveLineClickHandler();
    this.registerOnResetHandler();
    this.log();
  
;

$(document).ready(function() 
  var app = new APP();
  app.init();
);
@font-face 
  font-family: 'DejaVuSansMono33-Regular';
  font-style: normal;
  font-weight: normal;
  src: url("/static/DejaVuSansMono33-Regular.otf") format("truetype");


html,
body 
  font-family: DejaVuSansMono33-Regular, "Helvetica Neue", sans-serif;
  font-size: 14px;
  background: "red";
  height: 100%;


canvas#canvas 
  height: 100%;
  background-size: cover;
  background-size: 100% 100%;
  display: inline;


#measure 
  position: absolute;
  left: -10000px;
  top: -100000px;


body 
  text-align: center;
  background: #f2f6f8;


#canvas-container 
  height: 100%;
  width: 100%;
  text-align: center;
  padding: 1rem;
  background: none;


input[type="color"] 
  -webkit-appearance: none;
  border: none;
  width: 32px;
  height: 32px;


input[type="color"]::-webkit-color-swatch-wrapper 
  padding: 0;


input[type="color"]::-webkit-color-swatch 
  border: none;


.container 
  height: 100% !important;


.section 
  padding-bottom: 0px !important;
  margin-bottom: 0px !important;
<link rel="stylesheet" type="text/css" media="screen" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<div class="container">
  <section class="section">
    <div class="columns">
      <div class="column">
        <div class="container">
          <div id="canvas-container">
            <canvas id="canvas"></canvas>
            <div id="measure"></div>
          </div>
        </div>
      </div>
    </div>
  </section>
  <section class="section" style="padding-top: 10rem;">
    <div class="columns">
      <div class="column">
        <div class="container">
          <div class="field is-horizontal">
            <div class="field-body">
              <div class="field">
                <div class="file is-dark">
                  <label class="file-label">
                    <input class="file-input" type="file" name="imageFileInput" id="imageFileInput">
                    <span class="file-cta">
                      <span class="file-label">Upload Picture</span>
                    </span>
                  </label>
                </div>
              </div>
              <div class="field">
                <button class="button is-dark" id="reset">Reset to initial coordinates</button>
              </div>
              <div class="field">
                <label class="label is-expanded">Vector Color</label>
                <div class="control">
                  <input class="input" type="color" name="lineColor" id="lineColor" value="#000000" />
                </div>
              </div>
              <div class="field">
                <label class="label">Vector Size</label>
                <div class="control">
                  <input class="input" type="number" name="lineWeight" id="lineWeight" min="0" value="2" />
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

【问题讨论】:

【参考方案1】:

罪魁祸首显然是这个函数:

  drawImage(result) 
    var self = this;
    console.warn(result);
    var img = $('<img>', 
      src: result
    );
    img.on('load', function() 
      console.warn("jq image\t", img);
      self.imageWidth = img.get(0).naturalWidth;
      self.imageHeight = img.get(0).naturalHeight;
      self.ctx.canvas.width = img.get(0).naturalWidth;
      self.ctx.canvas.height = img.get(0).naturalHeight;
      self.ctx.clearRect(0, 0, self.ctx.canvas.width, self.ctx.canvas.height);
      self.ctx.drawImage(img.get(0), 0, 0, self.ctx.canvas.width, self.ctx.canvas.height);
    );
  

但那里实际发生了什么?

好吧,除了 resetLine()draw_angle() 它是 mouseMove 回调处理程序的一部分,因此每次鼠标移动都会执行。

所以:

    var img = $('<img>', 
      src: result
    );

您正在创建一个新图像并使用:

img.on('load', function() );

你实际上是在等待它被“加载”——每一次鼠标移动。

这意味着您观察到的闪烁是由触发图像的 onload 事件并且您的画布最终获得要绘制的图像所花费的时间引起的。

修复相当简单。我建议设置一个类变量,例如

this.image=null;

并按如下方式修改drawImage()函数:

如果this.imagenull,添加onLoad监听器并将局部变量img赋值给this.image 如果this.imagenot null,则将其用作参考而不是img

类似:

  drawImage(result) 
    var self = this;
    console.warn(result);
    var img = $('<img>', 
      src: result
    );

    if (this.image == null) 
      img.on('load', function() 
        console.warn("jq image\t", img);
        self.imageWidth = img.get(0).naturalWidth;
        self.imageHeight = img.get(0).naturalHeight;
        self.ctx.canvas.width = img.get(0).naturalWidth;
        self.ctx.canvas.height = img.get(0).naturalHeight;
        self.ctx.clearRect(0, 0, self.ctx.canvas.width, self.ctx.canvas.height);
        self.ctx.drawImage(img.get(0), 0, 0, self.ctx.canvas.width, self.ctx.canvas.height);
      );
      this.image = img;
     else 
      img = this.image;
      self.imageWidth = img.get(0).naturalWidth;
      self.imageHeight = img.get(0).naturalHeight;
      self.ctx.canvas.width = img.get(0).naturalWidth;
      self.ctx.canvas.height = img.get(0).naturalHeight;
      self.ctx.clearRect(0, 0, self.ctx.canvas.width, self.ctx.canvas.height);
      self.ctx.drawImage(img.get(0), 0, 0, self.ctx.canvas.width, self.ctx.canvas.height);
    
  

顺便说一句 - 这绝不是最优雅的方式,它只是为了让你开始。

【讨论】:

感谢您的时间和帮助,我理解您的解释,但是现在,即使我上传新的图像,我也会得到相同的图像,并且相同的图像会在第一次鼠标移动事件时消失,任何有什么想法吗? 墨菲没问题!问题是image 永远不会再次“重置”为空,这是真的。您可以在 fileInput 更改事件回调中执行此操作 self.$imageFileInput.on('change', function(event) console.log("image change", event); self.image=null; // 剩余代码 ); 谢谢,你说得对。我不想打扰,但是为什么您认为鼠标移动后图像会消失?可能是因为我处理 resetLine() 或 drawAngle() 的方式,正如您在回答中提到的那样?

以上是关于JQuery在画布图像上画线并重置线而不影响图像的主要内容,如果未能解决你的问题,请参考以下文章

学习记录:各种YUV图像上画线的实现

动态图处理

iOS Swift - 在 UIImage 上画线以保存到文件

我们可以在陀螺仪给出的两点之间在android画布上画线吗?

matlab在其中一幅图上画线怎么画如图所示

在 svg 图像上画一条线