在 100 行代码内构建一个简单的图像缩放 API
在本教程中,我们将使用 ImageMagick
构建一个简单的图片大小调整 API,并将其部署到 Deno Deploy。此 API 将通过拉伸或裁剪来调整图片大小,但您也可以在以后添加更多功能。
ImageMagick
我们将在 API 中使用 ImageMagick
进行所有图片处理。它是最受欢迎的图片处理库之一,功能包括调整大小、格式转换、压缩和应用效果。具体来说,我们将使用 imagemagick_deno
,它是编译为 WebAssembly 并兼容 Deno 和 Deno Deploy 的 ImageMagick
版本。
设置服务器
首先,为您的项目创建一个目录。然后,在该文件夹中创建一个名为 main.ts
的新 TypeScript 文件。
在 main.ts
中,让我们使用 标准库的 http
模块 创建一个基本的 Web 服务器。
import { serve } from "https://deno.land/std@0.173.0/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/imagemagick_deno@0.0.14/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/std@0.175.0/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
您可以使用我们的多租户 JavaScript 无服务器云 Deno Deploy 将您的应用部署到边缘。
首先,创建一个包含 main.ts
的 GitHub 仓库。是的,它可以是一个单文件仓库。然后,访问 https://deno.org.cn/deploy 并连接您的 GitHub 账户。从 GitHub 创建一个新项目,并选择您刚刚创建的仓库。选择“GitHub Automatic”,这将在每次合并到 main
分支时进行部署。最后,您的入口文件应该是 main.ts
。
连接后,部署大约需要一分钟。一旦上线,您就可以访问您的 URL 了。
接下来是什么?
使用 Deno 和 ImageMagick 构建图片大小调整 API 不仅简单快速,而且可以在 100 行代码内完成。不仅如此,您还可以将此 API 全球部署在边缘,以最大限度地降低 API 消费者的延迟。您可以通过添加文本或将其保存到存储服务等方式来扩展更多功能。