简体   繁体   中英

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 ColorType s 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?

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.

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

Rather than returning a DynamicImage from get_image , it would be easier to return a specific image type, for example a colour RGBA image:

use image::RgbaImage;

fn get_image() -> RgbaImage {
    RgbaImage::new(512, 512)
}

The RgbaImage implements Deref<[u8]> , which means that the image can be directly dereferenced as a slice of bytes, so &img can be used as the first argument to encode . The height and width are easy, just access img.height() and img.width() . The final argument to encode is the colour type, which is more fiddly as it is stored as an associated constant of the Pixel type parameter on the ImageBuffer . From the generic type it can be accessed as P::COLOR_TYPE , so instead of making encode_png specific to one type of image, I've written it as accepting any ImageBuffer type.

The full example is then:

use std::error::Error;
use std::ops::Deref;
use image::{png::PNGEncoder, ImageBuffer, ImageError, Pixel, RgbaImage, Rgba};

fn encode_png<P, Container>(img: &ImageBuffer<P, Container>) -> Result<Vec<u8>, ImageError>
where
    P: Pixel<Subpixel = u8> + 'static,
    Container: Deref<Target = [P::Subpixel]>,
{
    let mut buf = Vec::new();
    let encoder = PNGEncoder::new(&mut buf);
    encoder.encode(img, img.width(), img.height(), P::COLOR_TYPE)?;
    Ok(buf)
}

fn get_image() -> RgbaImage {
    let mut img = RgbaImage::new(32, 32);

    // Draw something to show in the final image
    for i in 0..32 {
        img.put_pixel(i, i, Rgba([255, 0, 0, 255]));
    }
    img
}

fn main() -> Result<(), Box<dyn Error>> {
    let img = get_image();
    let buf = encode_png(&img)?;

    // write out the image using the base64 crate as a data
    // URL so that it can be copy-pasted into a web browser
    println!("data:image/png;base64,{}", base64::encode(&buf));
    Ok(())
}

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e2611c5ef248667bf984a8dd3cedfec8

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM