Here's a complete example of using GetIconInfo()
and GetDIBits()
to extract the image data as an RgbaImage
that can be saved as a PNG file:
/*
[dependencies]
image = "0.24.5"
[dependencies.windows]
version = "0.46.0"
features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_UI_WindowsAndMessaging",
]
*/
use image::RgbaImage;
use std::{
mem::{self, MaybeUninit},
ptr::addr_of_mut,
};
use windows::Win32::{
Foundation::HWND,
Graphics::Gdi::{
DeleteObject, GetDC, GetDIBits, GetObjectW, ReleaseDC, BITMAP, BITMAPINFOHEADER, BI_RGB,
DIB_RGB_COLORS, HDC,
},
UI::WindowsAndMessaging::{GetIconInfo, HICON},
};
unsafe fn icon_to_image(icon: HICON) -> RgbaImage {
let bitmap_size_i32 = i32::try_from(mem::size_of::<BITMAP>()).unwrap();
let biheader_size_u32 = u32::try_from(mem::size_of::<BITMAPINFOHEADER>()).unwrap();
let mut info = MaybeUninit::uninit();
GetIconInfo(icon, info.as_mut_ptr()).unwrap();
let info = info.assume_init_ref();
DeleteObject(info.hbmMask).unwrap();
let mut bitmap: MaybeUninit<BITMAP> = MaybeUninit::uninit();
let result = GetObjectW(
info.hbmColor,
bitmap_size_i32,
Some(bitmap.as_mut_ptr().cast()),
);
assert!(result == bitmap_size_i32);
let bitmap = bitmap.assume_init_ref();
let width_u32 = u32::try_from(bitmap.bmWidth).unwrap();
let height_u32 = u32::try_from(bitmap.bmHeight).unwrap();
let width_usize = usize::try_from(bitmap.bmWidth).unwrap();
let height_usize = usize::try_from(bitmap.bmHeight).unwrap();
let buf_size = width_usize
.checked_mul(height_usize)
.and_then(|size| size.checked_mul(4))
.unwrap();
let mut buf: Vec<u8> = Vec::with_capacity(buf_size);
let dc = GetDC(HWND(0));
assert!(dc != HDC(0));
let mut bitmap_info = BITMAPINFOHEADER {
biSize: biheader_size_u32,
biWidth: bitmap.bmWidth,
biHeight: -bitmap.bmHeight,
biPlanes: 1,
biBitCount: 32,
biCompression: BI_RGB,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
};
let result = GetDIBits(
dc,
info.hbmColor,
0,
height_u32,
Some(buf.as_mut_ptr().cast()),
addr_of_mut!(bitmap_info).cast(),
DIB_RGB_COLORS,
);
assert!(result == bitmap.bmHeight);
buf.set_len(buf.capacity());
let result = ReleaseDC(HWND(0), dc);
assert!(result == 1);
DeleteObject(info.hbmColor).unwrap();
for chunk in buf.chunks_exact_mut(4) {
let [b, _, r, _] = chunk else { unreachable!() };
mem::swap(b, r);
}
RgbaImage::from_vec(width_u32, height_u32, buf).unwrap()
}
// testing:
use windows::{
core::PCWSTR,
Win32::{
Foundation::HINSTANCE,
UI::WindowsAndMessaging::{DestroyIcon, LoadImageW, IMAGE_ICON, LR_DEFAULTCOLOR, OIC_NOTE},
},
};
fn main() {
unsafe {
let icon = LoadImageW(
HINSTANCE(0),
PCWSTR::from_raw(OIC_NOTE as *const u16),
IMAGE_ICON,
0,
0,
LR_DEFAULTCOLOR,
)
.unwrap();
let icon = HICON(icon.0);
let image = icon_to_image(icon);
DestroyIcon(icon).unwrap();
image.save("example.png").unwrap();
}
}
(I do assume here that the hbmColor
bitmap of an HICON
is always a DDB compatible with the screen; I do not see any way for it to be a DIB, or to be compatible with some other DC.)