chore: better local image procession for crop image with both width and height.

This commit is contained in:
Yufan Sheng 2024-06-21 01:09:13 +08:00
parent 6b2211ba85
commit 42776cf8b0
Signed by: syhily
GPG Key ID: 9D18A22A7DCD5A9B
2 changed files with 92 additions and 0 deletions

View File

@ -15,6 +15,7 @@ export default defineConfig({
},
image: {
domains: ['localhost', '127.0.0.1'],
service: !options.isProd() ? { entrypoint: './plugins/resize', config: {} } : undefined,
},
experimental: {
env: {

91
plugins/resize.ts Normal file
View File

@ -0,0 +1,91 @@
import type { ImageOutputFormat, ImageQualityPreset, LocalImageService } from 'astro';
import { baseService } from 'astro/assets';
const qualityTable: Record<ImageQualityPreset, number> = {
low: 25,
mid: 50,
high: 80,
max: 100,
};
const parseQuality = (quality: string): string | number => {
const result = Number.parseInt(quality);
if (Number.isNaN(result)) {
return quality;
}
return result;
};
type BaseServiceTransform = {
src: string;
width?: number;
height?: number;
format: string;
quality?: string | null;
};
interface SharpImageServiceConfig {
/**
* The `limitInputPixels` option passed to Sharp. See https://sharp.pixelplumbing.com/api-constructor for more information
*/
limitInputPixels?: import('sharp').SharpOptions['limitInputPixels'];
}
const imageService: LocalImageService<SharpImageServiceConfig> = {
getURL: baseService.getURL,
getSrcSet: baseService.getSrcSet,
getHTMLAttributes: baseService.getHTMLAttributes,
validateOptions: baseService.validateOptions,
parseURL: baseService.parseURL,
async transform(inputBuffer, transformOptions, config) {
const { default: sharp } = await import('sharp');
const transform: BaseServiceTransform = transformOptions as BaseServiceTransform;
// Sharp has some support for SVGs, we could probably support this once Sharp is the default and only service.
if (transform.format === 'svg') return { data: inputBuffer, format: 'svg' };
const result = sharp(inputBuffer, {
failOnError: false,
pages: -1,
limitInputPixels: config.service.config.limitInputPixels,
});
result.rotate();
// Never resize using both width and height at the same time, prioritizing width.
if (transform.height) {
if (!transform.width) {
result.resize({ height: Math.round(transform.height) });
} else {
// Allow the width and height to be set.
result.resize({ width: Math.round(transform.width), height: Math.round(transform.height) });
}
} else if (transform.width) {
result.resize({ width: Math.round(transform.width) });
}
if (transform.format) {
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 ? qualityTable[transform.quality] : undefined;
}
}
result.toFormat(transform.format as keyof import('sharp').FormatEnum, { quality: quality });
}
const { data, info } = await result.toBuffer({ resolveWithObject: true });
return {
data: data,
format: info.format as ImageOutputFormat,
};
},
};
export default imageService;