在HTML5的画布上绘制扭曲图像

10

是否可能并且如果可以的话,如何绘制四个角被扭曲成非矩形形状的图像。例如,如果你想要绘制一个看起来像在3D空间中旋转过的图像。

6个回答

17

我基于Thatcher Ulrich的代码(http://tulrich.com/geekstuff/canvas/perspective.html)创建了一个简单的四点变换。这个版本只提取了小部分三角形代码,并处理几何细分以进行正确的变换。享受吧!

canvas中的4点变换

http://jsfiddle.net/mrbendel/6rbtde5t/1/

var controls = [];
var canvas;
var context;
var image;
var triangles = [];
var dirtyTriangles = true;

var rand = function(s,e) {
    return Math.random() * (e-s) + s;
}

// dom ready
$(document).ready(function() {
    image = new Image();
    $(image).load(function() {
        setInterval(draw, 1000 / 60);
    });
    $(image).attr('src', 'http://media.giphy.com/media/NWb6sWXQQTqwg/giphy.gif');

    canvas = document.createElement('canvas');
    $(canvas).attr('width', 500);
    $(canvas).attr('height', 500);
    $('body').append(canvas);

    context = canvas.getContext('2d');

    //
    for (var i = 0; i < 4; ++i) {
        var control = document.createElement('div');
        $(control).addClass('node');
        $('body').append(control);
        controls.push(control);
    }

    $(controls[0]).css('left', rand(25, 225));
    $(controls[0]).css('top', rand(25, 225));

    $(controls[1]).css('left', rand(250, 475));
    $(controls[1]).css('top', rand(25, 225));

    $(controls[2]).css('left', rand(250, 475));
    $(controls[2]).css('top', rand(250, 475));

    $(controls[3]).css('left', rand(25, 225));
    $(controls[3]).css('top', rand(250, 475));

    $('body').mousedown(function(e) {
        if ($(e.target).hasClass('node')) {
            var node = e.target;

            $('body').mousemove(function(e) {
                var x = e.pageX;
                var y = e.pageY;
                $(node).css('left', x);
                $(node).css('top', y);
                dirtyTriangles = true;
            });

            $('body').mouseup(function(e) {
                $('body').off( "mousemove" );
                $('body').off( "mouseup" );
            });
        }
    });
});

var draw = function() {
    context.clearRect(0,0,500,500);

    var render = function(wireframe, image, tri) {

        if (wireframe) {
            context.strokeStyle = "black";
            context.beginPath();
            context.moveTo(tri.p0.x, tri.p0.y);
            context.lineTo(tri.p1.x, tri.p1.y);
            context.lineTo(tri.p2.x, tri.p2.y);
            context.lineTo(tri.p0.x, tri.p0.y);
            context.stroke();
            context.closePath();
        }

        if (image) {
            drawTriangle(context, image,
                         tri.p0.x, tri.p0.y,
                         tri.p1.x, tri.p1.y,
                         tri.p2.x, tri.p2.y,
                         tri.t0.u, tri.t0.v,
                         tri.t1.u, tri.t1.v,
                         tri.t2.u, tri.t2.v);
        }
    }

    if (dirtyTriangles) {
        dirtyTriangles = false;
        calculateGeometry();
    }

    for (triangle of triangles) {
        render(true, image, triangle);
    }
}

var calculateGeometry = function() {
    // clear triangles out
    triangles = [];

    // generate subdivision
    var subs = 7; // vertical subdivisions
    var divs = 7; // horizontal subdivisions

    var p1 = new Point(parseInt($(controls[0]).css('left')) + 6, parseInt($(controls[0]).css('top')) + 6);
    var p2 = new Point(parseInt($(controls[1]).css('left')) + 6, parseInt($(controls[1]).css('top')) + 6);
    var p3 = new Point(parseInt($(controls[2]).css('left')) + 6, parseInt($(controls[2]).css('top')) + 6);
    var p4 = new Point(parseInt($(controls[3]).css('left')) + 6, parseInt($(controls[3]).css('top')) + 6);

    var dx1 = p4.x - p1.x;
    var dy1 = p4.y - p1.y;
    var dx2 = p3.x - p2.x;
    var dy2 = p3.y - p2.y;

    var imgW = image.naturalWidth;
    var imgH = image.naturalHeight;

    for (var sub = 0; sub < subs; ++sub) {
        var curRow = sub / subs;
        var nextRow = (sub + 1) / subs;

        var curRowX1 = p1.x + dx1 * curRow;
        var curRowY1 = p1.y + dy1 * curRow;

        var curRowX2 = p2.x + dx2 * curRow;
        var curRowY2 = p2.y + dy2 * curRow;

        var nextRowX1 = p1.x + dx1 * nextRow;
        var nextRowY1 = p1.y + dy1 * nextRow;

        var nextRowX2 = p2.x + dx2 * nextRow;
        var nextRowY2 = p2.y + dy2 * nextRow;

        for (var div = 0; div < divs; ++div) {
            var curCol = div / divs;
            var nextCol = (div + 1) / divs;

            var dCurX = curRowX2 - curRowX1;
            var dCurY = curRowY2 - curRowY1;
            var dNextX = nextRowX2 - nextRowX1;
            var dNextY = nextRowY2 - nextRowY1;

            var p1x = curRowX1 + dCurX * curCol;
            var p1y = curRowY1 + dCurY * curCol;

            var p2x = curRowX1 + (curRowX2 - curRowX1) * nextCol;
            var p2y = curRowY1 + (curRowY2 - curRowY1) * nextCol;

            var p3x = nextRowX1 + dNextX * nextCol;
            var p3y = nextRowY1 + dNextY * nextCol;

            var p4x = nextRowX1 + dNextX * curCol;
            var p4y = nextRowY1 + dNextY * curCol;

            var u1 = curCol * imgW;
            var u2 = nextCol * imgW;
            var v1 = curRow * imgH;
            var v2 = nextRow * imgH;

            var triangle1 = new Triangle(
                new Point(p1x, p1y),
                new Point(p3x, p3y),
                new Point(p4x, p4y),
                new TextCoord(u1, v1),
                new TextCoord(u2, v2),
                new TextCoord(u1, v2)
            );

            var triangle2 = new Triangle(
                new Point(p1x, p1y),
                new Point(p2x, p2y),
                new Point(p3x, p3y),
                new TextCoord(u1, v1),
                new TextCoord(u2, v1),
                new TextCoord(u2, v2)
            );

            triangles.push(triangle1);
            triangles.push(triangle2);
        }
    }
}

// from http://tulrich.com/geekstuff/canvas/jsgl.js
var drawTriangle = function(ctx, im, x0, y0, x1, y1, x2, y2,
    sx0, sy0, sx1, sy1, sx2, sy2) {
    ctx.save();

    // Clip the output to the on-screen triangle boundaries.
    ctx.beginPath();
    ctx.moveTo(x0, y0);
    ctx.lineTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.closePath();
    //ctx.stroke();//xxxxxxx for wireframe
    ctx.clip();

    /*
    ctx.transform(m11, m12, m21, m22, dx, dy) sets the context transform matrix.

    The context matrix is:

    [ m11 m21 dx ]
    [ m12 m22 dy ]
    [  0   0   1 ]

    Coords are column vectors with a 1 in the z coord, so the transform is:
    x_out = m11 * x + m21 * y + dx;
    y_out = m12 * x + m22 * y + dy;

    From Maxima, these are the transform values that map the source
    coords to the dest coords:

    sy0 (x2 - x1) - sy1 x2 + sy2 x1 + (sy1 - sy2) x0
    [m11 = - -----------------------------------------------------,
    sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

    sy1 y2 + sy0 (y1 - y2) - sy2 y1 + (sy2 - sy1) y0
    m12 = -----------------------------------------------------,
    sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

    sx0 (x2 - x1) - sx1 x2 + sx2 x1 + (sx1 - sx2) x0
    m21 = -----------------------------------------------------,
    sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

    sx1 y2 + sx0 (y1 - y2) - sx2 y1 + (sx2 - sx1) y0
    m22 = - -----------------------------------------------------,
    sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

    sx0 (sy2 x1 - sy1 x2) + sy0 (sx1 x2 - sx2 x1) + (sx2 sy1 - sx1 sy2) x0
    dx = ----------------------------------------------------------------------,
    sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

    sx0 (sy2 y1 - sy1 y2) + sy0 (sx1 y2 - sx2 y1) + (sx2 sy1 - sx1 sy2) y0
    dy = ----------------------------------------------------------------------]
    sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0
  */

    // TODO: eliminate common subexpressions.
    var denom = sx0 * (sy2 - sy1) - sx1 * sy2 + sx2 * sy1 + (sx1 - sx2) * sy0;
    if (denom == 0) {
        return;
    }
    var m11 = -(sy0 * (x2 - x1) - sy1 * x2 + sy2 * x1 + (sy1 - sy2) * x0) / denom;
    var m12 = (sy1 * y2 + sy0 * (y1 - y2) - sy2 * y1 + (sy2 - sy1) * y0) / denom;
    var m21 = (sx0 * (x2 - x1) - sx1 * x2 + sx2 * x1 + (sx1 - sx2) * x0) / denom;
    var m22 = -(sx1 * y2 + sx0 * (y1 - y2) - sx2 * y1 + (sx2 - sx1) * y0) / denom;
    var dx = (sx0 * (sy2 * x1 - sy1 * x2) + sy0 * (sx1 * x2 - sx2 * x1) + (sx2 * sy1 - sx1 * sy2) * x0) / denom;
    var dy = (sx0 * (sy2 * y1 - sy1 * y2) + sy0 * (sx1 * y2 - sx2 * y1) + (sx2 * sy1 - sx1 * sy2) * y0) / denom;

    ctx.transform(m11, m12, m21, m22, dx, dy);

    // Draw the whole image.  Transform and clip will map it onto the
    // correct output triangle.
    //
    // TODO: figure out if drawImage goes faster if we specify the rectangle that
    // bounds the source coords.
    ctx.drawImage(im, 0, 0);
    ctx.restore();
};

// point class

var Point = function(x,y) {
    this.x = x?x:0;
    this.y = y?y:0;
}

var p = Point.prototype;

p.length = function(point) {
    point = point?point:new Point();
    var xs =0, ys =0;
    xs = point.x - this.x;
    xs = xs * xs;

    ys = point.y - this.y;
    ys = ys * ys;
    return Math.sqrt( xs + ys );
}

var TextCoord = function(u,v) {
    this.u = u?u:0;
    this.v = v?v:0;
}

var Triangle = function(p0, p1, p2, t0, t1, t2) {
    this.p0 = p0;
    this.p1 = p1;
    this.p2 = p2;

    this.t0 = t0;
    this.t1 = t1;
    this.t2 = t2;
}

2
这已经非常棒了。然而当关闭线框时,可以看到三角形之间有小间隙,就像轮廓只是变成透明而不是黑色。这方面有什么可以解决的吗? - Constantin Groß
1
我认为这些瑕疵只是精度误差。你可以在三角剖分上添加小值,使其略微重叠。此外 - 你可以使用较少的三角形,但是三维效果就不那么精确了。 - Andy Poes
谢谢您的回复。我尝试了重叠三角形,但我的数学理解似乎太有限了,不知道需要适应什么才能实现它...;-) 不过我会再试一次的。 - Constantin Groß
你在让三角形对齐方面有什么成功经验吗? - erbridge
@AndyPoes,你能否更新一下fiddle的链接?它已经失效了。 - faniva
@Connum,你应该在drawTriangle方法中注释或删除起始行。 - A_HREF

