给URL加上参数,图片就能显示文字?

在写博客的过程中,我时常会遇到一些很有意思的现象。前不久我偶然发现有个网站,只要在图片链接后面加上一个参数,图片上就会立刻显示出对应的文字。比如在 URL 中写上 ?text=Hello,返回的图片就会带着"Hello"字样,简直像是有人在后台帮我随时做了一张新图片一样。这个小小的发现勾起了我的好奇心,我想弄清楚这背后的原理,并且自己动手复现一个类似的功能。于是,我开始了这段探索之旅。

起初,我的直觉告诉我,这绝对不是一张提前存好的静态图片,而是一种动态生成的结果。毕竟文字是随参数变化的,肯定要经过实时处理。那么要么是服务器在接收到请求时临时绘制一张图片并返回,要么就是返回了一个 SVG 矢量文件,浏览器把它当作图片来渲染。

在我脑海里,整个过程大概就是这么运转的:用户在浏览器中请求一个带参数的 URL,服务器收到参数后通过代码逻辑生成对应的图片,把文字渲染上去,再以 PNG 或 SVG 的形式返回给浏览器。看似只是一个小功能,但里面其实隐藏了很多有意思的技术点,比如服务器端如何处理图片绘制、不同语言的图形库如何选择、返回格式对性能的影响等等。

带着这样的思路,我决定先尝试自己写一个最小化的实现。我主要用 Node.js 比较多,所以自然想到用 Node 来搭建一个小服务。Node 有个非常好用的库叫做 canvas,它能够模拟浏览器里的 Canvas API,让我们可以在服务端生成带有图形和文字的 PNG 图片。我的计划就是:搭建一个最简单的 Express 服务,监听请求,把 text 参数解析出来,然后调用 canvas 绘制文字,最后返回 PNG 数据。听起来很直接,但真正实现起来还是遇到了一些小问题。

我先写了个基础框架,创建一个 server.js 文件:

js 复制代码
const express = require('express');
const { createCanvas } = require('canvas');

const app = express();
const port = 3000;

app.get('/image', (req, res) => {
  const text = req.query.text || 'Hello World';

  // 创建画布
  const canvas = createCanvas(600, 200);
  const ctx = canvas.getContext('2d');

  // 设置背景
  ctx.fillStyle = '#f0f0f0';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // 设置文字样式
  ctx.fillStyle = '#333';
  ctx.font = '30px Arial';
  ctx.fillText(text, 50, 100);

  // 输出为 PNG
  res.setHeader('Content-Type', 'image/png');
  canvas.pngStream().pipe(res);
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

这一段代码其实就是最核心的流程。首先我用 createCanvas 创建了一张画布,指定宽高,然后拿到 2D 上下文 ctx 来绘制内容。背景我随便填了个灰色,接着设置了字体和颜色,把用户传过来的 text 绘制在画布上。最后,我通过设置 Content-Typeimage/png,再把画布流返回给响应,这样浏览器拿到的就是一张真正的 PNG 图片。等我在浏览器里访问 http://localhost:3000/image?text=测试 时,页面果然显示出了一张图片,上面写着"测试"两个字。第一次成功的时候,我还挺有成就感的。

不过问题很快来了。我发现返回的字体有点奇怪,显示出来的中文字体很难看,似乎没有加载到合适的字体文件。原来在服务端使用 canvas 时,它并不会自动读取系统的字体配置,需要我们自己手动指定。于是我查了下文档,发现可以用 registerFont 方法引入指定的字体。于是我在代码里加上:

js 复制代码
const { createCanvas, registerFont } = require('canvas');
registerFont('C:/Windows/Fonts/msyh.ttc', { family: 'Microsoft YaHei' });

这样一来,我就能在绘制时写成 ctx.font = '30px "Microsoft YaHei"',中文就会变得好看很多。当然这个路径在 Linux 或 Mac 上是不一样的,如果要做成通用服务,最好把字体文件打包在项目里,直接读取相对路径,这样就不会依赖系统字体了。

接着我又想到,既然能绘制文字,那是不是也可以加入一些别的元素,比如自动调整文字大小,居中显示,甚至加个边框或者渐变背景。于是我在 canvas 上玩了一番。比如渐变背景的实现可以这么写:

js 复制代码
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, '#ff7e5f');
gradient.addColorStop(1, '#feb47b');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);

这样返回的图片就不再是单调的灰色背景,而是一个左右渐变的橙色背景。再搭配上黑色的文字,效果瞬间就丰富多了。我还试着用 ctx.strokeRect 给图片加上边框,感觉这个功能如果要用在生成个性化海报、临时封面图或者验证码之类的场景,会很有趣。

随着功能一点点增加,我觉得应该给整个项目的结构梳理一下,否则以后维护起来会很混乱。

结构其实非常简单,核心就是一个 server.js 文件,里面依赖 express 来做 HTTP 服务,依赖 canvas 来做绘图。额外的资源就是字体文件,保证文字显示的美观。这样一来,整个项目看上去就清晰明了了。

在实现过程中,我也顺便回顾了一些和 HTTP 相关的知识点。比如 Content-Type 头设置的重要性,如果不写成 image/png,浏览器可能会把它当作普通文本来渲染,结果一堆乱码。还有 URL 参数的解析,Express 已经帮我处理好了 req.query.text,如果要扩展成更多参数,比如 colorsizebg 等,其实只要多读取几个参数就可以了。这让我意识到,这种动态生成图片的功能完全可以做成一个小 API 服务,任何人只要传不同参数,就能得到个性化的图片。

我甚至想到了很多有趣的用途:比如给论坛或博客生成动态的签名图,每次显示不同的心情语录;给短链接服务加上一个临时生成的预览图;或者在前端项目里快速做一个带文字的占位图,省得自己再去用 Photoshop 画一张。

相关推荐
繁依Fanyi5 小时前
我把“Word 一键转 PDF”做成了一件顺手的小工具
后端
桦说编程7 小时前
使用注解写出更优雅的代码,以CFFU为例
java·后端·函数式编程
悟空聊架构8 小时前
一次Feign超时引发的血案:生产环境故障排查全记录
运维·后端·架构
一行•坚书8 小时前
Redisson分布式锁会发生死锁问题吗?怎么发生的?
java·分布式·后端
野犬寒鸦9 小时前
力扣hot100:矩阵置零(73)(原地算法)
java·数据结构·后端·算法
fleur9 小时前
关于xxl-job的一些使用小感悟
后端
京东零售技术9 小时前
理论到实战,高可用架构踩坑说明书
后端
SimonKing9 小时前
你的图片又被别人“白嫖”了?用这篇Java防盗链攻略说再见!
java·后端·程序员
Olaf_n9 小时前
SpringBoot中的监听机制
后端