| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- import { Logger } from '@vendure/core';
- import sharp, { FormatEnum, Region, ResizeOptions } from 'sharp';
- import { ImageTransformParameters } from './config/image-transform-strategy';
- import { loggerCtx } from './constants';
- import { ImageTransformFormat } from './types';
- export type Dimensions = { w: number; h: number };
- export type Point = { x: number; y: number };
- /**
- * Applies transforms to the given image according to the query params passed.
- */
- export async function transformImage(
- originalImage: Buffer,
- parameters: ImageTransformParameters,
- ): Promise<sharp.Sharp> {
- const { width, height, mode, format } = parameters;
- const options: ResizeOptions = {};
- if (mode === 'crop') {
- options.position = sharp.strategy.entropy;
- } else {
- options.fit = 'inside';
- }
- const image = sharp(originalImage).rotate();
- try {
- await applyFormat(image, parameters.format, parameters.quality);
- } catch (e: any) {
- Logger.error(e.message, loggerCtx, e.stack);
- }
- if (parameters.fpx && parameters.fpy && width && height && mode === 'crop') {
- const metadata = await image.metadata();
- if (metadata.width && metadata.height) {
- const xCenter = parameters.fpx * metadata.width;
- const yCenter = parameters.fpy * metadata.height;
- const {
- width: resizedWidth,
- height: resizedHeight,
- region,
- } = resizeToFocalPoint(
- { w: metadata.width, h: metadata.height },
- { w: width, h: height },
- { x: xCenter, y: yCenter },
- );
- return image.resize(resizedWidth, resizedHeight).extract(region);
- }
- }
- return image.resize(width, height, options);
- }
- async function applyFormat(
- image: sharp.Sharp,
- format: ImageTransformFormat | undefined,
- quality: number | undefined,
- ) {
- switch (format) {
- case 'jpg':
- case 'jpeg':
- return image.jpeg({ quality });
- case 'png':
- return image.png();
- case 'webp':
- return image.webp({ quality });
- case 'avif':
- return image.avif({ quality });
- default: {
- if (quality) {
- // If a quality has been specified but no format, we need to determine the format from the image
- // and apply the quality to that format.
- const metadata = await image.metadata();
- if (isImageTransformFormat(metadata.format)) {
- return applyFormat(image, metadata.format, quality);
- }
- }
- return image;
- }
- }
- }
- function isImageTransformFormat(input: keyof FormatEnum | undefined): input is ImageTransformFormat {
- return !!input && ['jpg', 'jpeg', 'webp', 'avif'].includes(input);
- }
- /**
- * Resize an image but keep it centered on the focal point.
- * Based on the method outlined in https://github.com/lovell/sharp/issues/1198#issuecomment-384591756
- */
- export function resizeToFocalPoint(
- original: Dimensions,
- target: Dimensions,
- focalPoint: Point,
- ): { width: number; height: number; region: Region } {
- const { width, height, factor } = getIntermediateDimensions(original, target);
- const region = getExtractionRegion(factor, focalPoint, target, { w: width, h: height });
- return { width, height, region };
- }
- /**
- * Calculates the dimensions of the intermediate (resized) image.
- */
- function getIntermediateDimensions(
- original: Dimensions,
- target: Dimensions,
- ): { width: number; height: number; factor: number } {
- const hRatio = original.h / target.h;
- const wRatio = original.w / target.w;
- let factor: number;
- let width: number;
- let height: number;
- if (hRatio < wRatio) {
- factor = hRatio;
- height = Math.round(target.h);
- width = Math.round(original.w / factor);
- } else {
- factor = wRatio;
- width = Math.round(target.w);
- height = Math.round(original.h / factor);
- }
- return { width, height, factor };
- }
- /**
- * Calculates the Region to extract from the intermediate image.
- */
- function getExtractionRegion(
- factor: number,
- focalPoint: Point,
- target: Dimensions,
- intermediate: Dimensions,
- ): Region {
- const newXCenter = focalPoint.x / factor;
- const newYCenter = focalPoint.y / factor;
- const region: Region = {
- left: 0,
- top: 0,
- width: target.w,
- height: target.h,
- };
- if (intermediate.h < intermediate.w) {
- region.left = clamp(0, intermediate.w - target.w, Math.round(newXCenter - target.w / 2));
- } else {
- region.top = clamp(0, intermediate.h - target.h, Math.round(newYCenter - target.h / 2));
- }
- return region;
- }
- /**
- * Limit the input value to the specified min and max values.
- */
- function clamp(min: number, max: number, input: number) {
- return Math.min(Math.max(min, input), max);
- }
|