Skip to content Skip to sidebar Skip to footer

FillRect() Not Overlapping Exactly When Float Numbers Are Used

The following code (jsFiddle) draws a red square at random points on a canvas taking care to erase the previous one (by filling a white square over it with ctx.fillRect():

Solution 1:

How do you render half a pixel red?

By taking the background color and interpolating with the foreground color based on the percentage, how much the foreground overlaps the background. When you now try to "delete" that, you do the same with the background-color.

So mathematically the color of the pixel is:

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

let's crunch some numbers: (a quick and dirty example)

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>

Solution 2:

The problem stems from the basic way that things are drawn on a canvas. When you draw a square, the edges of the shape are slightly "feathered". Thus when you draw the white box over the previous red box, the remnants of red bleed into the semi-transparent edge.

If you draw 10 white boxes instead of one, the problem goes away. Or if you make the white box probably 0.5 pixels larger, that would likely help.

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>

Looks like 0.75 bigger on each side works pretty well, but it's certain to be a function of canvas "logical" size vs. actual screen size.


Post a Comment for "FillRect() Not Overlapping Exactly When Float Numbers Are Used"