resize-img-api/index.ts

100 lines
3.4 KiB
TypeScript
Raw Normal View History

2024-03-25 10:17:19 +01:00
import { Hono } from "hono";
import { cors } from "hono/cors";
import { etag } from "hono/etag";
import { logger } from "hono/logger";
import { prettyJSON } from "hono/pretty-json";
import { html } from "hono/html";
import sharp from "sharp";
const homepage = html`
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charSet="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="og:type" content="website" />
<title>Resize Images API</title>
<meta name="description" content="Resize images via API">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" />
</head>
<body>
<main class="container">
<h1>Resize images API</h1>
<style>
h1 {
margin-top: 2rem;
margin-bottom: 3rem;
text-align: center;
}
</style>
<article style="max-width: 600px; margin-left: auto; margin-right: auto; padding: 3rem 2rem;">
<p>The structure of the API path is:</p>
<p><pre>/api/imgresize/:width/:height/:url</pre></p>
<p>Where <code>width</code> and <code>height</code> can be numbers in pixels or <code>auto</code>.</p>
<p>The URL should be URL encoded, which can be done in JS with <code>encodeURIComponent</code>. See <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent" target="_blank" rel="noopener noreferrer">MDN ref</a>.</p>
<p>Example for <a href="https://memori.ai/logo.png" target="_blank" rel="noopener noreferrer">https://memori.ai/logo.png</a>:</p>
<p><pre>https%3A%2F%2Fmemori.ai%2Flogo.png</pre></p>
<p>Then call the API as, for example:</p>
<p><pre>/api/imgresize/200/200/https%3A%2F%2Fmemori.ai%2Flogo.png</pre></p>
<p>You can also specify a format using the querystring <code>?format=</code> and indicating one of the following: avif, gif, heif, jpeg, jpg, jp2, pdf, png, svg, tiff, webp.</p>
</article>
</main>
</body>
</html>
`;
const app = new Hono();
app.use(prettyJSON());
app.use(etag(), logger());
app.use("/api/*", cors());
app.get("/", (c) => {
return c.html(homepage);
});
app.get("/api/imgresize/:width/:height/:url", async (c) => {
const { width, height, url } = c.req.param();
const format = c.req.query("format");
c.header("Cache-Control", "s-maxage=31536000, stale-while-revalidate");
c.header("Content-Type", `image/jpeg`);
const decodedUrl = decodeURIComponent(url as string);
const readStream = await fetch(decodedUrl, { cache: "no-cache" });
const input = Buffer.from(await readStream.arrayBuffer());
const w = width && width !== "auto" ? Number(width) : undefined;
const h = height && height !== "auto" ? Number(height) : undefined;
const outputBuffer = await sharp(input)
// outputBuffer contains JPEG image data
// no wider and no higher than w and h pixels
// and no larger than the input image
.resize({
width: w,
height: h,
fit: sharp.fit.inside,
withoutEnlargement: true,
})
.toFormat(
format && sharp.format[format as keyof typeof sharp.format] !== undefined
? sharp.format[format as keyof typeof sharp.format]
: sharp.format.jpeg
)
.toBuffer();
c.status(200);
return c.body(outputBuffer);
});
export default {
port: Bun.env.PORT || 8787,
fetch: app.fetch,
};