阅读 425

🦀 详解一键生成邀请海报工具 | Rust 与 WebAssembly

作者:Sailing and 夏歌

项目背景:用 Rust 与 WebAssembly 为 Rust China Conf 做了一个快速生成海报的工具,在线体验

插播一条消息 👉 用 Serverless 部署 TensorFlow AI 推理函数,就可以获得 Rust 搪瓷杯、Serverless 口罩与贴纸的活动正在进行中,欢迎大家前来参加


以下为正文,Enjoy!🦀

在办各种技术大会的时候,主办方都会给参与者制作带有个人名字的邀请海报,以显示对参会者的重视,同时也希望达到最大范围的传播。

邀请海报一般的工作流是:

  • 设计确定好海报样式;
  • 运营提供给设计名字;
  • 设计根据名字一张一张输出海报;
  • 运营再把海报一张一张发给参与者;

如果我们只有两位数的参与者,那使用这个工作流工作是没有问题的。但是当我们面向的是 300 人规模的 Rust China Conf 的时候,这个工作流的工作量就过于大了些,需要更多人力支持。再加上虽然 Rust 社区中的开发者无法到现场参与大会,依然也想转发 Rust 大会海报,为 Rust China Conf 打 Call。

在这种情况下,统计人名,再让设计志愿者一张一张输出海报,并把海报发给指定的人,对只有个位数的 Rust China Conf 组委会来说,基本上是不可能完整完成的任务。

不就是改改参数吗?🤔

那要怎么办呢?既然是办 Rust China Conf,首先想到的还是要用 Rust 解决。当然,Rust 与 WebAssembly 的集合很适合图片处理这种计算密集型函数。

我们这家创业公司(Second State)在成立的时候,就坚定地认为用 Rust 与 Wasm 做 serverless 是云计算的未来。所以很自然地就想到我们之前用 Rust 函数写的的 watermark demo。为什么不把水印变成名字/昵称,这样就可以让开发者自己在网页上输入名字,然后就能生成一张专属邀请海报。使用 Serverless 的好处是只需为所使用的资源付费,成本也能 Cover 住。

使用 Second State Functions 给出的例子,需要解决的是

  • 找到一个中英文通用的字体,没有任何版权问题
  • 重新计算 watermark 在海报上的位置,居中放置
  • watermark 的颜色与大小
#[wasm_bindgen]
pub fn watermark (img_buf: &[u8]) -> Vec<u8> {
    // 读取输入的图片
    let mut img = image::load_from_memory(img_buf).unwrap();
    let (w,h) = img.dimensions();
    let scale = Scale {
      x: w as f32 /10.0,
      y: h as f32 /10.0,
    };

    // 准备 watermark 的字体
    let font = Vec::from(include_bytes!("DejaVuSans.ttf") as &[u8]);
    let font = Font::try_from_vec(font).unwrap();

    // 在输入的图片画出 watermark
    drawing::draw_text_mut(&mut img, image::Rgba([255u8, 255u8, 255u8, 255u8]), 0+(h/10),h/2, scale, &font, "Hello Second State");

    // 编写并返回已经加好水印的照片
    let mut buf = vec![];
    img.write_to(&mut buf, image::ImageOutputFormat::Png).unwrap();
    return buf;
}
复制代码

let font = Vec::from(include_bytes!("DejaVuSans.ttf") as &[u8]); 在这里替换字体,用 PingFang Bold.ttf 替换原来的字体。

PingFang Bold.ttf 支持中英文,也与 Rust China Conf 的主 kv 字体一致,是个完美的选择。

Rgba([255u8, 255u8, 255u8, 255u8]), 0+(h/10),h/2, 在这里调整颜色的 RGB 值与位置,RGB 值也就是水印的颜色。根据海报的背景最终选定白色。再接下来就是通过调整 0+(h/10),h/2, 调整字体的位置了。

经过几次调试后,终于找到了基本上是居中的位置。虽然还有一点小瑕疵,但是看起来还不错,可以用了。效果如下图:

original.png

改参数也是要技巧的 🧐

但是很快又有一个问题出现,中英文长度不一样。如果输入的名字是 Second State Functions,英文字符过长,而且文字不是居中的,甚至超出了图片的安全区域。

😭 😭 😭

简单地调整位置参数的做法并不能很好解决这个问题。尤其现在是多元化环境,大家的昵称五花八门,无法判断输入文字的大小、也无法判断是否输入的是中文,还是中英混杂。

