Need crate to get a clip from an buffered image

Does anyone have any suggestions on getting a clip from a buffered image?

To animate a chess game board (to learn Rust), I'm currently using:

[dependencies]
graphics_buffer = { version = "0.7.3", features = ["piston_window_texture"] }
piston2d-graphics = "0.36.0"
piston_window = "0.109.0"

and it's nice because I can put .png images from files into buffers; but after searching the web and looking at the crates docs, I can't see how to get a rectangular clip from a buffer so I can draw it on top of the contents of another buffer.

so to move a piece buffered image across a across a board buffered image...I can currently:

1- read .png Board, and PIece image files
2- place Board in buffers 1, and 2
3- place PIece in buffer 3
4- draw buffer 3 onto buffer 2
like so:

    // load Board image
    let a_image =
        RenderBuffer::decode_from_bytes(include_bytes!(
            "board.png")).unwrap();
    
    // load piece image
    let piece_image =
        RenderBuffer::decode_from_bytes(include_bytes!(
            "piece.png")).unwrap();


    // Initialize the clean board buffer 1 size; this is where rectangular pieces/clips of the clean board buffered image are taken to draw on dirty board buffered image to clear places on the dirty board traversed by the piece
    let mut clean_board_buffer_1 =
        RenderBuffer::new(board_image.width(), board_image.height());
    clean_board_buffer.clear([0.0, 0.0, 0.0, 1.0]);

    // Initialize the dirty buffer 2 size; this is where all pieces are displayed
    let mut dirty_board_buffer =
        RenderBuffer::new(board_image.width(), board_image.height());
    dirty_board_buffer.clear([0.0, 0.0, 0.0, 1.0]);

    // Draw board_image to the clean buffer
    image(&board_image, IDENTITY, &mut clean_board_buffer);

    // Draw board_image to the dirty buffer
    image(&board_image, IDENTITY, &mut dirty_board_buffer);

    // Draw piece image to the dirty buffer
    image(&piece_image, IDENTITY.trans_pos([0.0, 0.0]), &mut dirty_board_buffer);

All I have left is to find out how to take the rectangular clip from the clean_board_buffer. Any ideas?

I could not figure out how to do this, however it is definitely possible.

Normally you would just draw the background and then all pieces on top of that, each frame.

So, i haven't tried this, but i looked at the docs and the following should work:

  1. create a new RenderBuffer with the width and height of the rectangle that you want to cut out
  2. RenderBuffer derefs to RgbaImage, which in turn implements Index<(u32, u32)>, so you should be able to get pixel values on it using buffer[(x,y)].
  3. This can be used to loop over (0..rect_width) and (0..rect_height), get the pixel values and copy them over to the new RenderBuffer.

@mmmmib Thank you for deciphering the docs. Since I'm too new at Rust to readily know how to fill in the blanks to your suggestion, could you please give me a concrete example in code?

1 Like

Something like this? It will copy the rectangle of the specified width starting from (10,20) into the new buffer.

let mut buffer = RenderBuffer::new(rect_width, rect_height);
for x in 0..rect_width {
    for y in 0..rect_height {
        buffer[(x,y)] = other_image[(x + 10, y + 20)];
    }
}
1 Like

@alice Thank you! ......when I ran your example I got this error:

error[E0596]: cannot borrow data in a dereference of `graphics_buffer::RenderBuffer` as mutable
   --> src/main.rs:797:13
    |
797 |             clip_buffer[(x,y)] = chessboard_image[(x + 10, y + 20)];  // other_image[(x + 10, y + 20)];
    |             ^^^^^^^^^^^ cannot borrow as mutable
    |
    = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `graphics_buffer::RenderBuffer`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0596`.
error: could not compile `learn-game-dev`.

Ah, apparently it implements Deref but not DerefMut, so you can't modify it in this manner. Use the RenderBuffer::set_pixel method instead to update the value.

1 Like

@alice THANK YOU so much for your help! You are fantastically helpful!!
I will take your help and pay it forward by doing a pull on the graphics_buffer crate and creating a higher level clip_from_buffer function call to make everything cleaner.

One lesson learned: ...to more closely examine low level functions in a crate to create any missing higher level functions. I was too focused on searching for a higher level way to do this, when the lower level solution was available all along!

This is finalization of the complete solution:

let mut buffer = RenderBuffer::new(rect_width, rect_height);
let mut color: [f32; 4];

for x in 0..rect_width {
    for y in 0..rect_height {
         color = other_image.pixel(x, y);
         buffer.set_pixel(x, y, color); 
    }
}
1 Like

Setting each pixel individually will probably be very slow. You can convert between RenderBuffer and image::buffer::ImageBuffer because of these impls:

impl From<DynamicImage> for RenderBuffer { ... }
impl From<ImageBuffer<Rgba<u8>, Vec<u8>>> for RenderBuffer { ... }

For clipping an ImageBuffer, there is image::imageops::crop, and it seems that you can use image::imageops::overlay to draw it over another image.

EDIT: although apparently it also works pixel by pixel. You can also use rows_mut to access the image row by row.

Why do you think that setting each pixel one-by-one is slow? Any method for copying the data ultimately boils down to that.

If there is something here that is slow, it is because we are doing it in the wrong direction. It is possible that swapping the x and y loop will be faster.

also, be sure to compile the binaries in release mode using

cargo run --release

that way, the compiler will perform optimizations which make it take a bit longer to compile, but the resulting binary will run faster.

Because setting a pixel involves a bounds check and an index calculation. I don't think the compiler can make that as fast as doing a memcopy of the whole line at once.

To my surprise, I couldn't find a crate that does this, so I wrote a prototype function for benching myself:

fn copy_line(
    src: &RgbaImage,
    src_x: u32,
    src_y: u32,
    dst: &mut RgbaImage,
    dst_x: u32,
    dst_y: u32,
    width: u32,
) {
    let src = src.as_flat_samples();
    let src_index = src.index(0, src_x, src_y).unwrap();
    assert!(src_x + width <= src.extents().1 as u32);
    let count = width as usize * src.extents().0;

    let mut dst = dst.as_flat_samples_mut();
    let dst_index = dst.index(0, dst_x, dst_y).unwrap();
    assert!(dst_x + width <= dst.extents().1 as u32);

    let src_slice = &src.as_slice()[src_index..src_index + count];
    let dst_slice = &mut dst.as_mut_slice()[dst_index..dst_index + count];
    dst_slice.copy_from_slice(src_slice);
}

It appears that copying a 500x500 pixels image using this function is 10 times faster than using put_pixel:

by_pixel                time:   [435.93 us 436.33 us 436.79 us]                     
                        change: [-3.5644% -3.3219% -2.9892%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  4 (4.00%) high mild
  5 (5.00%) high severe

by_line                 time:   [42.349 us 42.430 us 42.528 us]                     
                        change: [-3.3137% -3.1309% -2.9420%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  1 (1.00%) low mild
  3 (3.00%) high mild
  3 (3.00%) high severe

See the benchmark code here. There is a check that the results of the two versions are the same. Of course, this is just an example. You can improve error handling and support more sample layouts, but that shouldn't change the performance drastically.

1 Like

@Riateche Very interesting. Would you please post some concrete code examples of how to do each of your suggestions?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.