142 lines
3.7 KiB
TypeScript
Raw Normal View History

2023-07-26 13:47:01 +02:00
import { HandlerContext } from "$fresh/server.ts";
2023-07-26 15:48:03 +02:00
import {
ImageMagick,
initializeImageMagick,
MagickGeometry,
} from "https://deno.land/x/imagemagick_deno@0.0.14/mod.ts";
import { parseMediaType } from "https://deno.land/std@0.175.0/media_types/parse_media_type.ts";
import * as cache from "@lib/cache/image.ts";
2023-07-26 13:47:01 +02:00
2023-07-26 15:48:03 +02:00
await initializeImageMagick();
async function getRemoteImage(image: string) {
const sourceRes = await fetch(image);
if (!sourceRes.ok) {
return "Error retrieving image from URL.";
}
const mediaType = parseMediaType(sourceRes.headers.get("Content-Type")!)[0];
if (mediaType.split("/")[0] !== "image") {
return "URL is not image type.";
}
return {
buffer: new Uint8Array(await sourceRes.arrayBuffer()),
mediaType,
};
}
function getWidthHeight(
current: { width: number; height: number },
final: { width: number; height: number },
) {
const ratio = (current.width / final.width) > (current.height / final.height)
? (current.height / final.height)
: (current.width / final.width);
return new MagickGeometry(
current.width / ratio,
current.height / ratio,
);
}
function modifyImage(
imageBuffer: Uint8Array,
params: { width: number; height: number; mode: "resize" | "crop" },
) {
return new Promise<Uint8Array>((resolve) => {
ImageMagick.read(imageBuffer, (image) => {
const sizingData = getWidthHeight(image, params);
if (params.mode === "resize") {
image.resize(sizingData);
} else {
image.crop(sizingData);
}
image.write((data) => resolve(data));
});
});
}
function parseParams(reqUrl: URL) {
const image = reqUrl.searchParams.get("image")?.replace(/^\//, "");
if (image == null) {
return "Missing 'image' query parameter.";
}
2023-07-26 15:48:03 +02:00
const height = Number(reqUrl.searchParams.get("height")) || 0;
const width = Number(reqUrl.searchParams.get("width")) || 0;
if (height === 0 && width === 0) {
//return "Missing non-zero 'height' or 'width' query parameter.";
}
if (height < 0 || width < 0) {
return "Negative height or width is not supported.";
2023-07-26 13:47:01 +02:00
}
2023-07-26 15:48:03 +02:00
const maxDimension = 2048;
if (height > maxDimension || width > maxDimension) {
return `Width and height cannot exceed ${maxDimension}.`;
}
return {
image,
2023-07-26 15:48:03 +02:00
height,
width,
};
}
2023-07-26 13:47:01 +02:00
export const handler = async (
_req: Request,
_ctx: HandlerContext,
): Promise<Response> => {
2023-07-26 15:48:03 +02:00
const url = new URL(_req.url);
const params = parseParams(url);
if (typeof params === "string") {
return new Response(params, { status: 400 });
}
const imageUrl = Deno.env.get("SILVERBULLET_SERVER") + "/" + params.image;
2023-07-30 18:32:21 +02:00
console.log("[api/image] " + imageUrl);
const cachedResponse = await cache.getImage({
url: imageUrl,
width: params.width,
height: params.height,
});
2023-07-26 16:19:28 +02:00
if (cachedResponse) {
2023-07-30 18:32:21 +02:00
console.log("[api/image] got cached image");
return new Response(cachedResponse.buffer.slice(), {
headers: {
"Content-Type": cachedResponse.mediaType,
},
});
2023-07-30 18:32:21 +02:00
} else {
console.log("[api/image] no image in cache");
2023-07-26 15:48:03 +02:00
}
2023-07-26 15:55:00 +02:00
const remoteImage = await getRemoteImage(imageUrl);
if (typeof remoteImage === "string") {
2023-07-30 18:32:21 +02:00
console.log("[api/image] ERROR " + remoteImage);
2023-07-26 15:55:00 +02:00
return new Response(remoteImage, { status: 400 });
}
const modifiedImage = await modifyImage(remoteImage.buffer, {
...params,
mode: "resize",
});
2023-07-26 15:48:03 +02:00
2023-07-30 18:32:21 +02:00
console.log("[api/image] resized image", { imageUrl });
cache.setImage(modifiedImage, {
url: imageUrl,
width: params.width,
height: params.height,
mediaType: remoteImage.mediaType,
});
2023-07-26 15:48:03 +02:00
2023-07-30 18:32:21 +02:00
console.log("[api/image] stored image in cache");
return new Response(modifiedImage, {
headers: {
"Content-Type": remoteImage.mediaType,
},
});
2023-07-26 13:47:01 +02:00
};