-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathservice.ts
119 lines (103 loc) · 3.12 KB
/
service.ts
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
import type {
ImageOutputFormat,
ImageQualityPreset,
LocalImageService,
} from "astro";
import { baseService } from "astro/assets";
import { imageMetadata } from "astro/assets/utils";
import { processBuffer } from "./vendor/squoosh/image-pool.js";
import type { Operation } from "./vendor/squoosh/image.js";
function parseQuality(quality: string): string | number {
const result = parseInt(quality);
if (Number.isNaN(result)) {
return quality;
}
return result;
}
const baseQuality = { low: 25, mid: 50, high: 80, max: 100 };
const qualityTable: Record<
Exclude<ImageOutputFormat, "png" | "svg">,
Record<ImageQualityPreset, number>
> = {
avif: {
// Squoosh's AVIF encoder has a bit of a weird behavior where `62` is technically the maximum, and anything over is overkill
max: 62,
high: 45,
mid: 35,
low: 20,
},
jpeg: baseQuality,
jpg: baseQuality,
webp: baseQuality,
// Squoosh's PNG encoder does not support a quality setting, so we can skip that here
};
async function getRotationForEXIF(
inputBuffer: Uint8Array,
src?: string,
): Promise<Operation | undefined> {
const meta = await imageMetadata(inputBuffer, src);
if (!meta) return undefined;
// EXIF orientations are a bit hard to read, but the numbers are actually standard. See https://exiftool.org/TagNames/EXIF.html for a list.
// Various illustrations can also be found online for a more graphic representation, it's a bit old school.
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 };
default:
return undefined;
}
}
const service: LocalImageService = {
validateOptions: baseService.validateOptions,
getURL: baseService.getURL,
parseURL: baseService.parseURL,
getHTMLAttributes: baseService.getHTMLAttributes,
getSrcSet: baseService.getSrcSet,
async transform(inputBuffer, transformOptions) {
const transform = transformOptions;
const format = transform.format;
// Return SVGs as-is
if (format === "svg") return { data: inputBuffer, format: "svg" };
const operations: Operation[] = [];
const rotation = await getRotationForEXIF(inputBuffer, transform.src);
if (rotation) {
operations.push(rotation);
}
// Never resize using both width and height at the same time, prioritizing width.
if (transform.height && !transform.width) {
operations.push({
type: "resize",
height: Math.round(transform.height),
});
} else if (transform.width) {
operations.push({
type: "resize",
width: Math.round(transform.width),
});
}
let quality: number | string | undefined = undefined;
if (transform.quality) {
const parsedQuality = parseQuality(transform.quality);
if (typeof parsedQuality === "number") {
quality = parsedQuality;
} else {
quality =
transform.quality in qualityTable[format]
? qualityTable[format][transform.quality]
: undefined;
}
}
const data = await processBuffer(inputBuffer, operations, format, quality);
return {
data: Buffer.from(data),
format: format,
};
},
};
export default service;