使用2d点图查找轮廓

时间:2017-01-14 20:14:05

标签: javascript canvas ecmascript-6 html5-canvas

我有一个x,y坐标数组,当绘制到画布时会创建一个形状但是当我缩放图像时,我有一个“连接点”版本,因为每个点之间都有空格。

我尝试在每组坐标之间绘制一条线,但由于它没有跟随“轮廓”,因此产生了锯齿形图。

我怎样才能创建大纲?

编辑: 我需要获取形状的“向量”并将其传递给Web应用程序中的线条绘制功能。我没有直接访问画布。

var canvas = document.getElementById("myCanvas");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext("2d");
var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);

// That's how you define the value of a pixel //
function drawPixel (x, y, r=0, g=0, b=0, a=255) {
    var index = (x + y * canvasWidth) * 4;

    canvasData.data[index + 0] = r;
    canvasData.data[index + 1] = g;
    canvasData.data[index + 2] = b;
    canvasData.data[index + 3] = a;
}

// That's how you update the canvas, so that your //
// modification are taken in consideration //
function updateCanvas() {
    ctx.putImageData(canvasData, 0, 0);
}

var i;
var coords = [74,16,76,16,78,16,80,16,82,16,84,16,86,16,88,16,90,16,92,16,70,18,72,18,74,18,76,18,78,18,80,18,82,18,84,18,86,18,88,18,90,18,92,18,94,18,68,20,70,20,72,20,74,20,76,20,78,20,80,20,82,20,84,20,86,20,88,20,90,20,92,20,94,20,96,20,98,20,66,22,68,22,70,22,72,22,74,22,76,22,88,22,90,22,92,22,94,22,96,22,98,22,64,24,66,24,68,24,70,24,72,24,74,24,92,24,94,24,96,24,98,24,100,24,64,26,66,26,68,26,70,26,72,26,94,26,96,26,98,26,100,26,64,28,66,28,68,28,70,28,94,28,96,28,98,28,100,28,102,28,62,30,64,30,66,30,68,30,96,30,98,30,100,30,102,30,62,32,64,32,66,32,68,32,96,32,98,32,100,32,102,32,62,34,64,34,66,34,68,34,96,34,98,34,100,34,102,34,62,36,64,36,66,36,68,36,96,36,98,36,100,36,102,36,62,38,64,38,66,38,68,38,98,38,100,38,102,38,62,40,64,40,66,40,68,40,96,40,98,40,100,40,102,40,62,42,64,42,66,42,68,42,96,42,98,42,100,42,102,42,62,44,64,44,66,44,68,44,96,44,98,44,100,44,102,44,64,46,66,46,68,46,70,46,94,46,96,46,98,46,100,46,102,46,64,48,66,48,68,48,70,48,72,48,94,48,96,48,98,48,100,48,64,50,66,50,68,50,70,50,72,50,74,50,92,50,94,50,96,50,98,50,100,50,66,52,68,52,70,52,72,52,74,52,76,52,88,52,90,52,92,52,94,52,96,52,98,52,68,54,70,54,72,54,74,54,76,54,78,54,80,54,82,54,84,54,86,54,88,54,90,54,92,54,94,54,96,54,98,54,70,56,72,56,74,56,76,56,78,56,80,56,82,56,84,56,86,56,88,56,90,56,92,56,94,56,74,58,76,58,78,58,80,58,82,58,84,58,86,58,88,58,90,58,92,58];

for (i=0; i<coords.length; i+=2) { 
	drawPixel(coords[i],coords[i+1]);
}


updateCanvas();
<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;"></canvas>

</body>
</html>

2 个答案:

答案 0 :(得分:2)

你很幸运,你的积分很顺序。但如果你有其他一些不是那样的点,那么你会遇到一些问题。

有些功能可以处理更多的随机点,但它们非常复杂,而且我没有找到任何可用的在线点。

对于你给出的点,我使用了一种在一组点周围找到凸包的方法。 (称为格雷厄姆的扫描)。我给你的分数,它返回外面的路径。我必须通过在x和y中进行排序来修改它以处理共线点,这样它才能处理高达10000像素的点云。

复杂的位是中心的洞。

为此,我创建了一组点,在您的形状上创建了一个点矩形(称为蒙版)。然后我移除了在坐标中的掩码中的所有点。我删除了在最后一步计算出的船体外的所有点。

现在面具只是船体内的点,而不是原始坐标的一部分。然后我围绕它创建一个船体并改变它的方向。

我现在有两个路径,一个用于外部,一个用于内部,如果一起渲染,则根据需要绘制形状。

  

警告。这仅适用于与您所呈现的相似的坐标。仅适用于凸形。你可以调整它,但它是一种蛮力方法,在目前的限制条件下不能很好地工作。

哦,当我添加演示时,我注意到内圈已经出局了。没有办法解决它而不是删除我会留下这个答案。它可能有所帮助。

