How do I make a minifb window transparent?

  • Using minifb how do you make make a image transparent? The window option for transparency makes sense, but gives issues as shown below.
  • Besides I was wondering if there was a cleaner way to handle my if mode != 0 && mode != 1 bit. I feel I should be able to utilize the code in the if, but I am not sure how.

I use Linux btw so it should be implemented: WindowOptions in minifb - Rust

Code

const WIDTH: usize = 900;
const HEIGHT: usize = 900;

struct Image {
    width: usize,
    height: usize,
    buffer: Vec<u32>,
}

impl Image {
    fn load(filename: &str) -> Image {
        let image =
            lodepng::decode32_file(&filename).expect(&format!("Couldn't load {}", &filename));
        Image {
            width: image.width,
            height: image.height,
            buffer: image
                .buffer
                .iter()
                .map(|color| {
                    return u32::from_le_bytes([color.b, color.g, color.r, color.a]);
                })
                .collect(),
        }
    }
}

fn main() {
    let image1 = Image::load("mainframe1.png");
    let image2 = Image::load("mainframe2.png");

    let mut window = minifb::Window::new(
        "Rotating images",
        WIDTH as usize,
        HEIGHT as usize,
        minifb::WindowOptions {
            resize: false,
            topmost: true,
            scale: minifb::Scale::X1,
            transparency: true,
            borderless: true,
            none: true,
            ..minifb::WindowOptions::default()
        },
    )
    .expect("Unable to create window");

    let mut buffer = vec![0u32; (WIDTH * HEIGHT) as usize];

    // The current rotation in radians (0..2*Pi)
    let mut rotation = 0.0;

    let mut mode = 0;

    while window.is_open() && !window.is_key_down(minifb::Key::Escape) {
        rotation += 0.01;

        if window.is_key_pressed(minifb::Key::Space, minifb::KeyRepeat::No) {
            mode = (mode + 1) % 3;
        }

        if mode == 0 {
            window.update_with_buffer(&image1.buffer, image1.width, image1.height).unwrap();
        } else if mode == 1 {
            // TODO: Adjust Brightness image 1 to 25% (potentially utilizing https://docs.rs/minifb/latest/minifb/struct.WindowOptions.html)
            let new_buffer: Vec<u32> = image1.buffer.to_vec();
            let newer_buffer = (new_buffer[3] as f32 * 0.25) as u32;
            let newest_buffer: Vec<u32> = vec![new_buffer[0], new_buffer[1], new_buffer[2], newer_buffer];
            window.update_with_buffer(&newest_buffer, image1.width, image1.height).unwrap();
        } else if mode == 2 {
            // TODO: Fill and super impose both images. Decrease both images their brightness to 50%
        } else if mode == 3 {
            // TODO: Offset and rotate image 1
            // let positions: (isize, isize) = window.get_position();
            // window.set_position(p, y);
        } else if mode == 4 {
            // TODO: Offset and rotate two superimposed images in opposite directions
        } else {
            // Optimized
            // For optimization code can be combined here instead of the deduplication
            // in the name of speed
        }

        // Show the buffer in the window
        if mode != 0 && mode != 1 {
            window.update_with_buffer(&buffer, WIDTH as usize, HEIGHT as usize).unwrap(); 
        }   
}

Output

thread 'main' (113142) panicked at src/main.rs:79:84:
called `Result::unwrap()` on an `Err` value: Failed to Update, "Update failed because input buffer is too small. Required size for 885 (885 stride) x 720 buffer is 2548800\n            bytes but the size of the input buffer has the size 16 bytes"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: queue 0x626b17bc63e0 destroyed while proxies still attached:
  wl_surface#15 still attached
  wl_shm_pool#14 still attached
  xdg_toplevel#13 still attached
  xdg_surface#12 still attached
  xdg_wm_base#11 still attached
  wl_buffer#10 still attached
  wl_shm_pool#9 still attached
  wl_surface#8 still attached
  wl_shm#7 still attached
  wl_compositor#6 still attached
  wl_pointer#5 still attached
  wl_keyboard#4 still attached
  wl_seat#3 still attached
  wl_registry#2 still attached

This code produces a vector which contains only 4 pixels regardless of the size of image1, and minifb then rejects this too-short vector. Also, you’re treating the fourth value like it is the alpha value, but it’s a whole pixel. This error has nothing to do with minifb’s functionality; this code simply does not do the thing you intended it to do.

In order to not get an error, you need to supply a vector of the correct length. In order to get the picture you want, you need to loop over all the pixels of the image. Something like this (I haven’t tested this code):

let mut new_buffer: Vec<u32> = image1.buffer.to_vec();
let opacity = 0.25;
for pixel in new_buffer.iter_mut() {
    let [r, g, b, a] = pixel.to_le_bytes();
    let new_alpha = (a as f32 * opacity) as u8;
    *pixel = u32::from_le_bytes([r, g, b, new_alpha]);
}
window.update_with_buffer(&new_buffer, image1.width, image1.height).unwrap();

Note that if the window requires premultiplied alpha (minifb’s documentation doesn’t say), you should multiply all the color components by the same scale factor, not just alpha. If the window looks like it is glowing, then you’re having that problem.

There are several possible ways to do this, but it’s not as simple as it might be because you are sometimes using a temporary buffer. There are two basic ways to handle this. One of them is to use std::borrow::Cow, which allows you to have owned data or borrowed data:

use std::borrow::Cow;

...

// do this outside of the loop instead of your current `let mut buffer = ...`
let mut mutable_image = Image {
    buffer: vec![0u32; (WIDTH * HEIGHT) as usize],
    width: WIDTH,
    height: HEIGHT,
};

...

let image_to_show: Cow<Image> =
    if mode == 0 {
        Cow::Borrowed(&image1)
    } else if mode == 1 {
        // ... process image
        Cow::Owned(Image { buffer: new_buffer, width: image1.width, height: image1.height })
    } else {
        Cow::Borrowed(&mutable_image)
    };
window.update_with_buffer(&image_to_show.buffer, image_to_show.width, image_to_show.height).unwrap();

Another way is to use variables outside the if as temporary storage. This can be a tiny bit more efficient, but also more confusing.

let temp_image: Image;
let image_to_show: &Image =
    if mode == 0 {
        &image1
    } else if mode == 1 {
        // ... process image
        temp_image = Image { buffer: new_buffer, width: image1.width, height: image1.height };
        image_to_show = &temp_image;
    } else {
        &mutable_image
    };
window.update_with_buffer(&image_to_show.buffer, image_to_show.width, image_to_show.height).unwrap();

For this program, a third option would be to change your Image struct so that it contains a Cow<[u32]> instead of a Vec<u32>. However, this gets into the topic of structs that have lifetime parameters, so I wouldn’t recommend going for it until you have more practice.

In all of these cases, I’ve introduced the idea that the image to show is represented using your Image struct, rather than different paths to calling window.update_with_buffer() with different data. This is probably a good idea regardless of what else you do.

Thank you for the extensive information! I will try to solve it and post an update with the solution if I get it working for those interested. For my assignment I do rely on <Vec> so I will use that. How did you solve this issue if I might ask, so it might be easier for me to solve on my own in the future.

  1. I looked at the error and saw that it said the buffer had a size of 16 bytes, which is obviously far too small to be the image data, and is a small round number (in base 2).
  2. I looked at the code and saw that it was constructing a vector of 4 u32s = 4 × 4 bytes = 16 bytes to pass to update_with_buffer, so that was obviously the direct cause of the problem.
  3. I then read the rest of that block of code to understand what you were trying to do and suggest the fix.

Transparent windows can get you into some portability problems, you sometimes need to annotate that the window is transparent, or if it uses some window manager translucency effect (even on Linux X11 and Wayland behave differently IIRC).

Minifb creates and manages the window for you, so it would need to support it explicitly if you run into problems at that level.

@kpreid your solution did work as intended when testing it out. Thank you!