Skip to content Skip to sidebar Skip to footer

Using A Line To Divide A Canvas Into Two New Canvases

I'm looking to allow users to slice an existing canvas into two canvases in whatever direction they would like. I know how to allow the user to draw a line and I also know how to

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"