将图像裁剪成不规则形状并拉伸

时间:2014-09-07 10:35:40

标签: javascript html5 html5-canvas

我找到的图像描绘了我的问题:

enter image description here

enter image description here

用户可以在画布上选择四个点来裁剪图像的一部分而不是拉伸它。

如何在HTML5中执行此操作? drawImage函数(据我所知)只适用于矩形(取x,y,宽度和高度值)所以我不能使用不规则的形状。该解决方案必须适用于所有现代浏览器,因此我不想要基于webgl的东西。

修改 更多信息:这将是用于编辑图片的应用程序。我想让用户剪掉更大图片的某些部分并进行编辑。它类似于Paint,因此需要canvas来编辑像素。

4 个答案:

答案 0 :(得分:3)

你想要的效果是“透视翘曲”。

Canvas的2D上下文无法做到“开箱即用”,因为它无法将矩形变成梯形。 Canvas 2D只能进行仿射变换,只能形成平行四边形。

正如用户@Canvas所说,Canvas 3D(webgl)可以进行你想要的转换。

我做了一会儿。它使用Canvas 2d并使用1像素宽的垂直切片重绘图像,这些切片被拉伸以“伪造”透视扭曲。欢迎您将其作为项目的起点。

enter image description here enter image description here

示例代码和演示:http://jsfiddle.net/m1erickson/y4kst2pk/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    #canvas{border:1px solid red;}
</style>
<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var $canvas=$("#canvas");
    var canvasOffset=$canvas.offset();
    var offsetX=canvasOffset.left;
    var offsetY=canvasOffset.top;
    var scrollX=$canvas.scrollLeft();
    var scrollY=$canvas.scrollTop();

    //
    var isDown=false;
    var PI2=Math.PI*2;
    var selectedGuide=-1;
    var guides=[];


    //
    var marginLeft=50;
    var marginTop=50;
    var iw,ih,cw,ch;
    var img=new Image();
    img.onload=start;
    img.src='https://dl.dropboxusercontent.com/u/139992952/stack1/buildings1.jpg';
    function start(){

        iw=img.width;
        ih=img.height;
        canvas.width=iw+100;
        canvas.height=ih+100;
        cw=canvas.width;
        ch=canvas.height;
        ctx.strokeStyle="blue";
        ctx.fillStyle="blue";

        guides.push({x:0,y:0,r:10});
        guides.push({x:0,y:ih,r:10});
        guides.push({x:iw,y:0,r:10});
        guides.push({x:iw,y:ih,r:10});

        //
        $("#canvas").mousedown(function(e){handleMouseDown(e);});
        $("#canvas").mousemove(function(e){handleMouseMove(e);});
        $("#canvas").mouseup(function(e){handleMouseUp(e);});
        $("#canvas").mouseout(function(e){handleMouseOut(e);});

        drawAll();
    }

    function drawAll(){
        ctx.clearRect(0,0,cw,ch);
        drawGuides();
        drawImage();
    }

    function drawGuides(){
        for(var i=0;i<guides.length;i++){
            var guide=guides[i];
            ctx.beginPath();
            ctx.arc(guide.x+marginLeft,guide.y+marginTop,guide.r,0,PI2);
            ctx.closePath();
            ctx.fill();
        }
    }

    function drawImage(){

        // TODO use guides 
        var x1=guides[0].x;
        var y1=guides[0].y;
        var x2=guides[2].x;
        var y2=guides[2].y;
        var x3=guides[1].x;
        var y3=guides[1].y;
        var x4=guides[3].x;
        var y4=guides[3].y;


        // calc line equations slope & b (m,b)
        var m1=Math.tan( Math.atan2((y2-y1),(x2-x1)) );
        var b1=y2-m1*x2;
        var m2=Math.tan( Math.atan2((y4-y3),(x4-x3)) );
        var b2=y4-m2*x4;
        // draw vertical slices
        for(var X=0;X<iw;X++){
            var yTop=m1*X+b1;
            var yBottom=m2*X+b2;
            ctx.drawImage( img,X,0,1,ih, 
                X+marginLeft,yTop+marginTop,1,yBottom-yTop );
        }

        // outline
        ctx.save();
        ctx.translate(marginLeft,marginTop);
        ctx.beginPath();
        ctx.moveTo(x1,y1);
        ctx.lineTo(x2,y2);
        ctx.lineTo(x4,y4);
        ctx.lineTo(x3,y3);
        ctx.closePath();
        ctx.strokeStyle="black";
        ctx.stroke();
        ctx.restore();
    }



    function handleMouseDown(e){
      e.preventDefault();
      var mouseX=parseInt(e.clientX-offsetX);
      var mouseY=parseInt(e.clientY-offsetY);

      // Put your mousedown stuff here
      selectedGuide=-1;
      for(var i=0;i<guides.length;i++){
          var guide=guides[i];
          var dx=mouseX-(guide.x+marginLeft);
          var dy=mouseY-(guide.y+marginTop);
          if(dx*dx+dy*dy<=guide.r*guide.r){
              selectedGuide=i;
              break;
          }
      }
      isDown=(selectedGuide>=0);
    }

    function handleMouseUp(e){
      e.preventDefault();
      isDown=false;
    }

    function handleMouseOut(e){
      e.preventDefault();
      isDown=false;
    }

    function handleMouseMove(e){
      if(!isDown){return;}
      e.preventDefault();
      var x=parseInt(e.clientX-offsetX)-marginLeft;
      var y=parseInt(e.clientY-offsetY)-marginTop;
      var guide=guides[selectedGuide];
      guides[selectedGuide].y=y;
      if(selectedGuide==0 && y>guides[1].y){guide.y=guides[1].y;}
      if(selectedGuide==1 && y<guides[0].y){guide.y=guides[0].y;}
      if(selectedGuide==2 && y>guides[3].y){guide.y=guides[3].y;}
      if(selectedGuide==3 && y<guides[2].y){guide.y=guides[2].y;}
      drawAll();
    }

}); // end $(function(){});
</script>
</head>
<body>
    <h4>Perspective Warp by vertically dragging left or right blue guides.</h4>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

