使用浮点数时,fillRect()不完全重叠

时间:2019-09-27 14:34:04

标签: javascript html5-canvas

以下代码(jsFiddle)在画布上的任意点绘制一个红色正方形,注意擦除前一个(通过在ctx.fillRect()上填充一个白色正方形:

<html>
  <canvas id='canvas' width=300 height=300/>
 <script>
   const ctx = document.getElementById('canvas').getContext('2d');
   let prevRect = null;
   for (let i = 0 ; i < 10; i++) {
     if (prevRect != null) {
       ctx.fillStyle='white';
       ctx.fillRect(prevRect.x, prevRect.y, 50, 50);
     }
     ctx.fillStyle='red';
     const newRect = {x: Math.random()*(300-50), y: Math.random()*(300-50)};
     ctx.fillRect(newRect.x, newRect.y, 50, 50);
     prevRect = newRect;
   }
  </script>
</html>

该代码无法完全擦除先前的正方形,并且工件残留在屏幕上。相反,如果执行以下操作:

const newRect = {x: Math.floor(Math.random()*(300-50)), y: Math.floor(Math.random()*(300-50))};

...然后一切都会按预期进行。

我的问题是为什么。将值保留在prevRect中似乎完全没有必要进行截断,因此对fillRect()的两次调用使用完全相同的坐标(即使使用浮点数),因此两个正方形应始终完美匹配对齐。

2 个答案:

答案 0 :(得分:2)

如何将红色渲染半个像素?

通过采用背景颜色并根据百分比对前景颜色进行插值,可以使前景与背景重叠多少。 现在,当您尝试“删除”它时,您将对背景色执行相同的操作。

所以从数学上来说,像素的颜色是:

var afterRender = interpolate(background, forground, percentage);
var afterDelete = interpolate(afterRender, background, percentage);

让我们处理一些数字:(一个简单而肮脏的例子)

const update = () => {
  var bgColor = +bg.value.replace(/^#?/, "0x");
  bg.style.backgroundColor = toColor(bgColor);
  
  var fgColor = +fg.value.replace(/^#?/, "0x");
  fg.style.backgroundColor = toColor(fgColor);
  
  var percentage = overlap.value / 100;

  var afterRenderColor = interpolate(bgColor, fgColor, percentage);
  
  afterRender.textContent = afterRender.style.background = toColor(afterRenderColor);
  
  // now trying to erase this by overwriting with the background-color
  var afterDeleteColor = interpolate(afterRenderColor, bgColor, percentage);
  
  afterDelete.textContent = afterDelete.style.background = toColor(afterDeleteColor);
}

const toColor = v => "#" + v.toString(16).padStart(6, 0).toUpperCase();

const interpolate = (a, b, t) => ((a&0xFF0000) * (1-t) + (b&0xFF0000) * t) & 0xFF0000
    | ((a&0x00FF00) * (1-t) + (b&0x00FF00) * t) & 0x00FF00
    | ((a&0x0000FF) * (1-t) + (b&0x0000FF) * t) & 0x0000FF;


[bg, fg, overlap].forEach(input => input.onchange = input.oninput = update);
update();
#bg,
#fg,
#afterRender,
#afterDelete {
  border: 1px solid black;
  padding: 20px;
}
<label>Background: <input id="bg" type="text" pattern="/#?[0-9a-f]{6}/" value="#FFFFFF"/></label>
<br>
<label>Foreground: <input id="fg" type="text" pattern="/#?[0-9a-f]{6}/" value="#FF0000"/></label>
<br>
<label>"half a pixel" of overlap: <input id="overlap" type="range" min="0" max="100" value="50"></label>
more or less ;)

<br>
<br>

Color after rendering "half a pixel" of the foreground over the background:
<div id="afterRender"></div>

<br>
Color after trying to erase that by rendering "half a pixel" of the background-color over that:
<div id="afterDelete"></div>

答案 1 :(得分:0)

问题源于在画布上绘制事物的基本方式。绘制正方形时,形状的边缘会稍微“羽化”。因此,当您在前一个红色框上绘制白色框时,红色的残余会渗入半透明边缘。

如果您绘制10个白框而不是一个,那么问题就消失了。或者,如果您将白色框的尺寸扩大了0.5像素,那可能会有所帮助。

const ctx = document.getElementById('canvas').getContext('2d');
   let prevRect = null;
   for (let i = 0 ; i < 10; i++) {
     if (prevRect != null) {
       ctx.fillStyle='white';
       ctx.fillRect(prevRect.x - 0.75, prevRect.y - 0.75, 51.5, 51.5);
     }
     ctx.fillStyle='red';
     const newRect = {x: Math.random()*(300-50), y: Math.random()*(300-50)};
     ctx.fillRect(newRect.x, newRect.y, 50, 50);
     prevRect = newRect;
   }
body, html { padding: 0; }

canvas { border: 1px solid black; }
<canvas id=canvas height=300 width=300></canvas>

看起来每边都大0.75,效果很好,但是可以肯定,这是画布“逻辑”大小与实际屏幕大小的函数。