@npio/internals
Version:
A free visual website editor, powered with your own SolidJS components.
128 lines (112 loc) • 2.78 kB
text/typescript
import { Sharp } from "sharp";
import { clamp } from "../../number";
import {
getImageOptimizationTarget,
getProportionalCropTarget,
} from "../../string";
export type Dimensions = {
width: number;
height: number;
};
export type Region = Dimensions & {
left: number;
top: number;
};
type FocalPoint = { x: number; y: number };
// FocalPoint cropping based on https://github.com/lovell/sharp/issues/2740#issuecomment-1279560949
export const crop = async function (params: {
sharp: Sharp;
size: number;
aspectRatio?: string;
dpr?: number;
focalPoint?: FocalPoint;
}) {
const { sharp, size, aspectRatio, dpr } = params;
const metadata = await sharp.metadata();
if (metadata.width == null || metadata.height == null) {
throw new Error("Cannot resize image without dimensions");
}
const original = {
width: metadata.width,
height: metadata.height,
};
const rawTarget = getImageOptimizationTarget({
original,
size,
aspectRatio,
dpr,
});
const target = getProportionalCropTarget(original, rawTarget);
const focalPoint = params.focalPoint || { x: 0.5, y: 0.5 };
const { width, height, factor } = getResizeOptions({
original,
target,
});
const region = getExtractRegion({
focalPoint: {
x: Math.round((original.width * focalPoint.x) / factor),
y: Math.round((original.height * focalPoint.y) / factor),
},
target,
intermediate: {
width,
height,
},
});
return sharp
.rotate()
.resize({
fit: "cover",
width,
height,
withoutEnlargement: true,
})
.extract(region);
};
const getResizeOptions = ({
original,
target,
}: {
original: Dimensions;
target: Dimensions;
}): { width: number; height: number; factor: number } => {
const heightRatio = original.height / target.height;
const widthRatio = original.width / target.width;
let factor: number;
let width: number;
let height: number;
if (heightRatio < widthRatio) {
factor = heightRatio;
height = Math.round(target.height);
width = Math.round(original.width / factor);
} else {
factor = widthRatio;
width = Math.round(target.width);
height = Math.round(original.height / factor);
}
return { width, height, factor };
};
const getExtractRegion = ({
focalPoint,
target,
intermediate,
}: {
focalPoint: FocalPoint;
target: Dimensions;
intermediate: Dimensions;
}): Region => {
return {
left: clamp(
Math.round(focalPoint.x - target.width / 2),
0,
intermediate.width - target.width,
),
top: clamp(
Math.round(focalPoint.y - target.height / 2),
0,
intermediate.height - target.height,
),
width: target.width,
height: target.height,
};
};