【问题标题】:Set allowed drawing area in JavaScript canvas API在 JavaScript 画布 API 中设置允许的绘图区域
【发布时间】:2020-05-18 16:32:05
【问题描述】:

我正在使用 JavaScript 画布 API 进行免费绘图。我坚持掩盖允许绘制的区域 - 在我的示例中,它应该只是语音气泡区域。 我正在使用这个 Vue 组件:https://github.com/sametaylak/vue-draw/blob/master/src/components/CanvasDraw.vue

draw(event) {
  this.drawCursor(event);
  if (!this.isDrawing) return;
  if (this.tools[this.selectedToolIdx].name === 'Eraser') {
    this.canvasContext.globalCompositeOperation = 'destination-out';
  } else {
    this.canvasContext.globalCompositeOperation = 'source-over';
    this.canvasContext.strokeStyle = this.tools[this.selectedToolIdx].color;
  }
  this.canvasContext.beginPath();
  this.canvasContext.moveTo(this.lastX, this.lastY);
  this.canvasContext.lineTo(event.offsetX, event.offsetY);
  this.canvasContext.stroke();
  [this.lastX, this.lastY] = [event.offsetX, event.offsetY];
},
drawCursor(event) {
  this.cursorContext.beginPath();
  this.cursorContext.ellipse(
    event.offsetX, event.offsetY,
    this.brushSize, this.brushSize,
    Math.PI / 4, 0, 2 * Math.PI
  );
  this.cursorContext.stroke();
  setTimeout(() => {
    this.cursorContext.clearRect(0, 0, this.width, this.height);
  }, 100);
},

【问题讨论】:

  • 你能不检查event.offsetX, event.offsetY 与气泡的边界吗?
  • 不管怎样,如果你提供一个可运行的代码sn-p,你更有可能得到一个好的答案。
  • @avejidah 如前所述,我正在使用这个 Vue 组件:github.com/sametaylak/vue-draw
  • 如果您能用预期的结果和可重现的代码更好地描述您的问题,我们或许可以提供帮助
  • 您在画布中是否只有一张图像? (粉红色背景和对话泡泡)或者它们是独立的对象?

标签: javascript vue.js canvas


【解决方案1】:

有一个内置的clip() 方法将路径设置为剪切区域。

var ctx=document.getElementById("cnv").getContext("2d");
ctx.lineWidth=2;

ctx.strokeStyle="red";
ctx.moveTo(0,0);
ctx.lineTo(100,100);
ctx.stroke();                // 1.

ctx.strokeStyle="black";
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(100,10);
ctx.lineTo(100,60);
ctx.lineTo(30,60);
ctx.lineTo(10,80);
ctx.closePath();
ctx.stroke();                // 2.
ctx.clip();                  // 3.

ctx.strokeStyle="green";
ctx.beginPath();
ctx.moveTo(0,100);
ctx.lineTo(100,0);
ctx.stroke();                // 4.
<canvas id="cnv"></canvas>
  1. 红线在 0,0 和 100,100 之间绘制,没有剪裁
  2. 气泡以黑色绘制
  3. 气泡设置为剪切区域
  4. 在 0,100 和 100,0 之间绘制绿线,并正确地夹在气泡中。

在实践中,您可能希望在气泡内有一个像素的剪切区域,因此是单独的路径(不是stroke()-d,只是clip()-ped),因此绘图不能修改气泡本身。如果现在按原样放大,您会看到绿线实际上过度绘制了气泡的内部像素(线宽为 2 个像素,而外部是“未损坏”的)。

【讨论】:

    【解决方案2】:

    确定给定点是否属于多边形区域是一个非常棘手的solved problem在计算机科学中。

    在这个具体的场景中,您将画布设置为背景和 500x300 尺寸,您实际上并不需要使用 光线投射算法
    例如,您可以将对话气泡区域划分为一个矩形和一个三角形,然后使用event.offsetX, event.offsetY 检查任何给定点是否位于这两个图形中的任何一个内。

    代码示例:

     isPointInArea(event) {
          const x = event.offsetX;
          const y = event.offsetY;
           // For rectangle it is straightforward
          if (x >= 60 && x <= 325 && y >= 60 && y <= 215) { 
              return true;
          }
          /* Since two sides of this triangle are parallel to canvas
             It is enough to check y coordinate with one linear function of a third one
             in form of y = ax + b */
          if(x >= 60 && x <= 120 && y >= 215) {
            const boundaryY = -0.81818181818 * x + 313.181818182;
            if (y <= boundaryY) {
              return true;
            }
          }
           return false;
        }
    

    CanvasDraw.vue的draw函数中

    draw(event) {
         if(!this.isPointInArea(event)) {
           return;
         }
    
         this.drawCursor(event);
         if (!this.isDrawing) return;
         ...
    

    Working example on codesandbox

    结果:

    编辑: 正如@tevemadar 所指出的,您也可以简单地使用Canvas API 的clip() 方法。如果您没有其他要渲染的内容(因为它不是游戏,所以可能就是这种情况),那么您只需执行一次 clip() 就可以了。否则请记住使用save() 方法(当然还有restore(),这样您也可以在语音气泡剪辑区域之外渲染内容。

    【讨论】:

    • 感谢您的回答。你会说什么能提供更好的性能 - clip() 或你的“isPointInArea”?
    • 不客气 :) 鉴于在您的情况下,只要我测量了两种解决方案的性能并且 clip() 的性能稍好一些,您就可以调用 clip。 (我用过浏览器的performance.measure())
    猜你喜欢
    • 1970-01-01
    • 2014-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-31
    • 2018-03-20
    • 2017-11-28
    相关资源
    最近更新 更多