[SOLVED] Multi-line text on image using imageproc crate

Hi, I'm trying to create a function to print multiple lines of text to an image. I'm using the imageproc crate to accomplish this, I have done the same thing in Python using Python's with keyword, but Rust doesn't have an equivalent.

I'm using a for loop and the textwrap crate to make sure the lines of text will always fit into the image. Text wrap separates the text into sections and putting them into a Vector. I use this vector to iterate over the lines and print them out separately on the image. The function is below:

fn draw_fact_text(fact: &str, color: image::Rgba<u8>, fact_image: &str, mut x: i32, y: i32) {
    // Load image
    let fact_img = image::open(fact_image).unwrap();
    // Load font data
    let font_bytes = include_bytes!("../fonts/sans_rounded.ttf");
    let font = Font::try_from_bytes(font_bytes).unwrap();

    let wrapped_fact = textwrap::wrap(fact, 30);

    for line in wrapped_fact {
        let img = draw_text(
            &fact_img,
            color,
            x,
            y,
            Scale { x: 30.0, y: 30.0 },
            &font,
            &line,
        );
        x = x + 25;
    }

    match img.save("test.jpg") {
        Ok(_) => println!("Success!"),
        Err(e) => println!("Failed: {}", e),
    }
}

My problem is that img goes out of scope after the for loop and I don't know how to save the new image, I'm referencing my Python version of this function to follow the same logic but its not working for me because Rust is so different.

Any advice and help would be appreciated, thanks :grinning: :grinning:

When you want to use a variable after a loop ends, that means you must not declare it inside the loop. Also, you want to use one drawing operation as the input to the next, not reuse the original image without any previous drawing. So, change the inside to an assignment:

fn draw_fact_text(fact: &str, color: image::Rgba<u8>, fact_image: &str, mut x: i32, y: i32) {
    let mut img = image::open(fact_image).unwrap();       // declare mutable variable
    ...
    for line in wrapped_fact {
        img = draw_text(          // assignment, not let
            &img,                 // use the same variable
            color,
            x,
            y,
            Scale { x: 30.0, y: 30.0 },
            &font,
            &line,
        );
        x = x + 25;
    }
    ...
}
2 Likes

Assuming draw_text is drawing a new complete image and returning it, you probably want to do this

fn draw_fact_text(fact: &str, color: image::Rgba<u8>, fact_image: &str, mut x: i32, y: i32) {
    // Load image
    let mut img = image::open(fact_image).unwrap();
    // Load font data
    let font_bytes = include_bytes!("../fonts/sans_rounded.ttf");
    let font = Font::try_from_bytes(font_bytes).unwrap();

    let wrapped_fact = textwrap::wrap(fact, 30);

    for line in wrapped_fact {
        img = draw_text(&img, color, x, y, Scale { x: 30.0, y: 30.0 }, &font, &line).into();
        x = x + 25;
    }

    match img.save("test.jpg") {
        Ok(_) => println!("Success!"),
        Err(e) => println!("Failed: {}", e),
    }
}

That being said I think you would probably be better off using draw_text_mut

fn draw_fact_text(fact: &str, color: image::Rgba<u8>, fact_image: &str, mut x: i32, y: i32) {
    // Load image
    let mut img = image::open(fact_image).unwrap();
    // Load font data
    let font_bytes = include_bytes!("../fonts/sans_rounded.ttf");
    let font = Font::try_from_bytes(font_bytes).unwrap();

    let wrapped_fact = textwrap::wrap(fact, 30);

    for line in wrapped_fact {
        imageproc::drawing::draw_text_mut(
            &mut img,
            color,
            x,
            y,
            Scale { x: 30.0, y: 30.0 },
            &font,
            &line,
        );
        x = x + 25;
    }

    match img.save("test.jpg") {
        Ok(_) => println!("Success!"),
        Err(e) => println!("Failed: {}", e),
    }
}

That avoids making a bunch of extra copies of the image for every line drawn

2 Likes

Sorry for the late reply, been a bit busy and haven't had time to work on this project. Thank you for your replies @kpreid and @semicoleon this is extremely helpful, I didn't catch that I'm defining a variable in the for loop which would indeed create a new image for each line. The computer is doing exactly what I tell it :joy:.

I'm going to try implementing these solutions. I'm going to try using the draw_text_mut() function.

EDIT: It worked like a charm thanks again!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.