答案 1 :(得分:0)

所以这是一个杀戮技巧:您可以使用context2d的常规drawImage在三角形内绘制纹理。
约束是纹理坐标必须是轴对齐的。

要绘制纹理三角形,您必须:
•自己计算绘制图像所需的变换 •剪切为三角形,因为drawImage会绘制四边形 •使用正确的变换进行drawImage。

所以我们的想法是将你的四边形分成两个三角形,并渲染它们。

但还有一个技巧:绘制右下三角形时,纹理读取应从纹理的右下角开始,然后向上和向左移动。这不能在Firefox中完成,它只接受drawImage的正参数。所以我计算了第一个点与另外两个点的“镜像”,我可以再次以常规方向绘制 - 剪切将确保只绘制正确的部分 - 。

小提琴在这里:

http://jsfiddle.net/gamealchemist/zch3gdrx/

enter image description here

function rasterizeTriangle(v1, v2, v3, mirror) {
    var fv1 = {
        x: 0,
        y: 0,
        u: 0,
        v: 0
    };
    fv1.x = v1.x;
    fv1.y = v1.y;
    fv1.u = v1.u;
    fv1.v = v1.v;
    ctx.save();
    // Clip to draw only the triangle
    ctx.beginPath();
    ctx.moveTo(v1.x, v1.y);
    ctx.lineTo(v2.x, v2.y);
    ctx.lineTo(v3.x, v3.y);
    ctx.clip();
    // compute mirror point and flip texture coordinates for lower-right triangle
    if (mirror) {
        fv1.x = fv1.x + (v3.x - v1.x) + (v2.x - v1.x);
        fv1.y = fv1.y + (v3.y - v1.y) + (v2.y - v1.y);
        fv1.u = v3.u;
        fv1.v = v2.v;
    }
    // 
    var angleX = Math.atan2(v2.y - fv1.y, v2.x - fv1.x);
    var angleY = Math.atan2(v3.y - fv1.y, v3.x - fv1.x);
    var scaleX = lengthP(fv1, v2);
    var scaleY = lengthP(fv1, v3);
    var cos = Math.cos,
        sin = Math.sin;
    // ----------------------------------------
    //     Transforms
    // ----------------------------------------
    // projection matrix (world relative to center => screen)
    var transfMatrix = [];
    transfMatrix[0] = cos(angleX) * scaleX;
    transfMatrix[1] = sin(angleX) * scaleX;
    transfMatrix[2] = cos(angleY) * scaleY;
    transfMatrix[3] = sin(angleY) * scaleY;
    transfMatrix[4] = fv1.x;
    transfMatrix[5] = fv1.y;
    ctx.setTransform.apply(ctx, transfMatrix);
    // !! draw !!
    ctx.drawImage(bunny, fv1.u, fv1.v, v2.u - fv1.u, v3.v - fv1.v,
    0, 0, 1, 1);
    //
    ctx.restore();
};

编辑:我添加了@szym的相关评论,带有他的示例图片:

  

这种看起来没错。如果有任何直线   原始图像,您将看到每个三角形都有不同的扭曲   (2种不同的仿射变换而不是透视变换)。

enter image description here

答案 2 :(得分:-1)

  1. 您需要一个具有透视和透视原点设置的容器
  2. 您需要使用rotateY,skewY并更改图像的高度和宽度
  3. 这背后可能有很多数学 - 我个人只是在我的浏览器中弄乱它以使它看起来非常接近我需要的

    所以这是一个小提琴:

    http://jsfiddle.net/6egdevwe/1/

    #container { 
    margin: 50px; 
    perspective: 166px; perspective-origin: 50% 0px; }
    
    #testimage {
    transform: rotateY(93.4deg) skewY(34deg);
    width: 207px;
    height: 195px; }
    

答案 3 :(得分:-2)

css3转换 - &gt;旋转或旋转Z http://www.w3schools.com/cssref/css3_pr_transform.asp