feat: add image resizing
This commit is contained in:
@ -1,26 +1,128 @@
|
||||
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";
|
||||
|
||||
function copyHeader(headerName: string, to: Headers, from: Headers) {
|
||||
const hdrVal = from.get(headerName);
|
||||
if (hdrVal) {
|
||||
to.set(headerName, hdrVal);
|
||||
await initializeImageMagick();
|
||||
|
||||
const cache = new Map<string, Promise<Response>>();
|
||||
|
||||
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 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(
|
||||
imageUrl: string,
|
||||
remoteImage: { buffer: Uint8Array; mediaType: string },
|
||||
params: { width: number; height: number },
|
||||
): Promise<Response> {
|
||||
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<Response> => {
|
||||
const proxyRes = await fetch(
|
||||
"http://192.168.178.56:3007/Recipes/images/" + _ctx.params.image,
|
||||
);
|
||||
console.log({ params: _ctx.params });
|
||||
const headers = new Headers();
|
||||
copyHeader("content-length", headers, proxyRes.headers);
|
||||
copyHeader("content-type", headers, proxyRes.headers);
|
||||
copyHeader("content-disposition", headers, proxyRes.headers);
|
||||
return new Response(proxyRes.body, {
|
||||
status: proxyRes.status,
|
||||
headers,
|
||||
});
|
||||
const imageUrl = "http://192.168.178.56:3007/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 remoteImage = await getRemoteImage(imageUrl);
|
||||
if (typeof remoteImage === "string") {
|
||||
return new Response(remoteImage, { status: 400 });
|
||||
}
|
||||
|
||||
const imageId = `${imageUrl}.${params.width}.${params.height}`;
|
||||
|
||||
if (cache.has(imageId)) {
|
||||
return (await cache.get(imageId)!).clone();
|
||||
}
|
||||
|
||||
const response = getImageResponse(imageUrl, remoteImage, params);
|
||||
|
||||
cache.set(imageId, response);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
@ -16,10 +16,10 @@ export default function Greet(props: PageProps<Recipe>) {
|
||||
return (
|
||||
<MainLayout>
|
||||
<RecipeHero recipe={props.data} />
|
||||
<div class="px-12 text-white mt-10">
|
||||
<h3 class="text-3xl">Ingredients</h3>
|
||||
<div class="px-8 text-white mt-10">
|
||||
<h3 class="text-3xl my-5">Ingredients</h3>
|
||||
<IngredientsList ingredients={props.data.ingredients} />
|
||||
<h3 class="text-3xl">Preperation</h3>
|
||||
<h3 class="text-3xl my-5">Preparation</h3>
|
||||
</div>
|
||||
</MainLayout>
|
||||
);
|
||||
|
Reference in New Issue
Block a user