3

setTransform允许您指定一个2 x 3矩阵。要进行透视变换(而不是等角或正交),您需要一个4 x 4矩阵,不是吗? - Mr Bell
哦,你说得对。抱歉。虽然有一些不太正规的尝试去做这件事。在这里看看:http://tulrich.com/geekstuff/canvas/perspective.html - Simon Sarris

1
许多评论者问及如何隐藏三角形,只需这样做它们就会消失(供参考:@erbridge,@Constantin Groß):
以下所有内容的原创归功于@AndyPoes,因为这是他的建议 - 我只是添加了他建议的完成版本。
更改此行:
render(true, image, triangle);

转化为以下格式:

render(false, image, triangle);

...黑色三角形将会消失,但是你会看到三角形之间有空隙,所以现在做这个:

更改为:

var triangle1 = new Triangle(
            new Point(p1x, p1y),
            new Point(p3x, p3y),
            new Point(p4x, p4y),
            new TextCoord(u1, v1),
            new TextCoord(u2, v2),
            new TextCoord(u1, v2)
        );

        var triangle2 = new Triangle(
            new Point(p1x, p1y),
            new Point(p2x, p2y),
            new Point(p3x, p3y),
            new TextCoord(u1, v1),
            new TextCoord(u2, v1),
            new TextCoord(u2, v2)
        );

