Crop image in irregular shape and stretch it

I found images that depict my problem:

enter image description here

enter image description here

The user can select four points on the canvas to crop part of the image and stretch it.

How to do it in HTML5? The drawImage function (as I know) only works with rectangles (takes the values ​​x, y, width and height), so I cannot use the irregular shape. The solution should work in every modern browser, so I don’t need things based on webgl or something like that.

EDIT: More info: this is an image editing application. I want the user to cut off part of a larger picture and edit it. It will be similar to Paint, so a canvas is required for editing pixels.

+5
source share
4 answers

So, here's a tricky trick: you can use the regular drawImage of context2d to draw the texture inside the triangle.
The limitation is that the texture coordinates must be aligned along the axis.

To draw a textured triangle, you must: β€’ Calculate the transformation you need to draw the image yourself.
β€’ Fix the triangle, as drawImage will draw a quad. β€’ drawImage with the correct transformation.

So the idea is to split the quad into two triangles and display both.

But there is another trick: when drawing the lower right triangle, reading the texture should start from the bottom right of the texture, and then move up and to the left. This cannot be done in Firefox, which accepts only positive drawImage arguments. Therefore, I calculate the β€œmirror” of the first point against the other two, and I can draw again in the right direction - clipping will ensure that only the right side is drawn.

fiddle here:

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

Edit: I added an @szym comment with its example:

This view looks right. If there are direct lines in the original image, you will see that each triangle is deformed differently (2 different affine transformations, not a perspective transformation).

enter image description here

0
source

The effect you are going to use is "perspective warping."

A Canvas 2D context cannot do this out of the box because it cannot turn a rectangle into a trapezoid. Canvas 2D can only perform affine transformations that can only form parallelograms.

As the @Canvas user says, Canvas 3D (webgl) can do the transforms you are about to do.

I did this a while ago. He uses Canvas 2d, and he redraws the image using vertical slices of a width of 1 pixel, which are stretched to "fake" a perspective foundation. You can use it as a starting point for your project.

enter image description hereenter image description here

Sample code and demo: 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> 
+3
source
-1
source
  • You must have a container with a perspective and perspective of installation
  • You need to use rotateY, skewY and change your heights and widths in your image.

There is probably a lot of math behind this - personally, I just play with it in my browser so that it looks pretty close to what I need

So here is the fiddle:

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

Source: https://habr.com/ru/post/1202004/


All Articles