Используя линию, чтобы разделить холст на два новых холста - PullRequest
0 голосов
/ 02 июля 2018

Я хочу позволить пользователям разделить существующий холст на два холста в любом направлении, в котором они захотят.

Я знаю, как разрешить пользователю рисовать линию, и я также знаю, как копировать данные изображения одного холста на два новых, но как я могу скопировать только соответствующие данные цвета по обе стороны от нарисованной пользователем линия к соответствующему холсту?

Например, в следующей демонстрации я бы хотел, чтобы холст "обрезался" там, где находится белая линия:

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>

1 Ответ

0 голосов
/ 02 июля 2018

TL; DR демо .


Лучший способ сделать это - 1) вычислить «конечные точки» для линии на краю (или снаружи) границ холста, 2) создать два * полигона, используя конечные точки линия, сгенерированная на шаге 1, и четыре угла холста; 3) разделить данные изображения исходного холста на два новых холста на основе созданных нами многоугольников.

* На самом деле мы его создаем, но «вторая» - это оставшаяся часть исходного холста.


1) Рассчитать конечные точки

Вы можете использовать очень дешевый алгоритм для вычисления некоторых конечных точек с учетом начальной координаты, разности x и y (то есть наклона) и границ холста. Я использовал следующее:

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;
}

, где

let xDiff = firstPoint.x - secondPoint.x,
    yDiff = firstPoint.y - secondPoint.y;

2) Создать два полигона

Для создания многоугольников я использую Пересечение линий Javascript Пола Бурка *1027*:

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];
}

Наряду с моей собственной логикой:

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) Разделите исходные данные изображения холста

Теперь, когда у нас есть наш полигон, все, что осталось, - это поместить эти данные в новые холсты. Самый простой способ сделать это - использовать canvas 'ctx.drawImage и 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();

Все это вместе дает нам это демо !

...