改为这样:
            var triangle1 = new Triangle(
            new Point(p1x-1, p1y),
            new Point(p3x+2, p3y+1),
            new Point(p4x-1, p4y+1),
            new TextCoord(u1, v1),
            new TextCoord(u2, v2),
            new TextCoord(u1, v2)
        );

        var triangle2 = new Triangle(
            new Point(p1x-2, p1y),
            new Point(p2x+1, p2y),
            new Point(p3x+1, p3y+1),
            new TextCoord(u1, v1),
            new TextCoord(u2, v1),
            new TextCoord(u2, v2)
        );

......并且空白将被填补。

新的代码片段 = 屋顶上的小提琴手

完整代码在此处:

var controls = [];
var canvas;
var context;
var image;
var triangles = [];
var dirtyTriangles = false;

var rand = function(s,e) {
    return Math.random() * (e-s) + s;
}

// dom ready
$(document).ready(function() {
image = new Image();
$(image).load(function() {
    setInterval(draw, 1000 / 60);
});
$(image).attr('src', 'https://images.unsplash.com/photo-1500964757637-c85e8a162699?ixlib=rb-1.2.1&q=80&fm=jpg');

canvas = document.createElement('canvas');
$(canvas).attr('width', 500);
$(canvas).attr('height', 500);
$('body').append(canvas);

context = canvas.getContext('2d');

//
for (var i = 0; i < 4; ++i) {
    var control = document.createElement('div');
    $(control).addClass('node');
    $('body').append(control);
    controls.push(control);
}

$(controls[0]).css('left', rand(25, 225));
$(controls[0]).css('top', rand(25, 225));

$(controls[1]).css('left', rand(250, 475));
$(controls[1]).css('top', rand(25, 225));

$(controls[2]).css('left', rand(250, 475));
$(controls[2]).css('top', rand(250, 475));

$(controls[3]).css('left', rand(25, 225));
$(controls[3]).css('top', rand(250, 475));

$('body').mousedown(function(e) {
    if ($(e.target).hasClass('node')) {
        var node = e.target;

        $('body').mousemove(function(e) {
            var x = e.pageX;
            var y = e.pageY;
            $(node).css('left', x);
            $(node).css('top', y);
            dirtyTriangles = true;
        });

        $('body').mouseup(function(e) {
            $('body').off( "mousemove" );
            $('body').off( "mouseup" );
        });
    }
});
});

