image-js
Version:
Image processing and manipulation in JavaScript
108 lines • 4.8 kB
JavaScript
import { Matrix, inverse } from 'ml-matrix';
import { Image } from '../Image.js';
import { getClamp } from '../utils/clamp.js';
import { getDefaultColor } from "../utils/getDefaultColor.js";
import { getBorderInterpolation } from '../utils/interpolateBorder.js';
import { getInterpolationFunction } from '../utils/interpolatePixel.js';
import { validateColor } from "../utils/validators/validators.js";
/**
* Transforms an image using a matrix.
* @param image - Original image.
* @param transformMatrix - 2×3 transform matrix.
* @param options - Transform options.
* @returns The new image.
*/
export function transform(image, transformMatrix, options = {}) {
const { borderValue = getDefaultColor(image), borderType = 'constant', interpolationType = 'bilinear', fullImage, } = options;
let { width = image.width, height = image.height } = options;
if (Array.isArray(borderValue)) {
validateColor(borderValue, image);
}
if (!isValidMatrix(transformMatrix)) {
throw new TypeError(`transformation matrix must be 2x3 or 3x3. Received ${transformMatrix.length}x${transformMatrix[1].length}`);
}
if (transformMatrix.length === 2) {
transformMatrix.push([0, 0, 1]);
}
if (fullImage) {
transformMatrix = transformMatrix.map((row) => row.slice());
transformMatrix[0][2] = 0;
transformMatrix[1][2] = 0;
const corners = [
image.getCoordinates('top-left'),
image.getCoordinates('top-right'),
image.getCoordinates('bottom-right'),
image.getCoordinates('bottom-left'),
];
corners[1].column += 1;
corners[2].column += 1;
corners[2].row += 1;
corners[3].row += 1;
const transformedCorners = corners.map((corner) => {
return [
transformPoint(transformMatrix[0], transformMatrix[2], corner.column, corner.row),
transformPoint(transformMatrix[1], transformMatrix[2], corner.column, corner.row),
];
});
const xCoordinates = transformedCorners.map((c) => c[0]);
const yCoordinates = transformedCorners.map((c) => c[1]);
const maxX = Math.max(...xCoordinates);
const maxY = Math.max(...yCoordinates);
const minX = Math.min(...xCoordinates);
const minY = Math.min(...yCoordinates);
const center = [(image.width - 1) / 2, (image.height - 1) / 2];
width = maxX - minX;
height = maxY - minY;
const centerX = transformPoint(transformMatrix[0], transformMatrix[2], center[0], center[1]);
const centerY = transformPoint(transformMatrix[1], transformMatrix[2], center[0], center[1]);
const a = (width - 1) / 2 - centerX;
const b = (height - 1) / 2 - centerY;
transformMatrix[0][2] = a;
transformMatrix[1][2] = b;
width = Math.round(width);
height = Math.round(height);
}
if (!options.inverse) {
transformMatrix = inverse(new Matrix(transformMatrix)).to2DArray();
}
const newImage = Image.createFrom(image, {
width,
height,
});
const interpolateBorder = getBorderInterpolation(borderType, borderValue);
const clamp = getClamp(newImage);
const interpolate = getInterpolationFunction(interpolationType);
for (let row = 0; row < newImage.height; row++) {
for (let column = 0; column < newImage.width; column++) {
const nx = transformPoint(transformMatrix[0], transformMatrix[2], column, row);
const ny = transformPoint(transformMatrix[1], transformMatrix[2], column, row);
for (let channel = 0; channel < newImage.channels; channel++) {
const newValue = interpolate(image, nx, ny, channel, interpolateBorder, clamp);
newImage.setValue(column, row, channel, newValue);
}
}
}
return newImage;
}
/**
* Apply a transformation to a point.
* @param transform - Transformation matrix.
* @param perspective - Perspective matrix.
* @param column - Column of the point.
* @param row - Row of the point.
* @returns New value.
*/
function transformPoint(transform, perspective, column, row) {
return ((transform[0] * column + transform[1] * row + transform[2]) /
(perspective[0] * column + perspective[1] * row + perspective[2]));
}
function isValidMatrix(transformationMatrix) {
return ((transformationMatrix.length === 3 &&
transformationMatrix[0].length === 3 &&
transformationMatrix[1].length === 3 &&
transformationMatrix[2].length === 3) ||
(transformationMatrix.length === 2 &&
transformationMatrix[0].length === 3 &&
transformationMatrix[1].length === 3));
}
//# sourceMappingURL=transform.js.map