directus

Форк
0
/
transformations.ts 
184 строки · 5.4 Кб
1
import type { File } from '@directus/types';
2
import { clamp } from 'lodash-es';
3
import type { Region } from 'sharp';
4
import type { Transformation, TransformationFormat, TransformationSet } from '../types/index.js';
5

6
export function resolvePreset({ transformationParams, acceptFormat }: TransformationSet, file: File): Transformation[] {
7
	const transforms = transformationParams.transforms ? [...transformationParams.transforms] : [];
8

9
	if (transformationParams.format || transformationParams.quality) {
10
		transforms.push([
11
			'toFormat',
12
			getFormat(file, transformationParams.format, acceptFormat),
13
			{
14
				quality: transformationParams.quality ? Number(transformationParams.quality) : undefined,
15
			},
16
		]);
17
	}
18

19
	if ((transformationParams.width || transformationParams.height) && file.width && file.height) {
20
		const toWidth = transformationParams.width ? Number(transformationParams.width) : undefined;
21
		const toHeight = transformationParams.height ? Number(transformationParams.height) : undefined;
22

23
		const toFocalPointX = transformationParams.focal_point_x
24
			? Number(transformationParams.focal_point_x)
25
			: file.focal_point_x;
26

27
		const toFocalPointY = transformationParams.focal_point_y
28
			? Number(transformationParams.focal_point_y)
29
			: file.focal_point_y;
30

31
		/*
32
		 * Focal point cropping only works with a fixed size (width x height) when `cover`ing,
33
		 * since the other modes show the whole image. Sharp by default also simply scales up/down
34
		 * when only supplied with one dimension, so we **must** check, else we break existing behaviour.
35
		 * See: https://sharp.pixelplumbing.com/api-resize#resize
36
		 * Also only crop to focal point when explicitly defined so that users can still `cover` with
37
		 * other parameters like `position` and `gravity` - Else fall back to regular behaviour
38
		 */
39
		if (
40
			(transformationParams.fit === undefined || transformationParams.fit === 'cover') &&
41
			toWidth &&
42
			toHeight &&
43
			toFocalPointX !== null &&
44
			toFocalPointY !== null
45
		) {
46
			const transformArgs = getResizeArguments(
47
				{ w: file.width, h: file.height },
48
				{ w: toWidth, h: toHeight },
49
				{ x: toFocalPointX, y: toFocalPointY },
50
			);
51

52
			transforms.push(
53
				[
54
					'resize',
55
					{
56
						width: transformArgs.width,
57
						height: transformArgs.height,
58
						fit: transformationParams.fit,
59
						withoutEnlargement: transformationParams.withoutEnlargement
60
							? Boolean(transformationParams.withoutEnlargement)
61
							: undefined,
62
					},
63
				],
64
				['extract', transformArgs.region],
65
			);
66
		} else {
67
			transforms.push([
68
				'resize',
69
				{
70
					width: toWidth,
71
					height: toHeight,
72
					fit: transformationParams.fit,
73
					withoutEnlargement: transformationParams.withoutEnlargement
74
						? Boolean(transformationParams.withoutEnlargement)
75
						: undefined,
76
				},
77
			]);
78
		}
79
	}
80

81
	return transforms;
82
}
83

84
function getFormat(
85
	file: File,
86
	format: TransformationSet['transformationParams']['format'],
87
	acceptFormat: TransformationSet['acceptFormat'],
88
): TransformationFormat {
89
	const fileType = file.type?.split('/')[1] as TransformationFormat | undefined;
90

91
	if (format) {
92
		if (format !== 'auto') {
93
			return format;
94
		}
95

96
		if (acceptFormat) {
97
			return acceptFormat;
98
		}
99

100
		if (fileType && ['avif', 'webp', 'tiff'].includes(fileType)) {
101
			return 'png';
102
		}
103
	}
104

105
	return fileType || 'jpg';
106
}
107

108
/**
109
 * Try to extract a file format from an array of `Transformation`'s.
110
 */
111
export function maybeExtractFormat(transforms: Transformation[]): string | undefined {
112
	const toFormats = transforms.filter((t) => t[0] === 'toFormat');
113
	const lastToFormat = toFormats[toFormats.length - 1];
114
	return lastToFormat ? lastToFormat[1]?.toString() : undefined;
115
}
116

117
type Dimensions = { w: number; h: number };
118
type FocalPoint = { x: number; y: number };
119

120
/**
121
 * Resize an image but keep it centered on the focal point.
122
 * Based on the method outlined in https://github.com/lovell/sharp/issues/1198#issuecomment-384591756
123
 */
124
function getResizeArguments(
125
	original: Dimensions,
126
	target: Dimensions,
127
	focalPoint?: FocalPoint | null,
128
): { width: number; height: number; region: Region } {
129
	const { width, height, factor } = getIntermediateDimensions(original, target);
130

131
	const region = getExtractionRegion(factor, focalPoint ?? { x: original.w / 2, y: original.h / 2 }, target, {
132
		w: width,
133
		h: height,
134
	});
135

136
	return { width, height, region };
137
}
138

139
/**
140
 * Calculates the dimensions of the intermediate (resized) image.
141
 */
142
function getIntermediateDimensions(
143
	original: Dimensions,
144
	target: Dimensions,
145
): { width: number; height: number; factor: number } {
146
	const hRatio = original.h / target.h;
147
	const wRatio = original.w / target.w;
148

149
	let factor: number;
150
	let width: number;
151
	let height: number;
152

153
	if (hRatio < wRatio) {
154
		factor = hRatio;
155
		height = Math.round(target.h);
156
		width = Math.round(original.w / factor);
157
	} else {
158
		factor = wRatio;
159
		width = Math.round(target.w);
160
		height = Math.round(original.h / factor);
161
	}
162

163
	return { width, height, factor };
164
}
165

166
/**
167
 * Calculates the Region to extract from the intermediate image.
168
 */
169
function getExtractionRegion(
170
	factor: number,
171
	focalPoint: FocalPoint,
172
	target: Dimensions,
173
	intermediate: Dimensions,
174
): Region {
175
	const newXCenter = focalPoint.x / factor;
176
	const newYCenter = focalPoint.y / factor;
177

178
	return {
179
		left: clamp(Math.round(newXCenter - target.w / 2), 0, intermediate.w - target.w),
180
		top: clamp(Math.round(newYCenter - target.h / 2), 0, intermediate.h - target.h),
181
		width: target.w,
182
		height: target.h,
183
	};
184
}
185

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.