var draw = function() {
context.clearRect(0,0,500,500);

var render = function(wireframe, image, tri) {
    
    if (wireframe) {
        context.strokeStyle = "black";
        context.beginPath();
        context.moveTo(tri.p0.x, tri.p0.y);
        context.lineTo(tri.p1.x, tri.p1.y);
        context.lineTo(tri.p2.x, tri.p2.y);
        context.lineTo(tri.p0.x, tri.p0.y);
        context.stroke();
        context.closePath();
    }

    if (image) {
        drawTriangle(context, image,
                     tri.p0.x, tri.p0.y,
                     tri.p1.x, tri.p1.y,
                     tri.p2.x, tri.p2.y,
                     tri.t0.u, tri.t0.v,
                     tri.t1.u, tri.t1.v,
                     tri.t2.u, tri.t2.v);
    }
}

if (dirtyTriangles) {
    dirtyTriangles = false;
    calculateGeometry();
}

for (triangle of triangles) {
    render(true, image, triangle);
}
}

var calculateGeometry = function() {
// clear triangles out
triangles = [];

// generate subdivision
var subs = 7; // vertical subdivisions
var divs = 7; // horizontal subdivisions

var p1 = new Point(parseInt($(controls[0]).css('left')) + 6, parseInt($(controls[0]).css('top')) + 6);
var p2 = new Point(parseInt($(controls[1]).css('left')) + 6, parseInt($(controls[1]).css('top')) + 6);
var p3 = new Point(parseInt($(controls[2]).css('left')) + 6, parseInt($(controls[2]).css('top')) + 6);
var p4 = new Point(parseInt($(controls[3]).css('left')) + 6, parseInt($(controls[3]).css('top')) + 6);

var dx1 = p4.x - p1.x;
var dy1 = p4.y - p1.y;
var dx2 = p3.x - p2.x;
var dy2 = p3.y - p2.y;

var imgW = image.naturalWidth;
var imgH = image.naturalHeight;

for (var sub = 0; sub < subs; ++sub) {
    var curRow = sub / subs;
    var nextRow = (sub + 1) / subs;

    var curRowX1 = p1.x + dx1 * curRow;
    var curRowY1 = p1.y + dy1 * curRow;
    
    var curRowX2 = p2.x + dx2 * curRow;
    var curRowY2 = p2.y + dy2 * curRow;

    var nextRowX1 = p1.x + dx1 * nextRow;
    var nextRowY1 = p1.y + dy1 * nextRow;
    
    var nextRowX2 = p2.x + dx2 * nextRow;
    var nextRowY2 = p2.y + dy2 * nextRow;

    for (var div = 0; div < divs; ++div) {
        var curCol = div / divs;
        var nextCol = (div + 1) / divs;

        var dCurX = curRowX2 - curRowX1;
        var dCurY = curRowY2 - curRowY1;
        var dNextX = nextRowX2 - nextRowX1;
        var dNextY = nextRowY2 - nextRowY1;

        var p1x = curRowX1 + dCurX * curCol;
        var p1y = curRowY1 + dCurY * curCol;

        var p2x = curRowX1 + (curRowX2 - curRowX1) * nextCol;
        var p2y = curRowY1 + (curRowY2 - curRowY1) * nextCol;

        var p3x = nextRowX1 + dNextX * nextCol;
        var p3y = nextRowY1 + dNextY * nextCol;

        var p4x = nextRowX1 + dNextX * curCol;
        var p4y = nextRowY1 + dNextY * curCol;

        var u1 = curCol * imgW;
        var u2 = nextCol * imgW;
        var v1 = curRow * imgH;
        var v2 = nextRow * imgH;

        var triangle1 = new Triangle(
            new Point(p1x-1, p1y),
            new Point(p3x+2, p3y+1),
            new Point(p4x-1, p4y+1),
            new TextCoord(u1, v1),
            new TextCoord(u2, v2),
            new TextCoord(u1, v2)
        );

        var triangle2 = new Triangle(
            new Point(p1x-2, p1y),
            new Point(p2x+1, p2y),
            new Point(p3x+1, p3y+1),
            new TextCoord(u1, v1),
            new TextCoord(u2, v1),
            new TextCoord(u2, v2)
        );

        triangles.push(triangle1);
        triangles.push(triangle2);
    }
}
}

