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()<
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;
}
more or less ;)
Color after rendering "half a pixel" of the foreground over the background:
Color after trying to erase that by rendering "half a pixel" of the background-color over that: