rustgtkgtk-rs

How to modify a pixbuf when mouse is pressed in GTK-rs?


I'm trying to modify a pixbuf when the left mouse button is clicked but I don't think it's being modified. The modify_pixel function simply takes a specific pixel from the pixbuf and modifies it.

fn build_ui(application: &Application) {
    
    let pixbuf = Pixbuf::from_file("assets/map.png").expect("msg");
    let pixbuf = Rc::new(RefCell::new(pixbuf));
    
    let gesture = GestureClick::new();

    let pixbuf_clone = Rc::clone(&pixbuf);

    
    gesture.connect_pressed(move |_gesture, _n_press, x, y| {
        let mut pixbuf = pixbuf_clone.borrow_mut();
        let position = [x as usize, y as usize];
        if position[0] < pixbuf.width() as usize && position[1] < pixbuf.height() as usize {
            modify_pixel(&mut pixbuf, position);
        } else {
            println!("Out of bounds");
        }
    });

    let picture = Picture::for_pixbuf(Some(&pixbuf.borrow()).expect("No se pudo crear el Picture"));


    let window = ApplicationWindow::builder()
        .application(application)
        .default_height(800)
        .default_width(800)
        .title("My GTK App")
        .build();
    window.set_child(Some(&picture));
    window.add_controller(gesture);
    window.present();
}
fn modify_pixel(pixbuf: &mut Pixbuf, position: [usize;2]){
    let pixels = unsafe {pixbuf.pixels()};
    let rowstride = pixbuf.rowstride() as usize;
    let n_channels = pixbuf.n_channels() as usize;

    
    let offset = position[1] * rowstride + position[0] * n_channels;

    let mut pixels = pixels.to_vec(); 
    pixels[offset] = 255;  
    pixels[offset + 1] = 0;  
    pixels[offset + 2] = 0;  
    *pixbuf = Pixbuf::from_mut_slice(
        pixels,
        Colorspace::Rgb,
        pixbuf.has_alpha(),
        8, 
        pixbuf.width(),
        pixbuf.height(),
        rowstride as i32,
    );
}

I'm also not sure if using a Gesture is the most appropriate option. I tried to modify the picture inside the clause but I had the same problem


Solution

  • You need to use set_pixbuf in order to update the Pixbuf like so:

    fn build_ui(application: &Application) {
        let pixbuf = Pixbuf::from_file("assets/map.png").expect("msg");
        let pixbuf = Rc::new(RefCell::new(pixbuf));
        let pixbuf_clone = Rc::clone(&pixbuf);
        
        let gesture = GestureClick::new();
    
        let picture = Picture::for_pixbuf(Some(&pixbuf.borrow()).expect("Failed to load image."));
    
        let window = ApplicationWindow::builder()
            .application(application)
            .default_height(800)
            .default_width(800)
            .title("GTK Pixbuf test")
            .build();
        window.set_child(Some(&picture));
    
        gesture.connect_pressed(move |_gesture, _n_press, x, y| {
            let mut pixbuf = pixbuf_clone.borrow_mut();
            let position = [x as usize, y as usize];
            if position[0] < pixbuf.width() as usize && position[1] < pixbuf.height() as usize {
                modify_pixel(&mut pixbuf, position);
                picture.set_pixbuf(Some(&pixbuf));
            } else {
                println!("Out of bounds");
            }
        });
    
        window.add_controller(gesture);
        window.present();
    }
    

    This will change the pixel to red when clicking on the image with the mouse.

    I suppose this is not exactly what you would like to do though, for which reason, I would suggest looking into the DrawingArea example.

    Pixbuf related methods are deprecated since GTK v4.12 btw. and should be replaced by paintable as can be seen below:

    fn build_ui(application: &Application) {
        let pixbuf = Pixbuf::from_file("assets/blank.png").expect("msg");
        let pixbuf = Rc::new(RefCell::new(pixbuf));
        let pixbuf_clone = Rc::clone(&pixbuf);
    
        let gesture = GestureClick::new();
    
        let picture = Picture::for_paintable(&Texture::for_pixbuf(&pixbuf.borrow()));
    
        let window = ApplicationWindow::builder()
            .application(application)
            .default_height(800)
            .default_width(800)
            .title("GTK Pixbuf test")
            .build();
        window.set_child(Some(&picture));
    
        gesture.connect_pressed(move |_gesture, _n_press, x, y| {
            let mut pixbuf = pixbuf_clone.borrow_mut();
            let position = [x as usize, y as usize];
            if position[0] < pixbuf.width() as usize && position[1] < pixbuf.height() as usize {
                modify_pixel(&mut pixbuf, position);
                picture.set_paintable(Some(&Texture::for_pixbuf(&pixbuf)));
            } else {
                println!("Out of bounds");
            }
        });
    
        window.add_controller(gesture);
        window.present();
    }