// from http://tulrich.com/geekstuff/canvas/jsgl.js
var drawTriangle = function(ctx, im, x0, y0, x1, y1, x2, y2,
sx0, sy0, sx1, sy1, sx2, sy2) {
ctx.save();

// Clip the output to the on-screen triangle boundaries.
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
//ctx.stroke();//xxxxxxx for wireframe
ctx.clip();

/*
ctx.transform(m11, m12, m21, m22, dx, dy) sets the context transform matrix.

The context matrix is:

[ m11 m21 dx ]
[ m12 m22 dy ]
[  0   0   1 ]

Coords are column vectors with a 1 in the z coord, so the transform is:
x_out = m11 * x + m21 * y + dx;
y_out = m12 * x + m22 * y + dy;

From Maxima, these are the transform values that map the source
coords to the dest coords:

sy0 (x2 - x1) - sy1 x2 + sy2 x1 + (sy1 - sy2) x0
[m11 = - -----------------------------------------------------,
sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

sy1 y2 + sy0 (y1 - y2) - sy2 y1 + (sy2 - sy1) y0
m12 = -----------------------------------------------------,
sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

sx0 (x2 - x1) - sx1 x2 + sx2 x1 + (sx1 - sx2) x0
m21 = -----------------------------------------------------,
sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

sx1 y2 + sx0 (y1 - y2) - sx2 y1 + (sx2 - sx1) y0
m22 = - -----------------------------------------------------,
sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

sx0 (sy2 x1 - sy1 x2) + sy0 (sx1 x2 - sx2 x1) + (sx2 sy1 - sx1 sy2) x0
dx = ----------------------------------------------------------------------,
sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0

sx0 (sy2 y1 - sy1 y2) + sy0 (sx1 y2 - sx2 y1) + (sx2 sy1 - sx1 sy2) y0
dy = ----------------------------------------------------------------------]
sx0 (sy2 - sy1) - sx1 sy2 + sx2 sy1 + (sx1 - sx2) sy0
  */

// TODO: eliminate common subexpressions.
var denom = sx0 * (sy2 - sy1) - sx1 * sy2 + sx2 * sy1 + (sx1 - sx2) * sy0;
if (denom == 0) {
    return;
}
var m11 = -(sy0 * (x2 - x1) - sy1 * x2 + sy2 * x1 + (sy1 - sy2) * x0) / denom;
var m12 = (sy1 * y2 + sy0 * (y1 - y2) - sy2 * y1 + (sy2 - sy1) * y0) / denom;
var m21 = (sx0 * (x2 - x1) - sx1 * x2 + sx2 * x1 + (sx1 - sx2) * x0) / denom;
var m22 = -(sx1 * y2 + sx0 * (y1 - y2) - sx2 * y1 + (sx2 - sx1) * y0) / denom;
var dx = (sx0 * (sy2 * x1 - sy1 * x2) + sy0 * (sx1 * x2 - sx2 * x1) + (sx2 * sy1 - sx1 * sy2) * x0) / denom;
var dy = (sx0 * (sy2 * y1 - sy1 * y2) + sy0 * (sx1 * y2 - sx2 * y1) + (sx2 * sy1 - sx1 * sy2) * y0) / denom;

ctx.transform(m11, m12, m21, m22, dx, dy);

// Draw the whole image.  Transform and clip will map it onto the
// correct output triangle.
//
// TODO: figure out if drawImage goes faster if we specify the rectangle that
// bounds the source coords.
ctx.drawImage(im, 0, 0);
ctx.restore();
};

