fillRect () не перекрывается точно, когда используются числа с плавающей запятой - PullRequest
1 голос
/ 27 сентября 2019

Следующий код ( 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 ]

1 голос
/ 27 сентября 2019

Как визуализировать красный цвет на половину пикселя?

Используя цвет фона и интерполяцию с цветом переднего плана на основе процентного соотношения, насколько передний план перекрывает фон.Теперь, когда вы пытаетесь «удалить» это, вы делаете то же самое с цветом фона.

Математически цвет пикселя такой:

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 голос
/ 27 сентября 2019

Проблема связана с основным способом, которым вещи нарисованы на холсте.Когда вы рисуете квадрат, края фигуры слегка «заштрихованы».Таким образом, когда вы рисуете белый прямоугольник над предыдущим красным квадратом, остатки красного перетекают в полупрозрачный край.

Если вы рисуете 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 с каждой стороны работает довольно хорошо, но это определенно будет функцией "логического" размера холста по сравнению с реальным размером экрана.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...