import { match } from 'ts-pattern'; import type { Image } from '../Image.js'; import type { BorderInterpolationFunction } from './utils.types.js'; export const BorderType = { CONSTANT: 'constant', REPLICATE: 'replicate', REFLECT: 'reflect', WRAP: 'wrap', REFLECT_101: 'reflect101', } as const; export type BorderType = (typeof BorderType)[keyof typeof BorderType]; /** * Pick the border interpolation algorithm. * The different algorithms are illustrated here: * @see {@link https://vovkos.github.io/doxyrest-showcase/opencv/sphinx_rtd_theme/enum_cv_BorderTypes.html} * @param type - The border type. * @param value - A pixel value if BorderType.CONSTANT is used. * @returns The border interpolation function. */ export function getBorderInterpolation( type: BorderType, value: number | number[], ): BorderInterpolationFunction { if (typeof value === 'number') { value = new Array(4).fill(value); } return match(type) .with('constant', () => getInterpolateConstant(value)) .with('replicate', () => interpolateReplicate) .with('reflect', () => interpolateReflect) .with('reflect101', () => interpolateReflect101) .with('wrap', () => interpolateWrap) .exhaustive(); } function checkRange(point: number, length: number): void { if (point <= 0 - length || point >= length + length - 1) { throw new RangeError('border must be smaller than the original image'); } } function getInterpolateConstant(value: number[]): BorderInterpolationFunction { return function interpolateConstant( column: number, row: number, channel: number, image: Image, ): number { const newColumn = interpolateConstantPoint(column, image.width); const newRow = interpolateConstantPoint(row, image.height); if (newColumn === -1 || newRow === -1) { return value[channel]; } return image.getValue(newColumn, newRow, channel); }; } /** * Interpolate using a constant point. * @param point - The point to interpolate. * @param length - The length of the image. * @returns The interpolated point. */ export function interpolateConstantPoint( point: number, length: number, ): number { if (point >= 0 && point < length) { return point; } return -1; } function interpolateReplicate( column: number, row: number, channel: number, image: Image, ): number { return image.getValue( interpolateReplicatePoint(column, image.width), interpolateReplicatePoint(row, image.height), channel, ); } /** * Interpolate by replicating the border. * @param point - The point to interpolate. * @param length - The length of the image. * @returns The interpolated point. */ export function interpolateReplicatePoint( point: number, length: number, ): number { if (point >= 0 && point < length) { return point; } checkRange(point, length); if (point < 0) { return 0; } else { return length - 1; } } function interpolateReflect( column: number, row: number, channel: number, image: Image, ): number { return image.getValue( interpolateReflectPoint(column, image.width), interpolateReflectPoint(row, image.height), channel, ); } /** * Interpolate by reflecting the border. * @param point - The point to interpolate. * @param length - The length of the image. * @returns The interpolated point. */ export function interpolateReflectPoint(point: number, length: number): number { if (point >= 0 && point < length) { return point; } checkRange(point, length); if (point < 0) { return -1 - point; } else { return length + length - 1 - point; } } function interpolateWrap( column: number, row: number, channel: number, image: Image, ): number { return image.getValue( interpolateWrapPoint(column, image.width), interpolateWrapPoint(row, image.height), channel, ); } /** * Interpolate by wrapping the border. * @param point - The point to interpolate. * @param length - The length of the image. * @returns The interpolated point. */ export function interpolateWrapPoint(point: number, length: number): number { if (point >= 0 && point < length) { return point; } checkRange(point, length); if (point < 0) { return length + point; } else { return point - length; } } function interpolateReflect101( column: number, row: number, channel: number, image: Image, ): number { return image.getValue( interpolateReflect101Point(column, image.width), interpolateReflect101Point(row, image.height), channel, ); } /** * Interpolate by reflecting the border. * @param point - The point to interpolate. * @param length - The length of the image. * @returns The interpolated point. */ export function interpolateReflect101Point( point: number, length: number, ): number { if (point >= 0 && point < length) { return point; } checkRange(point, length); if (point < 0) { return 0 - point; } else { return length + length - 2 - point; } }