// point class

var Point = function(x,y) {
this.x = x?x:0;
this.y = y?y:0;
}

var p = Point.prototype;

p.length = function(point) {
    point = point?point:new Point();
    var xs =0, ys =0;
    xs = point.x - this.x;
    xs = xs * xs;

    ys = point.y - this.y;
    ys = ys * ys;
    return Math.sqrt( xs + ys );
}

var TextCoord = function(u,v) {
this.u = u?u:0;
this.v = v?v:0;
}

var Triangle = function(p0, p1, p2, t0, t1, t2) {
this.p0 = p0;
this.p1 = p1;
this.p2 = p2;

this.t0 = t0;
this.t1 = t1;
this.t2 = t2;
}

1

我知道这有点老了,但我相信问题在于剪裁,而不是像Steve建议的三角形。

你可以通过在Photoshop中创建一个新图层,填充它,然后使用多边形套索工具绘制一个形状,将其剪切并再次粘贴来模拟它。

如果你把它拖回原位,你会看到一个透明的线围绕着你绘制的形状。

我怀疑这就是发生的事情 - 被绘制和剪裁的路径实际上在靠近被剪裁区域的每个像素的alpha通道中添加了一些内容,使它们变得有点透明,然后它再次用下一个三角形的边缘做同样的事情,以此类推。从而创建出线框图案。

如果是这样,那么更好的解决方法是增加被剪裁的区域。

如果你替换这个:

