Reputation: 11756
For my application I'm trying to shift everything to the right of an arbitrary vertical seam-line one pixel to the left. Currently I am using an O(n^2) for loop and manually editing the pixel data, but unfortunately this is prohibitively slow.
I had the following idea of drawing the original image to the canvas, creating a Path2D
of the seam, creating a clipping region of everything to the right of the seam, and then drawing that region one pixel to the left.
Unfortunately, it seems as if this clipping is not pixel perfect, and instead introduces anti-aliasing into the image.
Here's a code example demonstrating the issue:
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
ctx.beginPath();
ctx.strokeStyle = "#00000000";
ctx.moveTo(50, 0);
for (let i = 1; i < 200; i++) {
ctx.lineTo(50 + getRandomInt(-1, 2), i);
}
ctx.lineTo(500, 200);
ctx.lineTo(500, 0);
ctx.lineTo(50, 0);
ctx.stroke();
ctx.clip();
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 100, 100);
<canvas id="canvas" width="300" height="300">
As you can see, the seam introduces antialiasing into the cut. Is there a way to create a clipping region that is pixel perfect, and not antialiased?
I also tried adding image-rendering: pixelated;
to the stylesheet, and while it seems to help a little, there is still anti-aliasing being done.
Upvotes: 1
Views: 223
Reputation: 136707
The problem is that you are drawing oblique lines between each stitch.
Making things a bit more dramatic, here is what you were doing.
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
ctx.beginPath();
ctx.strokeStyle = "#00000000";
ctx.moveTo(50, 0);
for (let i = 1; i < 200; i+= 10) {
ctx.lineTo(50 + getRandomInt(-5, 10), i);
}
ctx.lineTo(500, 200);
ctx.lineTo(500, 0);
ctx.lineTo(50, 0);
ctx.stroke();
ctx.clip();
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 100, 100);
<canvas id="canvas" width="300" height="300">
When rendering these oblique lines the browser will use antialiasing to smoothen the lines because that's usually what one wants.
But in your case you want pixel perfect, so the solution is to instead walk around the pixel boundaries in a step shape. This can be achieved by drawing a vertical line from the last y coord to the next one, and then draw the horizontal line.
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// for high-res monitors
{
const dPR = window.devicePixelRatio;
canvas.width = canvas.height *= dPR
ctx.scale( dPR, dPR );
}
ctx.beginPath();
ctx.strokeStyle = "#00000000";
ctx.moveTo(50, 0);
for (let i = 1; i < 200; i++) {
let rand = getRandomInt( -1, 2 );
ctx.lineTo( 50 + rand, i - 1 ); // from last y move horizontally
ctx.lineTo( 50 + rand, i ); // move vertically
}
ctx.lineTo(500, 200);
ctx.lineTo(500, 0);
ctx.lineTo(50, 0);
ctx.clip();
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 100, 100);
canvas { width: 300px; height: 300px }
<canvas id="canvas" width="300" height="300">
Once again in a more dramatic way:
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
ctx.beginPath();
ctx.strokeStyle = "#00000000";
ctx.moveTo(50, 0);
for (let i = 1; i < 200; i+= 10) {
const rand = getRandomInt(-5, 10);
ctx.lineTo(50 + rand, i-10);
ctx.lineTo(50 + rand, i);
}
ctx.lineTo(500, 200);
ctx.lineTo(500, 0);
ctx.lineTo(50, 0);
ctx.stroke();
ctx.clip();
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 100, 100);
<canvas id="canvas" width="300" height="300">
Upvotes: 3