【问题标题】:Crop the image in irregular shape and stretch it将图像裁剪为不规则形状并拉伸
【发布时间】:2014-09-07 10:35:40
【问题描述】:

我找到了描述我的问题的图片:

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

如何在 HTML5 中做到这一点? drawImage 函数(据我所知)仅适用于矩形(采用 x、y、宽度和高度值),因此我不能使用不规则形状。该解决方案必须适用于每个现代浏览器,所以我不想要基于 webgl 或其他东西的东西。

编辑: 更多信息:这将是用于编辑图片的应用程序。我想让用户剪切更大图片的一部分并对其进行编辑。它与Paint类似,因此需要画布来编辑像素。

【问题讨论】:

  • 我认为您最好的答案是使用为画布提供 3D 功能的 WebGL,但如果您不想尝试,也可以查看图像处理
  • 你能用画布以外的东西吗? css3 转换?
  • 您想将更改后的图像提供给您的用户吗?
  • 前段时间我尝试过,但我的结论是canvas的context2D太慢了,无法处理纹理。你可以看看这里:jsfiddle.net/gamealchemist/5cnkr2s5(Rq 我离开项目时有一些错误。)请注意,现在 webGL 支持变得非常大(caniuse.com/#feat=webgl)。
  • 第二个想法:如果你认为应该完全使用源图像(==纹理坐标在纹理的边缘),那么通过使用setTransform和一些技巧,我们可以实时画布上的纹理。如果你愿意,我可能会进一步解释,看看这里的小提琴:jsfiddle.net/gamealchemist/29oqyu3n

标签: javascript html html5-canvas


【解决方案1】:

你想要的效果是“透视变形”。

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

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

我不久前做过这个。它使用 Canvas 2d 并使用 1 像素宽的垂直切片重新绘制图像,这些垂直切片被拉伸以“伪造”透视扭曲。欢迎您将其用作项目的起点。

示例代码和演示: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>

【讨论】:

  • 这是一个非常有创意的解决方案。那没有直接回答我的问题,但多亏了你,我有一个想法:D
【解决方案2】:

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

要绘制带纹理的三角形,您必须:
• 自己计算绘制图像所需的变换。
• 剪辑到三角形,因为 drawImage 会绘制一个四边形。
• 具有正确变换的drawImage。

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

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

小提琴在这里:

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

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 种不同的仿射变换而不是透视变换)。

【讨论】:

  • 这看起来是正确的。如果原始图像中有任何直线,您将看到每个三角形的扭曲方式不同(2 种不同的仿射变换而不是透视变换)。见:imgur.com/OpLlGlC
【解决方案3】:
  1. 您需要有一个设置了透视和透视原点的容器
  2. 您需要使用 rotateY、skewY 并更改图像的高度和宽度

这背后可能有很多数学问题 - 就我个人而言,我只是在浏览器中摆弄它,让它看起来非常接近我需要的东西

所以这是一个小提琴:

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

【讨论】:

  • 我不想只调整某些对象的大小。我的程序将剪切较大图像的一部分并让用户编辑这个剪切的部分。
【解决方案4】:

【讨论】:

  • 你能提供更详细的解释吗?仅仅发布一个将来可能会中断的链接并不是很有帮助。
猜你喜欢
  • 1970-01-01
  • 2017-05-08
  • 2012-01-31
  • 1970-01-01
  • 1970-01-01
  • 2012-10-31
  • 1970-01-01
  • 1970-01-01
  • 2011-02-07
相关资源
最近更新 更多