Overlay text on an image, and get the resulting bytes

I want to get an image, overlay some arbitrary text, and get the resulting image as the raw bytes, NOT saving to disk. I've found a similar question at An image library, but this only saves to disk. My current code is as follows, I don't know how to get the bytes from the image, so the return line is commented out.

use image::DynamicImage;
use image::Rgba;
use imageproc::distance_transform::Norm;
use imageproc::drawing::draw_text_mut;
use imageproc::morphology::dilate_mut;
use rusttype::{Font, Scale};

fn overlay_text(text: &str, img_data: &[u8]) -> Vec<u8> {
    let sb_img = image::load_from_memory(img_data).unwrap();

    let mut image = sb_img.to_rgba8();

    let mut image2: DynamicImage = DynamicImage::new_luma8(image.width(), image.height());

    let font: &[u8] = include_bytes!(".fonts/DejaVuSans.ttf") as &[u8];
    let font = Font::try_from_bytes(font).unwrap();

    let scale = Scale {
        x: image.width() as f32 * 0.2,
        y: image.height() as f32 * 0.2,
    };

    let x = (image.width() as f32 * 0.10) as u32;
    let y = (image.width() as f32 * 0.10) as u32;
    draw_text_mut(
        &mut image2,
        Rgba([255u8, 255u8, 255u8, 255u8]),
        x,
        y,
        scale,
        &font,
        text,
    );

    let mut image2 = image2.to_luma8();
    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).0[0];
            if pixval != 255 {
                let new_pix = Rgba([pixval, pixval, pixval, 255]);
                image.put_pixel(x, y, new_pix);
            }
        }
    }

    draw_text_mut(
        &mut image,
        Rgba([255u8, 255u8, 255u8, 255u8]),
        x,
        y,
        scale,
        &font,
        text,
    );

    // image2.into_bytes() method does not exist
}

fn main() {
    let imgbytes = &reqwest::blocking::get("https://example.com/image.png")
        .unwrap()
        .bytes()
        .unwrap()
        .to_vec() as &[u8];
    overlay_text("some text", imgbytes);
}

https://docs.rs/image/0.23.14/image/enum.DynamicImage.html#method.to_rgba16
https://docs.rs/image/0.23.14/image/struct.ImageBuffer.html#method.into_raw

^-- Is that what you are looking for?

Or if you want to get the bytes in a format like PNG instead of a raw (uncompressed) image, you can use an &mut Vec<u8> instead of a File when constructing a png::Encoder (or other encoder type).

Into_raw doesn't return a Vector, so I'm pretty sure that's not what I'm looking for.
I'm handing an imagebuffer, so to_rgba can't be what I'm looking for.

I think this is what I'm looking for, can you show me how to do this with the setup I have now? I'm very new to image processing with Rust, this is the first thing I've done.

pub enum DynamicImage {
    ImageLuma8(GrayImage),
    ImageLumaA8(GrayAlphaImage),
    ImageRgb8(RgbImage),
    ImageRgba8(RgbaImage),
    ImageBgr8(ImageBuffer<Bgr<u8>, Vec<u8>>),
    ImageBgra8(ImageBuffer<Bgra<u8>, Vec<u8>>),
    ImageLuma16(ImageBuffer<Luma<u16>, Vec<u16>>),
    ImageLumaA16(ImageBuffer<LumaA<u16>, Vec<u16>>),
    ImageRgb16(ImageBuffer<Rgb<u16>, Vec<u16>>),
    ImageRgba16(ImageBuffer<Rgba<u16>, Vec<u16>>),
}

There exists a route to go ImageBuffer -> DynamicImage -> to_rgba

Here's the code from that other thread, modified to write to a Vec<u8> instead of a file:

let mut output: Vec<u8> = Vec::new();
{
    let mut encoder = png::Encoder::new(&mut output, 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");
}
// now `output` contains the PNG image.

Oh, I must not have seen that, or simply not understood. I'm very new to manipulating images in rust, could you show me how to convert the imagebuffer into a dynamicimage?

Thank you for this, however, (I should have made this clear in the OP, my bad) when I was referring to that thread, I was talking about safijari's response, not bugaevc's. Safijari's only needs minimal changes to work using latest rust/latest crate versions, however bugaevc's is very out of date, and I don't know how to get it working. (I am pretty knew to Rust if you couldn't tell lol)

PS:
Is there a reason you put curly braces around everything but the first line? Is it just for organization purposes?

Oh, sorry. In that program, you can replace this line:

let _ = image.save(path).unwrap();

with this:

let dyn_image = DynamicImage::ImageRgba8(image);
let mut output: Vec<u8> = Vec::new();
dyn_image.write_to(&mut output, image::ImageOutputFormat::Png).unwrap();

In that code, writer contained a mutable (exclusive) reference to output, so you wouldn't be able to access output until after writer was dropped or went out of scope.

1 Like

Thanks a lot!

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.