egui: Can't set an icon properly

fn main() -> Result<()> {
 
    let mut icon_bytes = Vec::new();
    let mut img_reader = BufReader::new(File::open("asset/test.jpg")?);
    img_reader.read_to_end(&mut icon_bytes)?;

    let options = eframe::NativeOptions {
        transparent: true,
        icon_data: Some(IconData{
            rgba: icon_bytes,
            width: 32,
            height: 32,
        }),
        ..Default::default()
    };

    eframe::run_native(Box::new(HelloWorldApp::new()?), options);
}

The is no change after add icon_data to options

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 25 (2 by maintainers)

Most upvoted comments

For anyone that uses Windows and wants to take the icon from the exe that they set with winres, here’s a beefy function that’s compatible with egui

build.rs

res.set_icon_with_id("file.ico", "your-icon-name-here");

cargo.toml (note I left too many feature deps in here and this needs to be slimmed down)

[dependencies.windows]
version = "0.43.0"
features = [
    "Win32_UI_WindowsAndMessaging",
    "Win32_Foundation",
    "Win32_System_LibraryLoader",
    "Win32_Graphics_Gdi",
    "Win32_UI_Input_KeyboardAndMouse",
    "Win32_System_Threading",
    "Win32_UI_Controls",
    "Win32_Graphics_Dwm",
    "Win32_UI_Shell"
]

load_app_icon() function

use eframe::IconData;
use windows::{
    w,
    Win32::{
        Graphics::Gdi::{
            CreateCompatibleDC, DeleteDC, GetDIBits, GetObjectA, SelectObject, BITMAP, BITMAPINFO,
            BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS,
        },
        System::LibraryLoader::GetModuleHandleW,
        UI::WindowsAndMessaging::{
            GetIconInfo, LoadImageW, HICON, ICONINFO, IMAGE_ICON, LR_DEFAULTCOLOR,
        },
    },
};

// Grab the icon from the exe and hand it over to egui
pub fn load_app_icon() -> IconData {
    let (mut buffer, width, height) = unsafe {
        let h_instance = GetModuleHandleW(None).expect("Failed to get HINSTANCE");
        let icon = LoadImageW(
            h_instance,
            w!("your-icon-name"),
            IMAGE_ICON,
            512,
            512,
            LR_DEFAULTCOLOR,
        )
        .expect("Failed to load icon");

        let mut icon_info = ICONINFO::default();
        let res = GetIconInfo(HICON(icon.0), &mut icon_info as *mut _).as_bool();
        if !res {
            panic!("Failed to load icon info");
        }

        let mut bitmap = BITMAP::default();
        GetObjectA(
            icon_info.hbmColor,
            std::mem::size_of::<BITMAP>() as i32,
            Some(&mut bitmap as *mut _ as *mut _),
        );

        let width = bitmap.bmWidth;
        let height = bitmap.bmHeight;

        let b_size = (width * height * 4) as usize;
        let mut buffer = Vec::<u8>::with_capacity(b_size);

        let h_dc = CreateCompatibleDC(None);
        let h_bitmap = SelectObject(h_dc, icon_info.hbmColor);

        let mut bitmap_info = BITMAPINFO::default();
        bitmap_info.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
        bitmap_info.bmiHeader.biWidth = width;
        bitmap_info.bmiHeader.biHeight = height;
        bitmap_info.bmiHeader.biPlanes = 1;
        bitmap_info.bmiHeader.biBitCount = 32;
        bitmap_info.bmiHeader.biCompression = BI_RGB;
        bitmap_info.bmiHeader.biSizeImage = 0;

        let res = GetDIBits(
            h_dc,
            icon_info.hbmColor,
            0,
            height as u32,
            Some(buffer.spare_capacity_mut().as_mut_ptr() as *mut _),
            &mut bitmap_info as *mut _,
            DIB_RGB_COLORS,
        );
        if res == 0 {
            panic!("Failed to get RGB DI bits");
        }

        SelectObject(h_dc, h_bitmap);
        DeleteDC(h_dc);

        assert_eq!(
            bitmap_info.bmiHeader.biSizeImage as usize,
            b_size,
            "returned biSizeImage must equal to b_size"
        );

        // set the new size
        buffer.set_len(bitmap_info.bmiHeader.biSizeImage as usize);

        (buffer, width as u32, height as u32)
    };

    // RGBA -> BGRA
    for pixel in buffer.as_mut_slice().chunks_mut(4) {
        pixel.swap(0, 2);
    }

    // Flip the image vertically
    let row_size = width as usize * 4; // number of pixels in each row
    let row_count = buffer.len() as usize / row_size; // number of rows in the image
    for row in 0..row_count / 2 {
        // loop through half of the rows
        let start = row * row_size; // index of the start of the current row
        let end = (row_count - row - 1) * row_size; // index of the end of the current row
        for i in 0..row_size {
            buffer.swap(start + i, end + i);
        }
    }

    IconData {
        rgba: buffer,
        width,
        height,
    }
}

