import { HandlerContext } from "$fresh/server.ts"; 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.ts"; 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((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 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."; } const maxDimension = 2048; if (height > maxDimension || width > maxDimension) { return `Width and height cannot exceed ${maxDimension}.`; } return { height, width, }; } async function getImageResponse( remoteImage: { buffer: Uint8Array; mediaType: string }, params: { width: number; height: number }, ): Promise { const modifiedImage = await modifyImage(remoteImage.buffer, { ...params, mode: "resize", }); const response = new Response(modifiedImage, { headers: { "Content-Type": remoteImage.mediaType, }, }); return response; } export const handler = async ( _req: Request, _ctx: HandlerContext, ): Promise => { const imageUrl = Deno.env.get("SILVERBULLET_SERVER") + "/Recipes/images/" + _ctx.params.image; const url = new URL(_req.url); const params = parseParams(url); if (typeof params === "string") { return new Response(params, { status: 400 }); } const imageId = `${imageUrl}.${params.width}.${params.height}`; const cachedResponse = await cache.get(imageId); if (cachedResponse) { return (await cachedResponse).clone(); } const remoteImage = await getRemoteImage(imageUrl); if (typeof remoteImage === "string") { return new Response(remoteImage, { status: 400 }); } const response = getImageResponse(remoteImage, params); await cache.set(imageId, response); return response; };