summaryrefslogtreecommitdiff
path: root/packages/integrations/image/src/loaders/squoosh.ts
blob: 455d476d83e5e263ab7e4f0bc8afc57da67c6888 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// @ts-ignore
import { red } from 'kleur/colors';
import { error } from '../utils/logger.js';
import { metadata } from '../utils/metadata.js';
import { isRemoteImage } from '../utils/paths.js';
import { processBuffer } from '../vendor/squoosh/image-pool.js';
import type { Operation } from '../vendor/squoosh/image.js';
import type { OutputFormat, TransformOptions } from './index.js';
import { BaseSSRService } from './index.js';

class SquooshService extends BaseSSRService {
	async processAvif(image: any, transform: TransformOptions) {
		const encodeOptions = transform.quality
			? { avif: { quality: transform.quality } }
			: { avif: {} };
		await image.encode(encodeOptions);
		const data = await image.encodedWith.avif;

		return {
			data: data.binary,
			format: 'avif' as OutputFormat,
		};
	}

	async processJpeg(image: any, transform: TransformOptions) {
		const encodeOptions = transform.quality
			? { mozjpeg: { quality: transform.quality } }
			: { mozjpeg: {} };
		await image.encode(encodeOptions);
		const data = await image.encodedWith.mozjpeg;

		return {
			data: data.binary,
			format: 'jpeg' as OutputFormat,
		};
	}

	async processPng(image: any, transform: TransformOptions) {
		await image.encode({ oxipng: {} });
		const data = await image.encodedWith.oxipng;

		return {
			data: data.binary,
			format: 'png' as OutputFormat,
		};
	}

	async processWebp(image: any, transform: TransformOptions) {
		const encodeOptions = transform.quality
			? { webp: { quality: transform.quality } }
			: { webp: {} };
		await image.encode(encodeOptions);
		const data = await image.encodedWith.webp;

		return {
			data: data.binary,
			format: 'webp' as OutputFormat,
		};
	}

	async autorotate(
		transform: TransformOptions,
		inputBuffer: Buffer
	): Promise<Operation | undefined> {
		// check EXIF orientation data and rotate the image if needed
		try {
			const meta = await metadata(transform.src, inputBuffer);

			switch (meta?.orientation) {
				case 3:
				case 4:
					return { type: 'rotate', numRotations: 2 };
				case 5:
				case 6:
					return { type: 'rotate', numRotations: 1 };
				case 7:
				case 8:
					return { type: 'rotate', numRotations: 3 };
			}
		} catch {}
	}

	async transform(inputBuffer: Buffer, transform: TransformOptions) {
		const operations: Operation[] = [];

		if (!isRemoteImage(transform.src)) {
			const autorotate = await this.autorotate(transform, inputBuffer);

			if (autorotate) {
				operations.push(autorotate);
			}
		}

		if (transform.width || transform.height) {
			const width = transform.width && Math.round(transform.width);
			const height = transform.height && Math.round(transform.height);

			operations.push({
				type: 'resize',
				width,
				height,
			});
		}

		if (!transform.format) {
			error({
				level: 'info',
				prefix: false,
				message: red(`Unknown image output: "${transform.format}" used for ${transform.src}`),
			});
			throw new Error(`Unknown image output: "${transform.format}" used for ${transform.src}`);
		}

		const data = await processBuffer(inputBuffer, operations, transform.format, transform.quality);

		return {
			data: Buffer.from(data),
			format: transform.format,
		};
	}
}

const service = new SquooshService();

export default service;