Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I encode a Rust Piston image and get the result in memory?

I am using the Piston image package to generate images.

fn get_image() -> image::DynamicImage {
    image::DynamicImage::ImageRgba8(image::ImageBuffer::new(512, 512))
}

I have a hyper web server, from which I would like to serve dynamically-generated images.

Based on the answer to How to create an in-memory object that can be used as a Reader or Writer in Rust?, I thought I might be able to use a Cursor<Vec<u8>> as the destination. However, image only seems to provide a method to write to a filename, not a Writer like my cursor.

After looking through image's documentation, I hoped there might be some way to use the image::png::PNGEncoder struct directly. It provides a public method encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> Result<()>. However, I wasn't sure what the data argument is supposed to be, and I couldn't find any public declarations of the ColorTypes used by ImageBuffer.

(&Get, "/image.png") => {
    let v = {
        let generated = get_image();
        let mut encoded_image = Cursor::new(Vec::new());
        let (width, heigth) = generated.dimensions();
        {
            let encoder = image::png::PNGEncoder::new(&encoded_image);
            encoder.encode(unimplemented!("what goes here?"));
        }
        encoded_image.get_mut()
    };

    Box::new(futures::future::ok(
        Response::new()
            .with_header(ContentLength(v.len() as u64))
            .with_body(v.clone()),
    ))
}

How do I encode a Piston GenericImage to a format like PNG and get the result in memory?

like image 960
Jeremy Avatar asked Jun 07 '18 01:06

Jeremy


2 Answers

fn getImage() -> image::DynamicImage 

You have a DynamicImage.

image only seems to provide a method to write to a filename

I assume you mean DynamicImage::save.

The method immediately before save is write_to:

pub fn write_to<W: Write, F: Into<ImageOutputFormat>>(
    &self, 
    w: &mut W, 
    format: F
) -> ImageResult<()>

This accepts any generic writer:

let mut buf = Vec::new();

get_image()
    .write_to(&mut buf, image::ImageOutputFormat::PNG)
    .expect("Unable to write");

There's no need for a Cursor.


How do I encode a Piston GenericImage to a format like PNG?

I have no idea the best way to do this. I'd probably copy the generic image into a DynamicImage and then follow the above instructions.

like image 166
Shepmaster Avatar answered Nov 15 '22 08:11

Shepmaster


I've adapted OP's code based on the image crate docs. It looks possible to make the original code to work. There is no need for Cursor but one needs to create a "by reference" adaptor for the instance of Write implemented by Vec.

// This code have not been tested or even compiled
let v = {
    let generated = get_image();
    let mut encoded_image = Vec::new();
    let (width, height) = generated.dimensions();
    {
        image::png::PNGEncoder::new(encoded_image.by_ref())
            .encode(
                generated.raw_pixels(), 
                width, 
                height,
                generated.color()
            ).expected("error encoding pixels as PNG");
    }
    encoded_image
};

Here is the code I actually used in my project. I get a reference to the raw pixels passed in as an &[u8] slice. Each pixel represents an 8 bit grayscale value. Here is a function that encodes the pixels into an in memory PNG image.

pub fn create_png(pixels: &[u8], dimensions: (usize, usize)) -> Result<Vec<u8>> {
    let mut png_buffer = Vec::new();
    PNGEncoder::new(png_buffer.by_ref())
        .encode(
            pixels,
            dimensions.0 as u32,
            dimensions.1 as u32,
            ColorType::Gray(8),
        ).expect("error encoding pixels as PNG");

    Ok(png_buffer)
}
like image 37
Taras Alenin Avatar answered Nov 15 '22 10:11

Taras Alenin