import type { Image } from '../Image.js'; import type { BorderType } from '../utils/interpolateBorder.js'; import type { InterpolationType } from '../utils/interpolatePixel.js'; import { assert } from '../utils/validators/assert.js'; import { transform } from './transform.js'; export interface ResizeOptions { /** * Width of the output image. */ width?: number; /** * Height of the output image. */ height?: number; /** * Factor by which to scale the width. */ xFactor?: number; /** * Factor by which to scale the width. */ yFactor?: number; /** * Whether the aspect ratio of the image should be preserved. * @default `true` */ preserveAspectRatio?: boolean; /** * Method to use to interpolate the new pixels. * @default `'bilinear'` */ interpolationType?: InterpolationType; /** * Specify how the borders should be handled. * @default `'constant'` */ borderType?: BorderType; /** * Value of the border if BorderType is 'constant'. * @default `0` */ borderValue?: number | number[]; } /** * Returns a resized copy of an image. * @param image - Original image. * @param options - Resize options. * @returns The new image. */ export function resize(image: Image, options: ResizeOptions): Image { const { interpolationType = 'bilinear', borderType = 'replicate', borderValue = 0, } = options; const { width, height, xFactor, yFactor } = checkOptions(image, options); return transform( image, [ [xFactor, 0, xFactor / 2], [0, yFactor, yFactor / 2], ], { interpolationType, borderType, borderValue, height, width, }, ); } /** * Verify that the resize options are valid. * @param image - Image. * @param options - Resize options. * @returns Resize options. */ function checkOptions( image: Image, options: ResizeOptions, ): { width: number; height: number; xFactor: number; yFactor: number } { const { width, height, xFactor, yFactor, preserveAspectRatio = true, } = options; if ( width === undefined && height === undefined && xFactor === undefined && yFactor === undefined ) { throw new TypeError( 'at least one of the width, height, xFactor or yFactor options must be passed', ); } let newWidth: number; let newHeight: number; const maybeWidth = getSize(width, xFactor, image.width, preserveAspectRatio); const maybeHeight = getSize( height, yFactor, image.height, preserveAspectRatio, ); if (maybeWidth === undefined) { assert(maybeHeight !== undefined); newWidth = Math.round(maybeHeight * (image.width / image.height)); } else { newWidth = maybeWidth; } if (maybeHeight === undefined) { assert(maybeWidth !== undefined); newHeight = Math.round(maybeWidth * (image.height / image.width)); } else { newHeight = maybeHeight; } return { width: newWidth, height: newHeight, xFactor: xFactor ?? newWidth / image.width, yFactor: yFactor ?? newHeight / image.height, }; } /** * Compute automatic new size. * @param sizeOpt - Size option. * @param factor - Factor option. * @param sizeImg - Size of the image. * @param preserveAspectRatio - Whether to preserve the aspect ratio. * @returns New size. */ function getSize( sizeOpt: number | undefined, factor: number | undefined, sizeImg: number, preserveAspectRatio: boolean, ): number | undefined { if (sizeOpt === undefined) { if (factor !== undefined) { return Math.round(sizeImg * factor); } else if (!preserveAspectRatio) { return sizeImg; } } else if (factor !== undefined) { throw new TypeError('factor and size cannot be passed together'); } else { return sizeOpt; } return undefined; }