Tom Kim
Tom Kim

Reputation: 190

Cropping images with html5 canvas in a non-rectangular shape and transforming

I am new to html5 canvas but I am creating a html5 canvas based image cropper which includes next features.

I tried to search any image croppers that satisfies these features, but couldn't find any.

I think I can manage to make the resize handlers and cropping stuff, but I have no idea of transformming the cropped image. Are there any nice examples or libraries to do this?


I am adding images to make things clear. when you crop the first image, the result should be the second image.

selecting cropping area enter image description here


ADDED!

I thought @markE 's solution would work, but after implementing and testing some images, I found out that it won't work very well with non-parallelograms.

sample 1 - original sample 1 - cropped

sample 2 - original sample 2 - cropped

As you can see, since this approach splits the rectangle into two triangles, the warped angles and amount? ends up different for both triangles. Thus, cropped images are shown distorted. I am figuring other ways to solve it.

Upvotes: 4

Views: 4735

Answers (1)

markE
markE

Reputation: 105035

What you are asking for is similar to Photoshop's Perspective Crop Tool.

This allow you to take in an un-straightened, skewed image and convert it into a straight, undistorted image.

http://www.howtogeek.com/howto/30973/easily-straighten-crooked-photographs-in-photoshop/

Left: unstraight, warped image, Right: straightened, unwarped image

enter image description here

You can also straighten and unwarp a 4-sided polygon in html5 canvas like this:

  1. Create a second destination canvas to hold your straightened, unwarped image.
  2. Divide the warped, 4-sided polygon into 2 triangles.
  3. Calculate the transformation matrix needed to map the first warped triangle into an unwarped triangle. A transformation matrix uses it's tools (scaling, rotation, skewing, movement) to take each pixel from the warped source triangle and transform that pixel onto the unwarped destination triangle.
  4. Clip the destination canvas to draw only in the calculated destination triangle.
  5. Draw the source image onto the destination canvas. Because the destination canvas has a clipping region defined, the image will only be drawn into that clipping region. Because the destination canvas has a transformation applied to it, the previously warped triangle will be draw as unwarped.
  6. Repeat steps #3-5 for the second warped triangle.

So your warped image (on the left) can be unwarped (as shown on the right):

enter image description here

This is what the 2 unwarped triangles look like before they are stitched together to form 1 unwarped rectangle:

enter image description here

Here is example code and a Demo:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");

// anchors defining the warped rectangle
var anchors={
  TL:{x:70,y:40},      // r
  TR:{x:377,y:30},     // g
  BR:{x:417,y:310},    // b
  BL:{x:70,y:335},     // gold
}

// cornerpoints defining the desire unwarped rectangle
var unwarped={
  TL:{x:0,y:0},        // r
  TR:{x:300,y:0},      // g
  BR:{x:300,y:300},    // b
  BL:{x:0,y:300},      // gold
}

// load example image
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/skewed1.png";
function start(){

  // set canvas sizes equal to image size
  cw=canvas.width=canvas1.width=img.width;
  ch=canvas.height=canvas1.height=img.height;

  // draw the example image on the source canvas
  ctx.drawImage(img,0,0);

  // unwarp the source rectangle and draw it to the destination canvas
  unwarp(anchors,unwarped,ctx1);

}


// unwarp the source rectangle
function unwarp(anchors,unwarped,context){

  // clear the destination canvas
  context.clearRect(0,0,context.canvas.width,context.canvas.height);

  // unwarp the bottom-left triangle of the warped polygon
  mapTriangle(context,
              anchors.TL,  anchors.BR,  anchors.BL,
              unwarped.TL, unwarped.BR, unwarped.BL
             );

  // eliminate slight space between triangles
  ctx1.translate(-1,1);

  // unwarp the top-right triangle of the warped polygon
  mapTriangle(context,
              anchors.TL,  anchors.TR,  anchors.BR,
              unwarped.TL, unwarped.TR, unwarped.BR
             );

}


// Perspective mapping: Map warped triangle into unwarped triangle
// Attribution: (SO user: 6502), http://stackoverflow.com/questions/4774172/image-manipulation-and-texture-mapping-using-html5-canvas/4774298#4774298
function mapTriangle(ctx,p0, p1, p2, p_0, p_1, p_2) {

  // break out the individual triangles x's & y's
  var x0=p_0.x, y0=p_0.y;
  var x1=p_1.x, y1=p_1.y;
  var x2=p_2.x, y2=p_2.y;
  var u0=p0.x,  v0=p0.y;
  var u1=p1.x,  v1=p1.y;
  var u2=p2.x,  v2=p2.y;

  // save the unclipped & untransformed destination canvas
  ctx.save();

  // clip the destination canvas to the unwarped destination triangle
  ctx.beginPath();
  ctx.moveTo(x0, y0);
  ctx.lineTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.closePath();
  ctx.clip();

  // Compute matrix transform
  var delta   = u0 * v1 + v0 * u2 + u1 * v2 - v1 * u2 - v0 * u1 - u0 * v2;
  var delta_a = x0 * v1 + v0 * x2 + x1 * v2 - v1 * x2 - v0 * x1 - x0 * v2;
  var delta_b = u0 * x1 + x0 * u2 + u1 * x2 - x1 * u2 - x0 * u1 - u0 * x2;
  var delta_c = u0 * v1 * x2 + v0 * x1 * u2 + x0 * u1 * v2 - x0 * v1 * u2 - v0 * u1 * x2 - u0 * x1 * v2;
  var delta_d = y0 * v1 + v0 * y2 + y1 * v2 - v1 * y2 - v0 * y1 - y0 * v2;
  var delta_e = u0 * y1 + y0 * u2 + u1 * y2 - y1 * u2 - y0 * u1 - u0 * y2;
  var delta_f = u0 * v1 * y2 + v0 * y1 * u2 + y0 * u1 * v2 - y0 * v1 * u2 - v0 * u1 * y2 - u0 * y1 * v2;

  // Draw the transformed image
  ctx.transform(
    delta_a / delta, delta_d / delta,
    delta_b / delta, delta_e / delta,
    delta_c / delta, delta_f / delta
  );

  // draw the transformed source image to the destination canvas
  ctx.drawImage(img,0,0);

  // restore the context to it's unclipped untransformed state
  ctx.restore();
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<h4>Warped image transformed into an unwarped image.</h4>
<canvas id="canvas" width=300 height=300></canvas>
<canvas id="canvas1" width=300 height=300></canvas>

Upvotes: 3

Related Questions