Taffy image

Just a quick experiment trying to use the Taffy layout library and image-builder crates to layout and build images in Rust.

This worked well enough to satiate my curiosity and could, for example, generate social share images à la @vercel/og.

Note: image-builder currently has a bug that causes text rendering to look a bit mushy.

The code below references this super pretty logo and IBM Plex Sans Bold.

use std::borrow::BorrowMut;
use std::fs;
use std::io::Cursor;

use image::io::Reader as ImageReader;
use image::DynamicImage;
use image_builder::{colors, FilterType, Image, Picture, Rect, Text};

use taffy::prelude::*;

fn main() {
let width = 1200;
let height = 560;

let mut tree: TaffyTree<()> = TaffyTree::new();

let mut image = Image::new(width, height, colors::GRAY);

let plex_bold = fs::read("fonts/ibm-plex-sans/IBMPlexSans-Bold.ttf").unwrap();

image.add_custom_font("IBM Plex Sans Bold", plex_bold);

image.add_rect(
Rect::new()
.size(width, height)
.position(0, 0)
.color(colors::WHITE),
);

let logo = fs::read("images/logo.png").unwrap();
let img: DynamicImage = ImageReader::new(Cursor::new(logo))
.with_guessed_format()
.expect("jpg or png")
.decode()
.unwrap();
let mut logo_picture = Picture::new(img).resize(160, 160, FilterType::Triangle);
let logo_node = tree
.new_leaf(Style {
size: Size {
width: length(160.0),
height: length(160.0),
},
..Default::default()
})
.unwrap();

let mut title = Text::new("taffy-image")
.size(90)
.font("IBM Plex Sans Bold")
.color(colors::BLACK);

let (title_width, title_height) = image.borrow_mut().text_size(&title);
let title_node = tree
.new_leaf(Style {
size: Size {
width: length(title_width as f32),
height: length(title_height as f32),
},
..Default::default()
})
.unwrap();

let mut description = Text::new("Generated with taffy-image")
.size(30)
.color(colors::GRAY);
let (description_width, description_height) = image.borrow_mut().text_size(&description);
let description_node = tree
.new_leaf(Style {
size: Size {
width: length(description_width as f32),
height: length(description_height as f32),
},
..Default::default()
})
.unwrap();

let root_node = tree
.new_with_children(
Style {
flex_direction: FlexDirection::Column,
align_content: Some(AlignContent::SpaceBetween),
align_items: Some(AlignItems::Center),
justify_content: Some(AlignContent::SpaceEvenly),
size: Size {
width: length(width as f32),
height: length(height as f32),
},
..Default::default()
},
&[logo_node, title_node, description_node],
)
.unwrap();

tree.compute_layout(root_node, Size::MAX_CONTENT).unwrap();

image.add_picture(logo_picture.position(
tree.layout(logo_node).unwrap().location.x as u32,
tree.layout(logo_node).unwrap().location.y as u32,
));

image.add_text(title.position(
tree.layout(title_node).unwrap().location.x as u32,
tree.layout(title_node).unwrap().location.y as u32,
));

image.add_text(description.position(
tree.layout(description_node).unwrap().location.x as u32,
tree.layout(description_node).unwrap().location.y as u32,
));

image.save("example.png");
}

That code produces this masterpiece:

Wow. Such art.