var drawTriangle = function(ctx, im, x0, y0, x1, y1, x2, y2,
    sx0, sy0, sx1, sy1, sx2, sy2) {
    ctx.save();

    // Clip the output to the on-screen triangle boundaries.
    ctx.beginPath();
    ctx.moveTo(x0, y0);
    ctx.lineTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.closePath();
    //ctx.stroke();//xxxxxxx for wireframe
    ctx.clip();

使用这个:

triangleType = 0;

var drawTriangle = function (ctx, im, x0, y0, x1, y1, x2, y2, sx0, sy0, sx1, sy1, sx2, sy2) {
  ctx.save();

triangleType++;
if((triangleType % 2) == 0){    //upper triangle

    clipx0 = x0 - 1;    //top left
    clipy0 = y0 - 1;

    clipx1 = x1 + 6;    //top right
    clipy1 = y1 - 1;

    clipx2 = x2 + 1;    //bottom right
    clipy2 = y2 + 1;

} else {            //lower triangle

    clipx0 = x0 - 1;    //top left
    clipy0 = y0 - 1;

    clipx1 = x1 + 6;    // bottom right
    clipy1 = y1 + 1;

    clipx2 = x2 - 1;    //bottom left
    clipy2 = y2 + 1;
}

  // Clip the output to the on-screen triangle boundaries.
  ctx.beginPath();
  ctx.moveTo(clipx0, clipy0);
  ctx.lineTo(clipx1, clipy1);   
  ctx.lineTo(clipx2, clipy2);
  ctx.closePath();
  //ctx.stroke();
  //xxxxxxx for wireframe
  ctx.clip();

我们不能随意更改三角形输入,因为这些值用于多个不同的事情,而我们想要改变的唯一的东西是剪辑。

由于三角形成对出现,每次调用函数时我们只需简单地检查即可并将其添加到全局变量中。然后,我们可以使用模数检查余数,以找出它是顶部三角形还是底部三角形。

然后,我们可以创建一个输入,进行修改,即在不修改实际三角形数据的情况下。我们在用于剪辑的路径中使用它们,然后 - 我们得到了更好的结果。

我在这里只是做了一些任意的数字,它们产生了比目前为止任何解决方案都更好的结果,但它并不完美,并且在平滑边缘的情况下工作得很好,直到移动一个角落到达一定程度,你会看到背面/镜像版本。

有人比我有更多的空闲时间可能会想要更彻底地调整这些数字。

然而,在查看代码时,还有更多可以改进的地方:没有必要将其放在setInterval上,它是一项非常耗费资源的操作,应该在加载时仅绘制一次,并在移动角落时绘制一次。

另一件事是jQuery。对于获取元素、向元素添加类或样式或读取样式等简单的操作,没有必要使用jQuery。

如果您坚持不使用ID,则可以尝试以下代码来处理事件:

document.getElementsByTagName('body')[0].onmousedown = function (e) {
    if(e.target.classList.contains('node') == true){
        var node = e.target;
        document.getElementsByTagName('body')[0].onmousemove = function (e) {
            var x = e.pageX;
            var y = e.pageY;
            node.style.left = x + 'px';
            node.style.top = y + 'px';
            dirtyTriangles = true;
        }
        document.getElementsByTagName('body')[0].onmouseup = function (e) {
            document.getElementsByTagName('body')[0].onmousemove = null;
            document.getElementsByTagName('body')[0].onmouseup = null;
        }
    }
}

这是用于创建节点的代码:

    var boolChk = new Array();
    boolChk[0] = 'a';
    boolChk[1] = 'a';

  for (var i = 0; i < 4; ++i) {
    var control = document.createElement('div');
    control.classList.add('node');

//gör alla hörn i en fyrkant. a = lågt värde, b = högt värde

    control.style.left = styleHelper(boolChk[0]);
    control.style.top = styleHelper(boolChk[1]);

    if(boolChk[0] == 'a'){
        boolChk[0] = 'b';
    } else if(boolChk[1] == 'a'){
        boolChk[1] = 'b';
    } else if(boolChk[0] == 'b'){
        boolChk[0] = 'a';
    }

    document.getElementsByTagName('body')[0].appendChild(control);
    controls.push(control);
  }

但是,如果有善良的人能够整理一下这个,我相信社区会非常感激。


我再次研究了大约5年后的这个问题,可能会在一个项目中使用。@daniel-bengtsson,你有一个fiddle或者codepen可以展示你建议的修改吗?只需要算法部分,不用关心jquery的内容,那部分很容易剥离出来。 - Jarrod McGuire
刚试着实施了你的修改,看起来好多了,谢谢!太棒了!暂时不想发布我的作品,因为它仍然受到我五年前玩弄的东西的影响,所以希望能更加清晰一些。还想完全从界面中提取扭曲算法。也可能按照你建议的调整一下数字。线框图效果不如之前好了,但我想重点是要有一个漂亮的图像,而不是一个漂亮的线框图。如果你有办法通过逐步操作来消除图像的某个部分的扭曲,那就太好了。 - Jarrod McGuire
非常抱歉,我现在有点忙 - 但如果你能等那么久的话,我会在这个周末准备一个 CodePen。 - Daniel Bengtsson
实际上,不着急,也可能没有必要。我现在已经成功地整合了它,并且看到了很好的结果。将会考虑将一支更干净的笔准备好。 - Jarrod McGuire
太棒了,很高兴我能给你带来灵感。如果你能点个赞或者提及一下,我会非常感激。期待着那支笔的到来。 - Daniel Bengtsson

0
你可以尝试使用 function transferPoint (xI, yI, source, destination) ,详见这里
源和目标是源矩形和目标矩形的四个点。该代码适用于(SVG)路径坐标(曲线和线),但稍加修改后也可用于像素。

0

1
你的回答可以通过添加更多支持信息来改进。请点击[编辑]以添加更多详细信息,如引用或文档,这样其他人就可以确认你的答案是正确的。您可以在帮助中心找到有关撰写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接