我们需要再多写点 Rust,来确保输入的文字,无论长短,无论中英文,都能够居中显示,这样才能达到为任何人生成定制海报的目的。

之前提到的代码已经告诉了我们如何在图片上添加文字,并输出新的图片。那我们再知道文字画到图片上的尺寸,就能实现文字的居中显示啦。

那么如何获取到这个尺寸呢?

我们的解决思路是,输入文字后,函数会先按照基准字号把文字画到一张纯白的图片上,然后根据文字在这张图片的表现自动调整文字大小,从而保证文字一直居中并且大小合适。

const MAX_WIDTH : u32 = 349;
const MAX_HEIGHT : u32 = 80;
const MAX_FONT_SIZE : f32 = 125.0;
const FAR_LEFT : u32 = 200;
const FAR_TOP : u32 = 590;
复制代码

lib.rs 先确定了输入文字的宽度与高度、基准字号、文字的左上角位置,这里可以理解成先规定了一个安全区域。

当我们输入文字 Second State Functions 之后,lib.rs 里的 watermark 函数的write_to_crop 函数先把文字按基准字号,即上文已经确定好的 MAX_FONT_SIZE ,画到 crop.png 上。crop.png 就是我们上文提到的纯白的图片。

pub fn watermark (watermark_text: &str) -> Vec<u8> {
    let width = write_to_crop(watermark_text);

    let mut left = FAR_LEFT;
    let mut top = FAR_TOP;
    let mut font_size = MAX_FONT_SIZE;
复制代码

接下来, imagecrop.rs 里定义的结构对象计算出白色背景上黑色文字所占据的宽度,这也就是水印画到图片上的基准宽度。

使用基准字号,Second State Functions 的位置明显过宽了,那就来调整字号,

lib.rs 里的 watermark 函数根据 imagecrop 里计算出的基准宽度,以及海报上允许放置文字的最大尺寸和相对位置,计算出能让文字居中显示的位置和字号。

if width < MAX_WIDTH {
      left = FAR_LEFT + (MAX_WIDTH - width) / 2;
    } else {
      font_size = (MAX_WIDTH as f32) / (width as f32) * MAX_FONT_SIZE;
      top = FAR_TOP + ((1.0 - (MAX_WIDTH as f32) / (width as f32)) * (MAX_HEIGHT as f32)) as u32;
    }
复制代码

得出字号以及让文字能够居中显示的位置后,剩下的工作就和前面提到的在图片上添加文字完全一样了。

当完成了 Rust 程序以后,就可以编译成 wasm 文件,并将其部署到 Second State Functions 上提供给外界使用。这一步是通过 ajax 的方式实现了这个服务。 这样,任何拿到链接的人,都可以输入自己的名字,生成一张定制的个人打 Call 海报。

better.png

生成适合自己的海报,解放双手 👐

首先是 fork 这个 github repo,然后就可以肆意更改了。

字体使用 ttf 格式。我测试的时候,otf 格式的字体不能工作,如果有哪位大神知道原因,欢迎指出。更改字体在 lib.rs 里。

template.png 是模板海报,你可以替换成自己的海报。如果你的海报宽度与我们的海报宽度一样,且名字的位置也一样,那么就不用调整任何参数了。

如果你的海报与我们的模板不一致,那你需要在 lib.rs 修改下面这些参数:

//文字安全区域的最大宽度
const MAX_WIDTH : u32 = 349;
//文字安全区域的最大高度
const MAX_HEIGHT : u32 = 80;
//最大允许字号
const MAX_FONT_SIZE : f32 = 125.0;
//文字安全区域相对图片左上角的偏移量
const FAR_LEFT : u32 = 200;
const FAR_TOP : u32 = 590;
复制代码

改完参数之后,按照 github 上的 Readme 部署函数,测试出最佳位置。

为了方便测试,我们在 src 文件夹添加了 main.rs 文件。

最后使用 ajax 部署成一个网页,这样就可以方便地把生成海报应用分享给相关的人使用,解放双手!

 $.ajax({
      url: "https://rpc.ssvm.secondstate.io:8081/api/run/149/watermark/bytes",
      type: "post",
      data : $('#input')[0].files[0],
      contentType: "application/octet-stream",
      processData: false,
      xhrFields:{
        responseType: 'blob'
      },
      success: function (data) {
        const img_url = URL.createObjectURL(data);
        $('#wm_img').prop('src', img_url);
      }
  });
复制代码

这或许也可以称为 Low Code Rust。Happy Coding! 🦀

Reference 🤝

文章分类
前端
文章标签