How do I encode a Rust Piston image and get the re

2020-02-14 06:46发布

问题:

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?

回答1:

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.



回答2:

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)
}