看完之后我会删除它。

    // create and add a canvas
    var canvas = document.createElement("canvas");
    canvas.width = canvas.height = 256;
    var ctx = canvas.getContext("2d");
    document.body.appendChild(canvas);

    // clear it
    ctx.setTransform(1,0,0,1,0,0)
    ctx.clearRect(0,0,canvas.width,canvas.height)

    //==============================================================================
    // Many functions requiered for the example
    //==============================================================================

    // returns a convex hull around a set of points
    // using Graham Scan. See wiki for details
    // points in the form [[x,y],[x,y],...]
    // returns the convex hull as a list of referances to points
    function boundingHull(points) {
        var pLen, p, s, hull, i, p1, j, len;
        var isRight = (a, b, c) => 0 <= (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
        points.sort((a, b) => (a[0] + a[1] / 10000) - (b[0] + b[1] / 10000) );
        pLen = points.length - 1;
        p = points;
        s = [];
        hull = [];
        s.push(p[0],p[1]);
        for(j = 0; j < 2; j ++){
            for (i = 2; i <= pLen; i++) {
                p1 = j === 0 ? p[i] : p[pLen - i];
                len = s.length;
                while (len > 1 && !isRight(s[len - 2], s[len - 1], p1)) {
                    s.pop();
                    len = s.length;
                }
                s.push(p1);
            }
            hull.push(...s);
            s.length = 0;
            s.push(p[pLen],p[pLen-1]);
        }
        return hull;
    }

    // gets the center of a set of point
    function findCenter(verts){
        var x = 0;
        var y = 0;
        verts.forEach(v => { x += v[0]; y += v[1]; })
        x /= verts.length;
        y /= verts.length;
        return {x,y};
    }
    // gets the extent of points
    function findExtent(verts){
        var minx = Infinity;
        var maxx = -Infinity;
        var miny = Infinity;
        var maxy = -Infinity;
        var y = 0;
        verts.forEach(v => { 
            minx = v[0] < minx ? v[0] : minx;
            miny = v[1] < miny ? v[1] : miny;
            maxx = v[0] > maxx ? v[0] : maxx;
            maxy = v[1] > maxy ? v[1] : maxy;
        });
        return {
            top : miny,
            left : minx,
            width : maxx- minx,
            height : maxy - miny,
        }
    }

    // moves points 
    function translate(verts,by){
        verts.forEach(v => { v[0] += by.x; v[1] += by.y; })
    }

    // converts flat array of points into an array of points 
    // [x,y,x,y,x,y,... ] into [[x,y],[x,y],[x,y],... ]
    function formatVerts(verts) {
        var v = [];
        for (var i = 0; i < verts.length; i += 2) {
            v.push([verts[i], verts[i + 1]]);
        }
        return v;
    }
    // returns true if point x,y is inside hull
    function isInsideHull(x,y,hull){
        var s,ss;
        var vx,vy;
        var px,py;
        
        for(var i = 1; i < hull.length; i ++){
            vx = hull[i][0] - hull[i-1][0]
            vy = hull[i][1] - hull[i-1][1]
            px = x - hull[i-1][0]
            py = y - hull[i-1][1]
            s = Math.sign(vx * py - vy * px);
            s = s === 0 ? 1 : s;
            if(ss === undefined){
                ss = s;
            }else if(ss !== s){
                return false;
            }
        }
        return true;
    }
    // returns only points inside the hull
    function removePointsOutsideHull(points,hull){
        return points.filter(p => isInsideHull(p[0],p[1],hull))
    }
    // returns only points that are not in hull
    function removePointsFromPoints(hull,points){
        return points.filter(p => !hull.some(h => h[0] === p[0] && h[1] === p[1]));
    }
    // creates a set of points covering extent and spaced by xS,yS
    function createOrderedPoints(extent,xS,yS){
        var a = [];
        for(var y = extent.top; y <= extent.top + extent.height; y += yS ){
            for(var x = extent.left; x <= extent.left + extent.width; x += xS ){
                a.push([x,y]);
            }
        }
        return a;
    }
    // display scale
    var scale = 5;
    ctx.setTransform(scale,0,0,scale,canvas.width /2 ,canvas.height / 2)

    //==============================================================================
    // drawing functions
    //==============================================================================
    // draws an array of points
    function drawPoints(points, col = "black",size = 3){
        var s = ((size - 1) / 2) / scale;
        size = size / scale;
        ctx.strokeStyle = col;
        points.forEach(p => ctx.strokeRect(p[0]-s,p[1]-s,size,size))
    }
    // sets out a path from an array of points
    function definePath(path){
        path.forEach((p,i) =>{
            if(i === 0){
                ctx.moveTo(p[0],p[1]);
            }else{
                ctx.lineTo(p[0],p[1]);
            }
        });
    }
    // draws a path using stroke
    function drawPath(path, col = "black", lineWidth = 1){
        ctx.strokeStyle = col;
        ctx.lineWidth = lineWidth/scale;
        ctx.lineJoin = "round"
        ctx.beginPath();
        definePath(path)
        ctx.stroke();
    }
    // draws a set of paths as a shape
    function fillShape(paths, col = "black"){
        ctx.fillStyle = col;
        ctx.beginPath();
        paths.forEach(definePath);
        ctx.fill();
    }

    // define the points
    var coords = [74, 16, 76, 16, 78, 16, 80, 16, 82, 16, 84, 16, 86, 16, 88, 16, 90, 16, 92, 16, 70, 18, 72, 18, 74, 18, 76, 18, 78, 18, 80, 18, 82, 18, 84, 18, 86, 18, 88, 18, 90, 18, 92, 18, 94, 18, 68, 20, 70, 20, 72, 20, 74, 20, 76, 20, 78, 20, 80, 20, 82, 20, 84, 20, 86, 20, 88, 20, 90, 20, 92, 20, 94, 20, 96, 20, 98, 20, 66, 22, 68, 22, 70, 22, 72, 22, 74, 22, 76, 22, 88, 22, 90, 22, 92, 22, 94, 22, 96, 22, 98, 22, 64, 24, 66, 24, 68, 24, 70, 24, 72, 24, 74, 24, 92, 24, 94, 24, 96, 24, 98, 24, 100, 24, 64, 26, 66, 26, 68, 26, 70, 26, 72, 26, 94, 26, 96, 26, 98, 26, 100, 26, 64, 28, 66, 28, 68, 28, 70, 28, 94, 28, 96, 28, 98, 28, 100, 28, 102, 28, 62, 30, 64, 30, 66, 30, 68, 30, 96, 30, 98, 30, 100, 30, 102, 30, 62, 32, 64, 32, 66, 32, 68, 32, 96, 32, 98, 32, 100, 32, 102, 32, 62, 34, 64, 34, 66, 34, 68, 34, 96, 34, 98, 34, 100, 34, 102, 34, 62, 36, 64, 36, 66, 36, 68, 36, 96, 36, 98, 36, 100, 36, 102, 36, 62, 38, 64, 38, 66, 38, 68, 38, 98, 38, 100, 38, 102, 38, 62, 40, 64, 40, 66, 40, 68, 40, 96, 40, 98, 40, 100, 40, 102, 40, 62, 42, 64, 42, 66, 42, 68, 42, 96, 42, 98, 42, 100, 42, 102, 42, 62, 44, 64, 44, 66, 44, 68, 44, 96, 44, 98, 44, 100, 44, 102, 44, 64, 46, 66, 46, 68, 46, 70, 46, 94, 46, 96, 46, 98, 46, 100, 46, 102, 46, 64, 48, 66, 48, 68, 48, 70, 48, 72, 48, 94, 48, 96, 48, 98, 48, 100, 48, 64, 50, 66, 50, 68, 50, 70, 50, 72, 50, 74, 50, 92, 50, 94, 50, 96, 50, 98, 50, 100, 50, 66, 52, 68, 52, 70, 52, 72, 52, 74, 52, 76, 52, 88, 52, 90, 52, 92, 52, 94, 52, 96, 52, 98, 52, 68, 54, 70, 54, 72, 54, 74, 54, 76, 54, 78, 54, 80, 54, 82, 54, 84, 54, 86, 54, 88, 54, 90, 54, 92, 54, 94, 54, 96, 54, 98, 54, 70, 56, 72, 56, 74, 56, 76, 56, 78, 56, 80, 56, 82, 56, 84, 56, 86, 56, 88, 56, 90, 56, 92, 56, 94, 56, 74, 58, 76, 58, 78, 58, 80, 58, 82, 58, 84, 58, 86, 58, 88, 58, 90, 58, 92, 58];
    // convert to correct format
    coords = formatVerts(coords);
    // find the center
    var center = findCenter(coords);
    // find the extent
    var extent = findExtent(coords);
    // create masking points from extent
    var mask = createOrderedPoints(extent,2,2);
    // remove points that are the in the coords list
    mask = removePointsFromPoints(coords,mask);
    // center the mask
    translate(mask,{x : - center.x, y : -center.y})
    // center the coords
    translate(coords,{x : - center.x, y : -center.y})
    // create a convex hull
    var hull = boundingHull(coords);
    // draw the hull
    drawPath(hull);
    // and the poitns
    drawPoints(coords);
    // remove all points from the mask outside the hull
    mask = removePointsOutsideHull(mask,hull);
    // draw remaining points in the mask
    //drawPoints(mask,"red");
    // create a hull around the mask
    var hullInside = boundingHull(mask);
    // reverse its direction
    hullInside = hullInside.reverse();
    // draw the mask points 
    drawPath(hullInside,"red");
    // draw the shape
    fillShape([hull,hullInside],"rgba(0,255,0,0.5)");

答案 1 :(得分:1)

a brief search on MDN之后,正如我所看到的,您有两个可能或可能不会有效的常规选项:

  • 正确,弯曲的方式 - 使用路径并以某种方式计算基于点的呃需要的曲率(这可能很困难),

  • 简单的“放大像素”方式 - 不是绘制像素,而是绘制1像素的矩形,并在缩放图像时缩放它们。