简体   繁体   中英

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 copy the image data of one canvas onto two new ones, but how can I copy only the relevant color data on either side of the user-drawn line to its respective canvas?

For example, in the following demo I'd like the canvas to be "cut" where the white line is:

 const canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d"); const red = "rgb(104, 0, 0)", lb = "rgb(126, 139, 185)", db = "rgb(20, 64, 87)"; var width, height, centerX, centerY, smallerDimen; var canvasData, inCoords; function sizeCanvas() { width = canvas.width = window.innerWidth; height = canvas.height = window.innerHeight; centerX = width / 2; centerY = height / 2; smallerDimen = Math.min(width, height); } function drawNormalState() { // Color the bg ctx.fillStyle = db; ctx.fillRect(0, 0, width, height); // Color the circle ctx.arc(centerX, centerY, smallerDimen / 4, 0, Math.PI * 2, true); ctx.fillStyle = red; ctx.fill(); ctx.lineWidth = 3; ctx.strokeStyle = lb; ctx.stroke(); // Color the triangle ctx.beginPath(); ctx.moveTo(centerX + smallerDimen / 17, centerY - smallerDimen / 10); ctx.lineTo(centerX + smallerDimen / 17, centerY + smallerDimen / 10); ctx.lineTo(centerX - smallerDimen / 9, centerY); ctx.fillStyle = lb; ctx.fill(); ctx.closePath(); screenshot(); ctx.beginPath(); ctx.strokeStyle = "rgb(255, 255, 255)"; ctx.moveTo(width - 20, 0); ctx.lineTo(20, height); ctx.stroke(); ctx.closePath(); } function screenshot() { canvasData = ctx.getImageData(0, 0, width, height).data; } function init() { sizeCanvas(); drawNormalState(); } init(); 
 body { margin: 0; } 
 <canvas></canvas> 

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 (ie slope), and the bounds for the canvas. I used the following:

function getEndPoints(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's Javascript line intersection :

function intersect(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 0
    if((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) {
        return false;
    }

    let denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));

    // Lines are parallel
    if(denominator === 0) {
        return false;;
    }

    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 segments
    if(ua < 0 || ua > 1 || ub < 0 || ub > 1) {
        return false;
    }

    // Return a object with the x and y coordinates of the intersection
    let 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 bound
let topIntersect = intersect(origin, xBound, points.firstPoint, points.secondPoint);
if(topIntersect) {
    polygon.push(topIntersect);
}
if(!topIntersect) {
    polygon.push(xBound);
}

// Check intersect with right
let rightIntersect = intersect(xBound, xyBound, points.firstPoint, points.secondPoint);
if(rightIntersect) {
    polygon.push(rightIntersect);
}
if((!topIntersect && !rightIntersect)
|| (topIntersect && rightIntersect)) {
    polygon.push(xyBound);
}


// Check intersect with bottom
let bottomIntersect = intersect(xyBound, yBound, points.firstPoint, points.secondPoint);
if(bottomIntersect) {
    polygon.push(bottomIntersect);
}
if((topIntersect && bottomIntersect)
|| (topIntersect && rightIntersect)) {
    polygon.push(yBound);
}

// Check intersect with left
let 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 canvas
let 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 !

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM