Using A Line To Divide A Canvas Into Two New Canvases
Solution 1:
TL;DR the demo.
The best way I've found to do this is to 1) calculate "end points" for the line at the edge of (or outside) the canvas' bounds, 2) create two* polygons using the end points of the line generated in step 1 and the canvas' four corners, and 3) divide up the original canvas' image data into two new canvases based on the polygons we create.
* We actually create one, but the "second" is the remaining part of the original canvas.
1) Calculate the end points
You can use a very cheap algorithm to calculate some end points given a start coordinate, x and y difference (i.e. slope), and the bounds for the canvas. I used the following:
functiongetEndPoints(startX, startY, xDiff, yDiff, maxX, maxY) {
let currX = startX,
currY = startY;
while(currX > 0 && currY > 0 && currX < maxX && currY < maxY) {
currX += xDiff;
currY += yDiff;
}
let points = {
firstPoint: [currX, currY]
};
currX = startX;
currY = startY;
while(currX > 0 && currY > 0 && currX < maxX && currY < maxY) {
currX -= xDiff;
currY -= yDiff;
}
points.secondPoint = [currX, currY];
return points;
}
where
let xDiff = firstPoint.x - secondPoint.x,
yDiff = firstPoint.y - secondPoint.y;
2) Create two polygons
To create the polygons, I make use of Paul Bourke'sJavascript line intersection:
functionintersect(point1, point2, point3, point4) {
let x1 = point1[0],
y1 = point1[1],
x2 = point2[0],
y2 = point2[1],
x3 = point3[0],
y3 = point3[1],
x4 = point4[0],
y4 = point4[1];
// Check if none of the lines are of length 0if((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) {
returnfalse;
}
let denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
// Lines are parallelif(denominator === 0) {
returnfalse;;
}
let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
// is the intersection along the segmentsif(ua < 0 || ua > 1 || ub < 0 || ub > 1) {
returnfalse;
}
// Return a object with the x and y coordinates of the intersectionlet x = x1 + ua * (x2 - x1);
let y = y1 + ua * (y2 - y1);
return [x, y];
}
Along with some of my own logic:
let origin = [0, 0],
xBound = [width, 0],
xyBound = [width, height],
yBound = [0, height];
let polygon = [origin];
// Work clockwise from 0,0, adding points to our polygon as appropriate// Check intersect with top boundlet topIntersect = intersect(origin, xBound, points.firstPoint, points.secondPoint);
if(topIntersect) {
polygon.push(topIntersect);
}
if(!topIntersect) {
polygon.push(xBound);
}
// Check intersect with rightlet rightIntersect = intersect(xBound, xyBound, points.firstPoint, points.secondPoint);
if(rightIntersect) {
polygon.push(rightIntersect);
}
if((!topIntersect && !rightIntersect)
|| (topIntersect && rightIntersect)) {
polygon.push(xyBound);
}
// Check intersect with bottomlet bottomIntersect = intersect(xyBound, yBound, points.firstPoint, points.secondPoint);
if(bottomIntersect) {
polygon.push(bottomIntersect);
}
if((topIntersect && bottomIntersect)
|| (topIntersect && rightIntersect)) {
polygon.push(yBound);
}
// Check intersect with leftlet leftIntersect = intersect(yBound, origin, points.firstPoint, points.secondPoint);
if(leftIntersect) {
polygon.push(leftIntersect);
}
3) Divide up the original canvas' image data
Now that we have our polygon, all that's left is putting this data into new canvases. The easiest way to do this is to use canvas' ctx.drawImage
and ctx.globalCompositeOperation
.
// Use or create 2 new canvases with the split original canvaslet newCanvas1 = document.querySelector("#newCanvas1");
if(newCanvas1 == null) {
newCanvas1 = document.createElement("canvas");
newCanvas1.id = "newCanvas1";
newCanvas1.width = width;
newCanvas1.height = height;
document.body.appendChild(newCanvas1);
}
let newCtx1 = newCanvas1.getContext("2d");
newCtx1.globalCompositeOperation = 'source-over';
newCtx1.drawImage(canvas, 0, 0);
newCtx1.globalCompositeOperation = 'destination-in';
newCtx1.beginPath();
newCtx1.moveTo(polygon[0][0], polygon[0][1]);
for(let item = 1; item < polygon.length; item++) {
newCtx1.lineTo(polygon[item][0], polygon[item][1]);
}
newCtx1.closePath();
newCtx1.fill();
let newCanvas2 = document.querySelector("#newCanvas2");
if(newCanvas2 == null) {
newCanvas2 = document.createElement("canvas");
newCanvas2.id = "newCanvas2";
newCanvas2.width = width;
newCanvas2.height = height;
document.body.appendChild(newCanvas2);
}
let newCtx2 = newCanvas2.getContext("2d");
newCtx2.globalCompositeOperation = 'source-over';
newCtx2.drawImage(canvas, 0, 0);
newCtx2.globalCompositeOperation = 'destination-out';
newCtx2.beginPath();
newCtx2.moveTo(polygon[0][0], polygon[0][1]);
for(let item = 1; item < polygon.length; item++) {
newCtx2.lineTo(polygon[item][0], polygon[item][1]);
}
newCtx2.closePath();
newCtx2.fill();
All of that put together gives us this demo!
Post a Comment for "Using A Line To Divide A Canvas Into Two New Canvases"