UNPKG

image-js

Version:

Image processing and manipulation in JavaScript

75 lines 3.38 kB
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