I have an image and I want to draw a text on it using Rust. How can I do that? Is the library “image” suitable for this? I haven’t found anything related to text in its documentation.
Here’s an example of how you can do that using the png
crate for encoding/decoding PNGs and the Cairo graphics library (via the cairo-rs
crate) for drawing:
extern crate png;
extern crate cairo;
use std::io::BufWriter;
use std::fs::File;
// for `encoder.set()`
use png::HasParameters;
fn main() {
// open the image file
let file = File::open("/path/to/some/file.png")
.expect("Failed to open the file");
// parse the metadata
let decoder = png::Decoder::new(file);
let (info, mut reader) = decoder.read_info()
.expect("Invalid PNG");
// extract the raw image
let mut data = vec![0; info.buffer_size()];
reader.next_frame(&mut data)
.expect("Invalid PNG");
// create a Cairo image surface
let mut surface = cairo::ImageSurface::create_for_data(
data.into_boxed_slice(),
|_| {}, // just drop it
cairo::Format::ARgb32,
info.width as i32,
info.height as i32,
info.line_size as i32
);
// initialize a Cairo drawing context
let context = cairo::Context::new(&surface);
// draw some text
context.select_font_face(
"serif",
cairo::FontSlant::Normal,
cairo::FontWeight::Normal
);
context.set_font_size(20.0);
context.set_source_rgb(1.0, 0.0, 0.0);
context.move_to(50.0, 50.0);
context.show_text("Hello");
drop(context);
let data = surface.get_data().unwrap();
// save the image back
let file = File::create("/path/to/output/file.png")
.expect("Failed to create the output file");
let buf_writer = BufWriter::new(file);
let mut encoder = png::Encoder::new(buf_writer, info.width, info.height);
encoder.set(png::ColorType::RGBA).set(png::BitDepth::Eight);
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&data)
.expect("Failed to write the image data");
}
3 Likes
why not “magick_rust”?
The imageproc crate is based on the image crate and supports this. See this example: https://github.com/PistonDevelopers/imageproc/blob/master/examples/font.rs
2 Likes
For the weary traveler that stumbles onto this thread: an example modified from font.rs that uses more parts of the imageproc crate (I do a dilate to add a stroke to the text as well as do some pixel level manipulation).
extern crate image;
extern crate imageproc;
extern crate rusttype;
use std::path::Path;
use std::env;
use imageproc::drawing::draw_text_mut;
use imageproc::morphology::dilate_mut;
use imageproc::distance_transform::Norm;
use image::{Rgb, RgbImage, Rgba};
use rusttype::{FontCollection, Scale};
use image::load;
use image::{ DynamicImage, ImageBuffer, ImageResult, ImageRgb8, GenericImage, Pixel};
fn main() {
let text = "HeLlO, wOrLd!";
let sb_img = image::open(&Path::new(
"/home/jari/Work/rust_meme/src/spongebobo.png"))
.ok().expect("Can't open spongebob image.");
let arg = if env::args().count() == 2 {
env::args().nth(1).unwrap()
} else {
panic!("Please enter a target file path")
};
let path = Path::new(&arg);
let mut image = sb_img.to_rgba();
// let mut image2 : DynamicImage = DynamicImage::new_luma8(502, 353);
let mut image2 : DynamicImage = DynamicImage::new_luma8(
image.width(), image.height());
let font = Vec::from(include_bytes!("impact.ttf") as &[u8]);
let font = FontCollection::from_bytes(font).unwrap().into_font().unwrap();
let height = 100f32;
let scale = Scale { x: height * 1.0, y: height };
draw_text_mut(&mut image2, Rgba([255u8, 255u8, 255u8, 255u8]), 10, 10, scale, &font, text);
let mut image2 = image2.to_luma();
dilate_mut(&mut image2, Norm::LInf, 4u8);
for x in 0..image2.width() {
for y in 0..image2.height() {
let pixval = 255 - image2.get_pixel(x, y).data[0];
if pixval != 255 {
let newPix = Rgba([pixval, pixval, pixval, 255]);
image.put_pixel(x, y, newPix);
}
}
}
draw_text_mut(&mut image, Rgba([255u8, 255u8, 255u8, 255u8]), 10, 10, scale, &font, text);
let _ = image.save(path).unwrap();
}
the result
3 Likes