Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a Rust struct with an image::ImageBuffer as a member?

Tags:

struct

rust

I'm using the image crate for picture manipulation and want to create a little wrapper to make my code a bit fancier.

extern crate image;

const BLACK: [u8; 4] = [0, 0, 0, 255];
const WHITE: [u8; 4] = [255, 255, 255, 255];
const RED: [u8; 4] = [255, 0, 0, 255];

pub struct Picture {
    buffer: image::ImageBuffer,
    width: u32,
    height: u32
}

impl Picture {
    // My functions like fill(), line() etc.
}

But when I compile this, I have an error:

src\pic.rs:11:13: 11:31 error: wrong number of type arguments: expected 2, found 0 [E0243]
src\pic.rs:11     buffer: image::ImageBuffer,
                          ^~~~~~~~~~~~~~~~~~

In the source, I saw that ImageBuffer accepts two arguments:

pub struct ImageBuffer<P: Pixel, Container> {
    width: u32,
    height: u32,
    _phantom: PhantomData<P>,
    data: Container,
}

So I decided to put those arguments in the Picture declaration:

pub struct Picture {
    buffer: image::ImageBuffer<image::Pixel>,
}

And got another error:

src\pic.rs:11:32: 11:44 error: the value of the associated type `Subpixel` (from the trait `pic::image::buffer::Pixel`) must be specified [E0191]
src\pic.rs:11     buffer: image::ImageBuffer<image::Pixel>,
                                             ^~~~~~~~~~~~

That means I must specify some value for the Subpixel type, and I don't get it. I don't know how to declare that Container type, I can't find anything useful in the sources. I tried to re-read the Rust Book, examples, rustc --explain E0191 but I am still lost.

update:

In sources found next declaration:

impl<P, Container> ImageBuffer<P, Container>
where P: Pixel + 'static,
      P::Subpixel: 'static,
      Container: Deref<Target=[P::Subpixel]>

And Subpixel is:

/// Primitive trait from old stdlib
pub trait Primitive: Copy + NumCast + Num + PartialOrd<Self> + Clone + Bounded {
}

But it not public for my crate.

like image 461
Drakoriss Avatar asked Feb 18 '16 17:02

Drakoriss


1 Answers

Looking at the documentation for ImageBuffer, we can see the trait requirements for the type parameters:

impl<P, Container> ImageBuffer<P, Container>
    where P: Pixel + 'static,
          P::Subpixel: 'static,
          Container: Deref<Target=[P::Subpixel]>

So, it's important that the second type parameter can dereference to a slice of subpixels. Subpixel is determined by the Pixel trait, which has a few implementations:

  • Rgb<T>
  • Luma<T>
  • Rgba<T>
  • LumaA<T>

Where T is the subpixel type.

This is all pretty complicated, but should provide a good bit of power and generality.

Note that I've only shown one of the implementation blocks. There are others that require DerefMut or that require the subpixel to be a u8.


How do you actually use it? First you need to pick a specific color and subpixel type. Based on your colors, it looks like you want to use a 4-channel, 8-bit color type. That would be Rgba<u8>.

There are many types that could dereference to an &[u8] or &mut [u8]. The obvious (but not only!) choice would be a Vec<u8>.

Combining these two, your structure needs to be something like:

buffer: image::ImageBuffer<Rgba<u8>, Vec<u8>>,

Then, it's just a matter of creating the structure and doing things to it:

extern crate image;

use image::{ImageBuffer, Pixel, Rgba};

pub struct Picture {
    buffer: ImageBuffer<Rgba<u8>, Vec<u8>>,
}

impl Picture {
    fn new(width: u32, height: u32) -> Self {
        // There's probably a cleaner way to figure out the right size
        let storage = vec![0; 4 * width as usize * height as usize];
        let buf = ImageBuffer::from_raw(width, height, storage).unwrap();

        Picture {
            buffer: buf,
        }
    }

    fn fill_red(&mut self) {
        let color = Rgba::from_channels(255, 0, 0, 255);

        for p in self.buffer.pixels_mut() {
            *p = color;
        }
    }
}

fn main() {
    let mut pic = Picture::new(256, 256);
    pic.fill_red();
}

Note that ImageBuffer already tracks the width and height, so there's no real reason to track them a second time.


However, all of this is the hard way. There are convenience functions like new which will allocate storage for you:

buffer: ImageBuffer::new(width, height),

These functions are hard-coded to return a Vec as the Container. If you didn't see that in the docs, you could try something like printing the type:

let () = ImageBuffer::new(width, height);

Which would tell you the type in the error message:

expected `image::buffer::ImageBuffer<_, collections::vec::Vec<_>>`,
like image 179
Shepmaster Avatar answered Oct 15 '22 21:10

Shepmaster