Mr Bell
Mr Bell

Reputation: 9366

Draw distorted image on html5's canvas

Is it possible, and if so how, to draw images with its four corners distorted to a non rectangular shape. For example if you were wanting to draw the image as if it had been rotated around in 3d space.

Upvotes: 10

Views: 15650

Answers (6)

Daniel Bengtsson
Daniel Bengtsson

Reputation: 361

I know this is a little bit old, but I'm pretty sure that the issue is with the clipping, rather than the triangles as Steve suggested.

You can sort of replicate it in photoshop by creating a new layer, filling it and then using the polygonal lasso tool draw a shape, cut it out and paste it in again.

You will see a transparant line around the shape you drew if you drag it back into place.

I suspect that is what is happening here - the path that is being drawn and clipped essentially adds a bit to the alpha channel of each pixel close to the area being clipped, turning them a bit transparant, and then it does the same thing again with the edges of the next triangle, and the next, etc. Creating the wireframe pattern.

If so then a better way of solving it would be to increase the area that is being clipped.

If you replace this:

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();

with this:

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();

We can't really mess around with the triangle input, as those values are used for several different things, and the only thing we want to alter is the clipping.

As triangles comes in paires, we simply check each time the function is called and add to a global variable. We can then check the remainder using modulus to find out if it's a top or a bottom triangle.

We can then create an input which we modify, ie, without modifying the actual triangle data. We use those in the path that is used for clipping, and boom - we get a nicer result.

I just did some arbitrary numbers here, and they produce a better result than any solution here thusfar, but it's not perfect, and works just fine with smoother edges until the point where one corner is moved so far that you see the backside/mirrored version.

Someone with more free time than me might want to tweek those numbers a bit more thuroughly.

Looking over the code though, there is more that could be improved upon: there is absolutely no need to put this on a setInterval, it's quite a resource heavy operation, and should really only be drawn once when you load it and when you move a corner.

Another thing is the jquery. There is no need to use jquery for such simple things as getting an element, adding a class or style to an element or reading a style.

If you insist on not using an id, then something like this should work for events:

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

And this for creating the nodes:

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

But yeah, if some kind sole could clean this up, I think the community would appreciate it greatly.

Upvotes: 1

Jeremy Knudsen
Jeremy Knudsen

Reputation: 1

@Steve, I noticed that the image is invisible on first render in your updated Fiddle. It appears as soon as you start interacting with the corners however. See below

Image is invisible on first render until you interact with the corners

Upvotes: 0

Steve
Steve

Reputation: 602

A number of commenters above have asked about hiding the triangles - do this and they'll disappear (for info: @erbridge, @Constantin Groß):

All credit for the following still goes to @AndyPoes, since this was all his suggestion - I'm just adding the completed version he suggested.

Change this line:

render(true, image, triangle);

To this:

render(false, image, triangle);

...the black triangles will have gone, but you'll see gaps between the triangles, so now do this:

Change this:

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

To this instead:

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

....and the gaps will be closed.

New fiddle = fiddler off the roof

Full code is here:

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

Upvotes: 1

Andy Poes
Andy Poes

Reputation: 1710

I created a simple 4 point transform based on Thatcher Ulrich's code (http://tulrich.com/geekstuff/canvas/perspective.html). This version just extracts the small bit of triangle code and handles the geometry subdivision for proper transformation. Enjoy!

4 Point transformation in canvas

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

Upvotes: 18

Timo K&#228;hk&#246;nen
Timo K&#228;hk&#246;nen

Reputation: 12210

You could try function transferPoint (xI, yI, source, destination) explicated here.

Source and destination are the four points of source and destination rectangles. The code works for (SVG) path coordinates (curve and line), but with some modification should work also with pixels.

Upvotes: 0

Simon Sarris
Simon Sarris

Reputation: 63812

The easiest way is to apply a transformation to the canvas before drawing the image.

http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#transformations

With skewing transformations you can make it look like it was drawn in perspective

Here's a very simple example:

ctx.setTransform (1, -0.2, 0, 1, 0, 0);
ctx.drawImage(blah);

Upvotes: 3

Related Questions