88 lines
2.3 KiB
TypeScript
88 lines
2.3 KiB
TypeScript
export type Area = {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
|
|
function loadImage(src: string): Promise<HTMLImageElement> {
|
|
return new Promise((resolve, reject) => {
|
|
const image = new Image();
|
|
image.addEventListener("load", () => resolve(image));
|
|
image.addEventListener("error", (error) => reject(error));
|
|
image.crossOrigin = "anonymous";
|
|
image.src = src;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Crop that may extend outside the source image.
|
|
* Outside pixels stay transparent (PNG) or fillColor (JPEG).
|
|
*/
|
|
export async function getCroppedImageFile(
|
|
imageSrc: string,
|
|
pixelCrop: Area,
|
|
fileName: string,
|
|
mimeType = "image/jpeg",
|
|
quality = 0.92,
|
|
fillColor = "#ffffff",
|
|
): Promise<File> {
|
|
const image = await loadImage(imageSrc);
|
|
const canvas = document.createElement("canvas");
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) throw new Error("Could not crop image.");
|
|
|
|
const outputWidth = Math.max(1, Math.round(pixelCrop.width));
|
|
const outputHeight = Math.max(1, Math.round(pixelCrop.height));
|
|
canvas.width = outputWidth;
|
|
canvas.height = outputHeight;
|
|
|
|
if (mimeType !== "image/png") {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fillRect(0, 0, outputWidth, outputHeight);
|
|
} else {
|
|
ctx.clearRect(0, 0, outputWidth, outputHeight);
|
|
}
|
|
|
|
const srcX = Math.max(0, pixelCrop.x);
|
|
const srcY = Math.max(0, pixelCrop.y);
|
|
const srcRight = Math.min(image.naturalWidth, pixelCrop.x + pixelCrop.width);
|
|
const srcBottom = Math.min(
|
|
image.naturalHeight,
|
|
pixelCrop.y + pixelCrop.height,
|
|
);
|
|
const srcWidth = srcRight - srcX;
|
|
const srcHeight = srcBottom - srcY;
|
|
|
|
if (srcWidth > 0 && srcHeight > 0) {
|
|
const destX = Math.round(srcX - pixelCrop.x);
|
|
const destY = Math.round(srcY - pixelCrop.y);
|
|
ctx.drawImage(
|
|
image,
|
|
srcX,
|
|
srcY,
|
|
srcWidth,
|
|
srcHeight,
|
|
destX,
|
|
destY,
|
|
srcWidth,
|
|
srcHeight,
|
|
);
|
|
}
|
|
|
|
const blob = await new Promise<Blob>((resolve, reject) => {
|
|
canvas.toBlob(
|
|
(result) => {
|
|
if (result) resolve(result);
|
|
else reject(new Error("Could not export cropped image."));
|
|
},
|
|
mimeType,
|
|
quality,
|
|
);
|
|
});
|
|
|
|
const base = fileName.replace(/\.[^.]+$/, "") || "image";
|
|
const extension = mimeType === "image/png" ? "png" : "jpg";
|
|
return new File([blob], `${base}-cropped.${extension}`, { type: mimeType });
|
|
}
|