Deciding on parameter type when moving code to function

I have this program:

use image::{ImageBuffer, ImageFormat, Rgba};
use imageproc::drawing::draw_filled_rect_mut;
use imageproc::rect::Rect;

fn main() {
    let mut img = ImageBuffer::new(500, 500);
    // -----
    for x in (0..500).step_by(10) {
        for y in (0..500).step_by(10) {
            draw_filled_rect_mut(&mut img, Rect::at(x as i32, y as i32).of_size(10, 10), Rgba([255, 0, 0, ((x + y) % 255) as u8]));
        }
    }
    // -----
    img.save_with_format("out.png", ImageFormat::Png).unwrap();
}

I would like to extract the part between the lines into a function that receives the image img as a parameter. Currently all the types are automatically inferred, so I am unclear about what the type of that parameter should be?

You can do this to have the compiler tell you:

let mut img: u32 = ImageBuffer::new(500, 500);

It will fail with an error that tells you what the type is.

I tried that but it says:

expected type u32
found struct image::buffer::ImageBuffer<_, std::vec::Vec<_>>

and I can't use ImageBuffer<_, std::vec::Vec<_>> as the parameter type.

And those type parameters aren't simple to fill, that's the reason I'm asking this question.

They are associated types that again have trait bounds themselves and I'm kind of lost.

Do I have to replace them all the way down with concrete types, probably something like ImageBuffer<Rgba<u8>, Vec<u8>>? (I didn't actually try that.)
I would rather keep some of the trait bounds, like ImageBuffer<impl Pixel, Vec<???>? (I don't know what to put instead of ???.)
Even better would be if I could just use the "top" trait, GenericImage, but then I don't know what to put as the last argument to draw_filles_rect_mut().

Right, it's doing something weird. You can make it generic with something like this:

fn my_fn<P>(buf: &mut ImageBuffer<P, Vec<P::SubPixel>>)
where
    P: Pixel + 'static,
    P::Subpixel: 'static,

Not tested, but the doc seems to claim it needs the 'static.

Ok, now I'm back at the problem with the last parameter of draw_filled_rect_mut():

error[E0308]: mismatched types
  --> src\main.rs:23:85
   |
16 | fn my_fn<P>(buf: &mut ImageBuffer<P, Vec<P::Subpixel>>)
   |          - this type parameter
...
23 |             draw_filled_rect_mut(buf, Rect::at(x as i32, y as i32).of_size(10, 10), Rgba([255, 0, 0, ((x + y) % 255) as u8]));
   |                                                                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `P`, found struct `image::color::Rgba`
   |
   = note: expected type parameter `P`
                      found struct `image::color::Rgba<u8>`

If you require it to use a specific kind of pixel, you can't make pixel generic.

That makes sense. So let's replace P with Rgba<u8>. Now I get:

error[E0223]: ambiguous associated type
  --> src\main.rs:16:49
   |
16 | fn my_fn<P>(buf: &mut ImageBuffer<Rgba<u8>, Vec<Rgba<u8>::Subpixel>>)
   |                                                 ^^^^^^^^^^^^^^^^^^ help: use fully-qualified syntax: `<image::color::Rgba<u8> as Trait>::Subpixel`

What is ambiguous? Changing Rgba to image::color::Rgba doesn't help. Why color? Isn't the type called image::Rgba?

Oh, I think I got it. It's ambiguous from which of the traits that Rgba implements the Subpixel is supposed to come. The "as Trait" is the important part. I have to use the function signature:

fn my_fn(buf: &mut ImageBuffer<Rgba<u8>, Vec<<Rgba<u8> as Pixel>::Subpixel>>)
1 Like

And with a type alias I only have to have that contraption in one place:

type MyImage = ImageBuffer<Rgba<u8>, Vec<<Rgba<u8> as Pixel>::Subpixel>>;

The subpixel type for Rgba<u8> is u8, so you can just do

type MyImage = ImageBuffer<Rgba<u8>, Vec<u8>>;

The image crate's types seem to trip me up more often than I feel like they should, but one handy thing to know is that these type aliases are defined by the crate too. For Rgba pixels, there is the RgbaImage buffer documented here:

type RgbaImage = ImageBuffer<Rgba<u8>, Vec<u8>>;

There are also RgbImage, GrayImage and GrayAlphaImage pre-defined.

In your original example, the type of img was being determined by the type of the pixel being used with it in the draw_filled_rect_mut call. I often find that I know up-front what type of image I'm going to be using, so I'd write the first part as:

use image::{Rgba, RgbaImage};

fn main() {
    let mut img = RgbaImage::new(500, 500);
    ...
}

Then the type for the function would match, like this:

fn my_fn(buf: &mut RgbaImage) { ... }
4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.