Search code examples
winapirustexe

Extracting executable's icon in rust


I'm making some code to extract the 64x64px variant of the icon in an exe file. The following code does work, but it outputs a grayscale 16x16px image. I'm still a bit new to rust so please excuse the bad code. I'm using winapi version 0.3.9 with the features: "winuser", "shellapi".

use std::ffi::CString;
use std::ptr;
use iced::widget::image::Handle;
use winapi::shared::minwindef::DWORD;
use winapi::um::shellapi::ExtractIconA;
use winapi::shared::windef::HICON;
use winapi::um::wingdi::{CreateDIBSection, DeleteObject, GetDIBits, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, RGBQUAD};
use winapi::um::winuser::{DestroyIcon, GetIconInfo};
use winapi::um::wingdi::{CreateCompatibleDC, SelectObject};

pub fn exe_icon_to_vec_u8(path: &str) -> Result<Handle, String> {
    let c_path = CString::new(path).map_err(|_| "Invalid path".to_string())?;
    let hicon: HICON = unsafe { ExtractIconA(ptr::null_mut(), c_path.as_ptr(), 0) };
    if hicon.is_null() {
        return Err("Failed to extract icon".into());
    }
    let mut icon_info = unsafe { std::mem::zeroed() };
    if unsafe { GetIconInfo(hicon, &mut icon_info) } == 0 {
        unsafe { DestroyIcon(hicon) };
        return Err("Failed to get icon info".into());
    }
    let mut bmp_info = BITMAPINFO {
        bmiHeader: BITMAPINFOHEADER {
            biSize: std::mem::size_of::<BITMAPINFOHEADER>() as DWORD,
            biWidth: 64, biHeight: -64, biPlanes: 1, biBitCount: 32,
            biCompression: BI_RGB, biSizeImage: 0, biXPelsPerMeter: 0,
            biYPelsPerMeter: 0, biClrUsed: 0, biClrImportant: 0,
        }, bmiColors: [RGBQUAD { rgbBlue: 0, rgbGreen: 0, rgbRed: 0, rgbReserved: 0 }; 1],
    };
    let mut pixels: Vec<u8> = vec![0; 64 * 64 * 4];
    let hdc = unsafe { CreateCompatibleDC(ptr::null_mut()) };
    if hdc.is_null() {
        unsafe {
            DestroyIcon(hicon);
        }
        return Err("Failed to create compatible DC".into());
    }
    let hbitmap = unsafe {
        CreateDIBSection(
            hdc,
            &mut bmp_info,
            DIB_RGB_COLORS,
            &mut pixels.as_mut_ptr() as *mut _ as *mut _,
            ptr::null_mut(),
            0,
        )
    };
    if hbitmap.is_null() {
        unsafe {
            DeleteObject(icon_info.hbmColor as _);
            DeleteObject(icon_info.hbmMask as _);
            DestroyIcon(hicon);
        }
        return Err("Failed to create DIB section".into());
    }
    unsafe {
        SelectObject(hdc, hbitmap as _);
        GetDIBits(
            hdc,
            icon_info.hbmColor,
            0,
            64,
            pixels.as_mut_ptr() as *mut _,
            &mut bmp_info,
            DIB_RGB_COLORS,
        );
        DeleteObject(hbitmap as _);
        DeleteObject(icon_info.hbmColor as _);
        DeleteObject(icon_info.hbmMask as _);
        DestroyIcon(hicon);
    }
    Ok(Handle::from_pixels(64, 64, pixels))
}

I tried to find some cargo packages that do this but with no success.


Solution

  • Here is the answer for anyone in the future:

    use winapi::um::wingdi::{ CreateCompatibleDC, DeleteDC, DeleteObject, GetDIBits, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, RGBQUAD };
    use winapi::um::shellapi::{ SHGetFileInfoW, SHFILEINFOW, SHGFI_ICON, SHGFI_LARGEICON };
    use winapi::um::winbase::{ GlobalAlloc, GlobalLock, GHND, GlobalUnlock, GlobalFree };
    use winapi::um::winuser::{ GetIconInfo, ICONINFO, DestroyIcon };
    use winapi::shared::minwindef::DWORD;
    use iced::widget::image::Handle;
    use widestring::U16CString;
    use std::{mem, ptr, slice};
    
    pub fn extract_icon_as_handle(path: &str) -> Result<Handle, Box<dyn std::error::Error>> {
        unsafe {
            let mut shfi = SHFILEINFOW {
                hIcon: ptr::null_mut(), iIcon: 0,
                dwAttributes: 0,
                szDisplayName: [0; 260],
                szTypeName: [0; 80],
            };
    
            SHGetFileInfoW(
                U16CString::from_str(path)?.as_ptr(),
                0,
                &mut shfi,
                mem::size_of::<SHFILEINFOW>() as DWORD,
                SHGFI_ICON | SHGFI_LARGEICON,
            );
    
            if shfi.hIcon.is_null() {
                return Err("No icon found.".into());
            }
    
            let mut icon_info = ICONINFO {
                fIcon: 0,
                xHotspot: 0,
                yHotspot: 0,
                hbmMask: ptr::null_mut(),
                hbmColor: ptr::null_mut(),
            };
            GetIconInfo(shfi.hIcon, &mut icon_info);
    
            let hdc = CreateCompatibleDC(ptr::null_mut());
    
            let bmp_info_header = BITMAPINFOHEADER {
                biSize: mem::size_of::<BITMAPINFOHEADER>() as DWORD,
                biWidth: 32,
                biHeight: -32, // Negative to indicate a top-down DIB
                biPlanes: 1,
                biBitCount: 32,
                biCompression: BI_RGB as DWORD,
                biSizeImage: 0,
                biXPelsPerMeter: 0,
                biYPelsPerMeter: 0,
                biClrUsed: 0,
                biClrImportant: 0,
            };
    
            let mut bitmap_info = BITMAPINFO {
                bmiHeader: bmp_info_header,
                bmiColors: [RGBQUAD { rgbBlue: 0, rgbGreen: 0, rgbRed: 0, rgbReserved: 0 }; 1],
            };
    
            let bitmap_memory = GlobalAlloc(GHND, (32 * 32 * 4) as usize);
            let bitmap_bits = GlobalLock(bitmap_memory) as *mut u8;
    
            GetDIBits(
                hdc,
                icon_info.hbmColor,
                0,
                32,
                bitmap_bits as *mut _,
                &mut bitmap_info,
                0,
            );
    
            GlobalUnlock(bitmap_memory);
            DeleteDC(hdc);
            DeleteObject(icon_info.hbmColor as _);
            DestroyIcon(shfi.hIcon);
    
            let bitmap_slice = slice::from_raw_parts(bitmap_bits, (32 * 32 * 4) as usize).to_vec();
    
            let mut rgba_slice = vec![0u8; bitmap_slice.len()];
            for i in 0..(32 * 32) {
                let b = bitmap_slice[i * 4 + 0];
                let g = bitmap_slice[i * 4 + 1];
                let r = bitmap_slice[i * 4 + 2];
                let a = bitmap_slice[i * 4 + 3];
                rgba_slice[i * 4 + 0] = r;
                rgba_slice[i * 4 + 1] = g;
                rgba_slice[i * 4 + 2] = b;
                rgba_slice[i * 4 + 3] = a;
            }
    
            GlobalFree(bitmap_memory);
    
            let handle = Handle::from_pixels(32, 32, rgba_slice);
    
            return Ok(handle);
        }
    }