用不到 100 行代码构建一个简单的图像调整大小 API
在本教程中,我们将使用 ImageMagick
构建一个简单的图像调整大小 API,并在 Deno Deploy 上部署它。该 API 将通过拉伸或裁剪来调整图像大小,尽管您以后可以添加更多功能。
ImageMagick
我们将使用 ImageMagick
来完成 API 中的所有图像操作。它是用于操作图像的最流行的库之一,包括调整大小、格式转换、压缩和效果应用。更具体地说,我们将使用 imagemagick_deno
,它将 ImageMagick
编译为 WebAssembly,并且与 Deno 和 Deno Deploy 兼容。
设置服务器
首先,为您的项目创建一个目录。然后,在该文件夹中创建一个名为 main.ts
的新 TypeScript 文件。
在 main.ts
中,让我们使用 std lib 的 http
模块 创建一个基本的 Web 服务器。
import { serve } from "https://deno.land/[email protected]/http/server.ts";
serve(
async (req: Request) => {
return new Response("Hello World!");
},
);
现在,您可以通过在该目录中打开一个终端并运行以下命令来运行此脚本
deno run --allow-net main.ts
现在,导航到 localhost:8000
。您应该会看到 Hello World!
。
操作图像
下一步是添加图像操作。在 main.ts
中,让我们从 imagemagick_deno
中导入一些内容
import {
ImageMagick,
initializeImageMagick,
MagickGeometry,
} from "https://deno.land/x/[email protected]/mod.ts";
然后,让我们初始化 ImageMagick
,它会为二进制文件及其 API 的工作设置必要的配置。
await initializeImageMagick();
接下来,我们将确认必要的参数存在并且有意义,否则我们将返回 400
(错误请求)错误代码。我们可以使用 searchParams
函数访问查询字符串参数。
让我们创建一个名为 parseParams
的新函数,它将
- 检查必要的参数
image
、height
和width
- 确保
height
和width
大于 0 且小于 2048 - 在错误时返回一个
string
function parseParams(reqUrl: URL) {
const image = reqUrl.searchParams.get("image");
if (image == null) {
return "Missing 'image' query parameter.";
}
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 {
image,
height,
width,
};
}
在我们的 serve()
函数中,我们可以访问查询字符串参数并调用 parseParams
// ...
serve(
async (req) => {
const reqURL = new URL(req.url);
const params = parseParams(reqURL);
if (typeof params === "string") {
return new Response(params, { status: 400 });
}
},
);
现在我们已经确保了必要参数的存在,让我们获取图像。让我们创建一个名为 getRemoteImage
的新函数,它将通过 URL fetch
图像,检查 mediaType
是否正确,并返回一个 buffer
和 mediaType
(或错误消息)。注意,我们需要在文件顶部导入 parseMediaType
。
import { parseMediaType } from "https://deno.land/[email protected]/media_types/parse_media_type.ts";
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,
};
}
然后,在我们的 serve()
函数中,在 parseParams
后,我们可以调用 getRemoteImage
并处理任何错误
// ...
serve(
async (req) => {
const reqURL = new URL(req.url);
const params = parseParams(reqURL);
if (typeof params === "string") {
return new Response(params, { status: 400 });
}
const remoteImage = await getRemoteImage(params.image);
if (remoteImage === "string") {
return new Response(remoteImage, { status: 400 });
}
},
);
接下来,我们终于可以使用 ImageMagick
修改图像了。让我们创建一个名为 modifyImage
的新函数,它将接受 imageBuffer
和 params
对象。
在此函数中,我们将使用 MagickGeometry
构造函数来设置调整大小的参数。此外,为了允许自适应调整大小,如果 height
或 width
缺失,我们将 ignoreAspectRatio
设置为 false
。
最后,此函数将返回一个 Promise
,它将解析为已转换的图像,作为一个 Uint8Array
缓冲区
function modifyImage(
imageBuffer: Uint8Array,
params: { width: number; height: number; mode: string },
) {
const sizingData = new MagickGeometry(
params.width,
params.height,
);
sizingData.ignoreAspectRatio = params.height > 0 && params.width > 0;
return new Promise<Uint8Array>((resolve) => {
ImageMagick.read(imageBuffer, (image) => {
image.resize(sizingData);
image.write((data) => resolve(data));
});
});
}
在 serve()
中,在我们调用 getRemoteImage
之后,让我们添加对 modifyImage
的调用。然后,我们可以返回一个新的 Response
,它包含 modifiedImage
// ...
serve(
async (req: Request) => {
const reqURL = new URL(req.url);
const params = parseParams(reqURL);
if (typeof params === "string") {
return new Response(params, { status: 400 });
}
const remoteImage = await getRemoteImage(params.image);
if (remoteImage === "string") {
return new Response(remoteImage, { status: 400 });
}
const modifiedImage = await modifyImage(remoteImage.buffer, params);
return new Response(modifiedImage, {
headers: {
"Content-Type": remoteImage.mediaType,
},
});
},
);
恭喜!您已经创建了一个用于调整图像大小的 API。
接下来,让我们在 API 中添加更多灵活性,并允许裁剪。
裁剪图像
标准调整大小在大多数情况下都能正常工作,但有一些情况下您可能希望裁剪图像。裁剪将允许您在更改纵横比时避免挤压图像,并删除图像中不需要的区域。
在 parseParams
函数中,让我们检查可接受的模式(“crop” 和 “resize”),并将 mode
添加到返回对象中
function parseParams(reqUrl: URL) {
const image = reqUrl.searchParams.get("image");
if (image == null) {
return "Missing 'image' query parameter.";
}
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}.`;
}
const mode = reqUrl.searchParams.get("mode") || "resize";
if (mode !== "resize" && mode !== "crop") {
return "Mode not accepted: please use 'resize' or 'crop'.";
}
return {
image,
height,
width,
mode,
};
}
然后,在 modifyImage
函数中,如果模式是 crop
,我们将使用 image.crop()
function modifyImage(
imageBuffer: Uint8Array,
params: { width: number; height: number; mode: "resize" | "crop" },
) {
const sizingData = new MagickGeometry(
params.width,
params.height,
);
sizingData.ignoreAspectRatio = params.height > 0 && params.width > 0;
return new Promise<Uint8Array>((resolve) => {
ImageMagick.read(imageBuffer, (image) => {
if (params.mode === "resize") {
image.resize(sizingData);
} else {
image.crop(sizingData);
}
image.write((data) => resolve(data));
});
});
}
就是这样!现在,如果您想尝试您的 API,只需像以前一样运行 deno run --allow-net main.ts
,并使用包含宽度、高度、图像 URL 和模式的 URL 访问它。例如,URL localhost:8000/?image=https://deno.land/images/artwork/deno_city.jpeg&width=500&height=500
应该会显示以下内容
您有一个可以工作的图像调整器!
部署到 Deno Deploy
您可以使用 Deno Deploy(我们的多租户 JavaScript 无服务器云)在边缘托管您的应用程序。
首先,创建一个包含 main.ts
的 GitHub 仓库。是的,它可以是一个单文件仓库。然后,访问 https://deno.org.cn/deploy 并连接您的 GitHub 帐户。从 GitHub 创建一个新项目,并选择您刚刚创建的仓库。选择“GitHub 自动”,这将每次合并到您的 main
分支时都进行部署。最后,您的入口点应该是 main.ts
。
连接后,它应该需要最多一分钟才能部署。部署完成后,您可以访问您的 URL。
下一步是什么?
使用 Deno 和 ImageMagick 构建一个图像调整大小 API 不仅简单快捷,而且可以在 100 行代码内完成。不仅如此,您还可以将此 API 全局托管在边缘,以最大程度地减少 API 消费者的延迟。您可以通过添加文本或将其保存到存储服务等功能来扩展此 API。