An image library


#1

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.


#2

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

why not “magick_rust”?


#4

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


#5

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 image