Taeho
Taeho

Reputation: 23

Combine two image with two point of each image with html5 canvas

I try to combine two image.

  1. Each image has different size and ratio.
  2. Each image has two point for matching.
  3. One image be background and the other be drew on that.

I want to combine images like this sample picture. (black is background) sample

I know the normalized value of each points and size of each images like this

I try to combine with angle function, but I didn't do this well with html5 canvas. How can I match these points well?

Upvotes: 0

Views: 327

Answers (1)

Kaiido
Kaiido

Reputation: 137171

You first need to calculate the transformation to apply on your green rectangle so that its first-point overlaps the black's first point.

const translate = {
  x: black_points[ 0 ].x - green_points[ 0 ].x,
  y: black_points[ 0 ].y - green_points[ 0 ].y
};

Then you need to find the angle by which the green "line" made by the two points would be in the same direction as the black one.
This can be done by finding the angle between two points (using Math.atan2()) and simply substracting the result of both lines.

const angle = getRotation( black_points ) - getRotation( green_points );

Finally, you need to find by how much you'd need to scale the green line so that it's as big as the black one. This is simply finding the ratio between their respective distance, which can be calculated with Math.hypot().

const scale = getDistance( black_points) / getDistance( green_points );

However, beware that since we calculated the rotation and the scale relatively to the first point, we need to move the transformation-origin to that point before applying these transforms.

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

// we'll start by converting our relative points to absolute coords
const black_rect = { width: 500, height: 500 };
const black_points = [
  [0.15, 0.25],
  [0.74, 0.5]
].map( ([x, y]) => new DOMPoint( x * black_rect.width, y * black_rect.height ) );

const green_rect = { width: 200, height: 90 };
const green_points = [
  [0.05, 0.85],
  [0.86, 0.12]
].map( ([x, y]) => new DOMPoint( x * green_rect.width, y * green_rect.height ) );

// move the first green point so it overlaps the black one
const translate = {
  x: black_points[ 0 ].x - green_points[ 0 ].x,
  y: black_points[ 0 ].y - green_points[ 0 ].y
};
// find the direction so that there is a single vector 
const angle = getRotation( black_points ) - getRotation( green_points );
// by how much to stretch
const scale = getDistance( black_points) / getDistance( green_points );

// first we draw the "black" part untransformed
ctx.fillRect(0, 0, black_rect.width, black_rect.height);
ctx.fillStyle = "red";
black_points.forEach( ({x, y}) => ctx.arc( x, y, 5, 0, Math.PI * 2) );
ctx.fill();

// now we apply the transfrormation we found
// first make both first points overlap
ctx.translate( translate.x, translate.y );
// set the transformation origin on these first points
// because we calculated both the 'angle' and 'scale' relative to this position
ctx.translate( green_points[ 0 ].x, green_points[ 0 ].y );
ctx.rotate( angle );
ctx.scale( scale, scale );
// revert the transformation origin to top-left corner
ctx.translate( -green_points[ 0 ].x, -green_points[ 0 ].y );

// draw the "green" part
ctx.globalAlpha = .5;
ctx.fillStyle = "green";
ctx.fillRect( 0, 0, green_rect.width, green_rect.height );
ctx.beginPath();
ctx.fillStyle = "blue";
green_points.forEach( ({x, y}) => ctx.arc( x, y, 5, 0, Math.PI * 2 ));
ctx.fill();


function getRotation( [ point1, point2 ] ) {
  const dx = point1.x - point2.x;
  const dy = point1.y - point2.y;
  return Math.atan2(dy, dx);
}
function getDistance( [ point1, point2 ] ) {
  const dx = point1.x - point2.x;
  const dy = point1.y - point2.y;
  return Math.hypot( dy, dx );
}
<canvas width="800" height="800"></canvas>

Upvotes: 3

Related Questions