2022-07-01 15:47:48 +00:00
|
|
|
import sharp from 'sharp';
|
2022-09-07 17:22:11 +00:00
|
|
|
import { isAspectRatioString, isColor, isOutputFormat } from '../loaders/index.js';
|
2022-07-27 15:41:22 +00:00
|
|
|
import type { OutputFormat, SSRImageService, TransformOptions } from './index.js';
|
2022-07-01 15:47:48 +00:00
|
|
|
|
2022-07-01 17:43:26 +00:00
|
|
|
class SharpService implements SSRImageService {
|
2022-07-01 15:47:48 +00:00
|
|
|
async getImageAttributes(transform: TransformOptions) {
|
2022-07-08 21:37:55 +00:00
|
|
|
// strip off the known attributes
|
2022-09-07 17:22:11 +00:00
|
|
|
const { width, height, src, format, quality, aspectRatio, background, ...rest } = transform;
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
return {
|
|
|
|
...rest,
|
|
|
|
width: width,
|
2022-07-01 17:43:26 +00:00
|
|
|
height: height,
|
|
|
|
};
|
2022-07-01 15:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
serializeTransform(transform: TransformOptions) {
|
|
|
|
const searchParams = new URLSearchParams();
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (transform.quality) {
|
|
|
|
searchParams.append('q', transform.quality.toString());
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (transform.format) {
|
|
|
|
searchParams.append('f', transform.format);
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (transform.width) {
|
|
|
|
searchParams.append('w', transform.width.toString());
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (transform.height) {
|
|
|
|
searchParams.append('h', transform.height.toString());
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (transform.aspectRatio) {
|
|
|
|
searchParams.append('ar', transform.aspectRatio.toString());
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-09-07 17:22:11 +00:00
|
|
|
if (transform.background) {
|
|
|
|
searchParams.append('bg', transform.background);
|
|
|
|
}
|
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
return { searchParams };
|
|
|
|
}
|
|
|
|
|
|
|
|
parseTransform(searchParams: URLSearchParams) {
|
|
|
|
let transform: TransformOptions = { src: searchParams.get('href')! };
|
|
|
|
|
|
|
|
if (searchParams.has('q')) {
|
|
|
|
transform.quality = parseInt(searchParams.get('q')!);
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (searchParams.has('f')) {
|
|
|
|
const format = searchParams.get('f')!;
|
|
|
|
if (isOutputFormat(format)) {
|
|
|
|
transform.format = format;
|
|
|
|
}
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (searchParams.has('w')) {
|
|
|
|
transform.width = parseInt(searchParams.get('w')!);
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (searchParams.has('h')) {
|
|
|
|
transform.height = parseInt(searchParams.get('h')!);
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (searchParams.has('ar')) {
|
|
|
|
const ratio = searchParams.get('ar')!;
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (isAspectRatioString(ratio)) {
|
|
|
|
transform.aspectRatio = ratio;
|
|
|
|
} else {
|
|
|
|
transform.aspectRatio = parseFloat(ratio);
|
|
|
|
}
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-09-07 17:22:11 +00:00
|
|
|
if (searchParams.has('bg')) {
|
|
|
|
const background = searchParams.get('bg')!;
|
|
|
|
if (isColor(background)) {
|
|
|
|
transform.background = background;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
return transform;
|
|
|
|
}
|
|
|
|
|
|
|
|
async transform(inputBuffer: Buffer, transform: TransformOptions) {
|
2022-08-05 22:32:45 +00:00
|
|
|
const sharpImage = sharp(inputBuffer, { failOnError: false, pages: -1 });
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-22 23:01:56 +00:00
|
|
|
// always call rotate to adjust for EXIF data orientation
|
|
|
|
sharpImage.rotate();
|
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (transform.width || transform.height) {
|
2022-07-01 19:56:43 +00:00
|
|
|
const width = transform.width && Math.round(transform.width);
|
|
|
|
const height = transform.height && Math.round(transform.height);
|
|
|
|
sharpImage.resize(width, height);
|
2022-07-01 15:47:48 +00:00
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-09-07 17:22:11 +00:00
|
|
|
// remove alpha channel and replace with background color if requested
|
|
|
|
if (transform.background) {
|
|
|
|
sharpImage.flatten({ background: transform.background });
|
|
|
|
}
|
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
if (transform.format) {
|
|
|
|
sharpImage.toFormat(transform.format, { quality: transform.quality });
|
|
|
|
}
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
const { data, info } = await sharpImage.toBuffer({ resolveWithObject: true });
|
2022-07-01 17:43:26 +00:00
|
|
|
|
2022-07-01 15:47:48 +00:00
|
|
|
return {
|
|
|
|
data,
|
|
|
|
format: info.format as OutputFormat,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-08 18:41:17 +00:00
|
|
|
const service: SSRImageService = new SharpService();
|
2022-07-01 15:47:48 +00:00
|
|
|
|
|
|
|
export default service;
|