it works:

fn main() {
    let app = egui_template::WrapperApp::default();
    
    let icon = image::open("test.png").expect("Failed to open icon path").to_rgba8();
    let (icon_width, icon_height) = icon.dimensions();
    
    let options = eframe::NativeOptions {
        icon_data: Some(eframe::epi::IconData {
            rgba: icon.into_raw(),
            width: icon_width,
            height: icon_height,
        }),
        ..Default::default()
    };

    eframe::run_native(Box::new(app), options);
}

yeah I’m unsure why. my ico editor seems to like it just fine.

favicon.zip

@TheMaverickProgrammer Please replace pixel.rotate_right(1); with pixel.swap(0, 2); and see if that works. Your icon colors look right to me now after that

I probably just need to get the pixel order ironed out.

Update: eframe::epi is private. The following worked for me.

    let icon = image::open("static/icon.png")
        .expect("Failed to open icon path")
        .to_rgba8();
    let (icon_width, icon_height) = icon.dimensions();

    let options = eframe::NativeOptions {
        icon_data: Some(eframe::IconData {
            rgba: icon.into_raw(),
            width: icon_width,
            height: icon_height,
        }),
        drag_and_drop_support: true,
        ..Default::default()
    };

    eframe::run_native(
        "App Name",
        options,
        Box::new(|_cc| Box::<NWBView>::default()),
    );

@MolotovCherry Didn’t mean to leave you hanging. I thought it was clear that you fixed the color problem before me! Thank you!

@MolotovCherry thank you. I had to update rust. After getting the dependencies to load I could not get the app to run:

thread 'main' panicked at 'Failed to load icon: Error { code: HRESULT(0x80070715), message: "The specified resource type cannot be found in the image file." }', src\main.rs:39:10 note: run with RUST_BACKTRACE=1 environment variable to display a backtrace

I made sure the icon name was changed and the dimensions were correct. I’m also surprised why we have to re-specify the name and dimensions even with the build.rs compiling the resource file for us. Is there a way to pull that automatically from the resource file? Either way, I’m unsure why I cannot get the icon to load. BTW it is a .ICO file. Any ideas?

@TheMaverickProgrammer Did you define the icon like so? res.set_icon_with_id("file.ico", "your-icon-name-here");

The name you need to specify yourself. The api call can get the dimensions (you can set it to 0x0), but it may be too small to look good at that point. Probably best to just specify a size, e.g. 64x64

@MolotovCherry your toml file looks strange to me. Here’s my attempt to fix it and cargo it’s liking either:

[dependencies.windows]
windows_rs = {
    version = "0.43.0",
    features = [
        "Win32_UI_WindowsAndMessaging",
        "Win32_Foundation",
        "Win32_System_LibraryLoader",
        "Win32_Graphics_Gdi",
        "Win32_UI_Input_KeyboardAndMouse",
        "Win32_System_Threading",
        "Win32_UI_Controls",
        "Win32_Graphics_Dwm",
        "Win32_UI_Shell"
    ]
}

@TheMaverickProgrammer This is the standard toml format recommended by windows-rs and Rust https://github.com/microsoft/windows-rs#rust-for-windows https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features