image-js
Version:
Image processing and manipulation in JavaScript
75 lines • 3.38 kB
JavaScript
import { transform } from '../geometry/index.js';
import { getAngle } from '../maskAnalysis/utils/getAngle.js';
import { rotatePoint } from '../point/operations.js';
/**
* Crop an oriented rectangle from an image.
* If the rectangle's length or width are not an integers, its dimension is expanded in both directions such as the length and width are integers.
* @param image - The input image
* @param points - The points of the rectangle. Points must be circling around the rectangle (clockwise or anti-clockwise). The validity of the points passed is assumed and not checked.
* @param options - Crop options, see {@link CropRectangleOptions}
* @returns The cropped image. The orientation of the image is the one closest to the rectangle passed as input.
*/
export function cropRectangle(image, points, options) {
if (points.length !== 4) {
throw new Error('The points array must contain 4 points');
}
// get the smallest possible angle which puts the rectangle in an upright position
const angle = getSmallestAngle(points);
const center = {
row: (points[0].row + points[2].row) / 2,
column: (points[0].column + points[2].column) / 2,
};
// Rotated points form an upright rectangle
const rotatedPoints = points.map((p) => rotatePoint(p, center, angle));
const [p1, p2, p3] = rotatedPoints;
const originalWidth = Math.max(Math.abs(p1.column - p2.column), Math.abs(p2.column - p3.column));
const originalHeight = Math.max(Math.abs(p1.row - p2.row), Math.abs(p2.row - p3.row));
// Deal with numerical imprecision when the rectangle actually had a whole number width or height
const width = Math.min(Math.ceil(originalWidth), Math.ceil(originalWidth - 1e-10));
const height = Math.min(Math.ceil(originalHeight), Math.ceil(originalHeight - 1e-10));
// Top left position of the upright rectangle after normalization of width and height
const expandedTopLeft = {
row: Math.min(...rotatedPoints.map((p) => p.row)) -
(height - originalHeight) / 2,
column: Math.min(...rotatedPoints.map((p) => p.column)) -
(width - originalWidth) / 2,
};
const translation = rotatePoint(expandedTopLeft, center, -angle);
const angleCos = Math.cos(-angle);
const angleSin = Math.sin(-angle);
const matrix = [
[angleCos, -angleSin, translation.column],
[angleSin, angleCos, translation.row],
];
return transform(image, matrix, {
inverse: true,
width,
height,
...options,
});
}
/**
* Get the smallest angle to put the rectangle in an upright position
* @param points - 2 points forming a line
* @returns The angle in radians
*/
function getSmallestAngle(points) {
// Angle respective to horizontal, -π/2 and π/2
let angleHorizontal = -getAngle(points[1], points[0]);
if (angleHorizontal > Math.PI / 2) {
angleHorizontal -= Math.PI;
}
else if (angleHorizontal < -Math.PI / 2) {
angleHorizontal += Math.PI;
}
// angle is between -π/4 and π/4
let angle = angleHorizontal;
if (Math.abs(angleHorizontal) > Math.PI / 4) {
angle =
angleHorizontal > 0
? -Math.PI / 2 + angleHorizontal
: Math.PI / 2 + angleHorizontal;
}
return angle;
}
//# sourceMappingURL=cropRectangle.js.map