Skip to content

Perspective Control

TIP

See Perspective control at Wikipedia

With Lens you can correct perspective distortion of objects on photo.

Look at this photo of art object:

Art object

It looks like sculpture is 'falling':

Guides

Let's correct this with Perspective distortion. I've marked control points that we will use. Magenta points (1, 2) are staying still. And points 3 and 4 are moving from their original position (green) to the new position (red):

Control points

Let's apply that perspective transform and see what we will get:

ts
import { distort, Canvas } from "@alxcube/lens";

const sourceImage = await Canvas.createFromUrl("fall.jpg");
// prettier-ignore
const controlPoints   = [
  97, 236, 97, 236,     // We are not moving...
  275, 255, 275, 255,   // ...these two points.
  95, 432, 97, 432,     // Move (95, 432) to (97, 432).
  282, 397, 275, 397    // Move (282, 397) to (275, 397).
];
const { image } = distort(sourceImage, "Perspective", controlPoints);

// display result
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image.getResource(), 0, 0);
document.body.appendChild(canvas);

Perspective applied

As you can see, there's no more falling effect, but some empty area appeared. And you may not noticed it, but part of image at the top was cropped.

Using more hard work, we can pre-calculate viewport for distortion to crop the excess and make image rectangular.

WARNING

This is not universal way for viewport calculation. It used for this particular example in demo purposes.

ts
import { Canvas, distort, PerspectiveFactory } from "@alxcube/lens";

const sourceImage = await Canvas.createFromUrl("fall.jpg");
// prettier-ignore
const controlPoints   = [
  97, 236, 97, 236,     // We are not moving...
  275, 255, 275, 255,   // ...these two points.
  95, 432, 97, 432,     // Move (95, 432) to (97, 432).
  282, 397, 275, 397    // Move (282, 397) to (275, 397).
];

// Create Perspective distortion class instance from control points set above
const perspective = new PerspectiveFactory().create(controlPoints);

// Forward map image corners to find their new locations.
const topLeft = perspective.forwardMap(0, 0);
const topRight = perspective.forwardMap(sourceImage.width - 1, 0);
const bottomLeft = perspective.forwardMap(0, sourceImage.height - 1);
const bottomRight = perspective.forwardMap(
  sourceImage.width - 1,
  sourceImage.height - 1
);

/*
 * Use inner orthogonal rectangle of quadrilateral defined by forward mapped points as cropped viewport.
 * This is NOT UNIVERSAL WAY, but we can use it in this simple demo.
 * If you want universal solution, have a look at this algorithm:
 * http://cgm.cs.mcgill.ca/~athens/cs507/Projects/2003/DanielSud/
 */
const viewport = {
  x1: Math.max(topLeft[0], bottomLeft[0]),
  y1: Math.max(topLeft[1], topRight[1]),
  x2: Math.min(topRight[0], bottomRight[0]),
  y2: Math.min(bottomRight[1], bottomLeft[1]),
};

// Perform distortion using previously created perspective distortion instance
const { image } = await distort(sourceImage, perspective, { viewport });

// display result
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image.getResource(), 0, 0);
document.body.appendChild(canvas);

Final result