Rust提取EXE文件64x64图标异常:输出16x16灰度图
问题描述
编写Rust代码提取EXE文件中64x64px规格的图标,代码可运行,但输出的却是16x16px的灰度图。使用的是0.3.9版本的winapi库,启用了"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)) }
尝试寻找可实现该功能的Cargo包,但未找到合适的。
问题根源
ExtractIconA的限制:该函数默认提取的是16x16的小图标,无法指定目标图标尺寸,所以你拿到的本来就是小图标,后续强制按64x64处理只会拉伸成模糊的灰度图。- 错误的位图读取方式:直接读取
icon_info.hbmColor没有正确处理图标的Alpha通道和绘制流程,导致颜色信息丢失,呈现灰度效果。
修正方案
改用ExtractIconExA来指定提取大尺寸图标(64x64),然后通过DrawIconEx将图标绘制到兼容DC中,再通过GetDIBits获取正确的像素数据。
修正后的代码:
use std::ffi::CString; use std::ptr; use iced::widget::image::Handle; use winapi::shared::minwindef::{DWORD, FALSE, TRUE}; use winapi::um::shellapi::ExtractIconExA; use winapi::shared::windef::{HICON, HDC}; use winapi::um::wingdi::{CreateDIBSection, DeleteObject, GetDIBits, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, RGBQUAD}; use winapi::um::winuser::{DestroyIcon, DrawIconEx, DI_NORMAL}; 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())?; // 提取64x64的大图标 let mut hicon_large: HICON = ptr::null_mut(); let count = unsafe { ExtractIconExA( c_path.as_ptr(), 0, &mut hicon_large, // 大图标指针 ptr::null_mut(), // 小图标指针(不需要则传空) 1 // 提取图标数量 ) }; if count == 0 || hicon_large.is_null() { return Err("Failed to extract 64x64 icon".into()); } let mut bmp_info = BITMAPINFO { bmiHeader: BITMAPINFOHEADER { biSize: std::mem::size_of::<BITMAPINFOHEADER>() as DWORD, biWidth: 64, biHeight: -64, // 负数表示从上到下的像素顺序 biPlanes: 1, biBitCount: 32, // 32位带Alpha通道 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_large) }; 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 { DestroyIcon(hicon_large); DeleteObject(hdc as _); } return Err("Failed to create DIB section".into()); } unsafe { // 将DIB选入DC let old_bmp = SelectObject(hdc, hbitmap as _); // 绘制图标到DC let success = DrawIconEx( hdc, 0, 0, hicon_large, 64, 64, 0, ptr::null_mut(), DI_NORMAL ); if success == FALSE { DeleteObject(hbitmap as _); SelectObject(hdc, old_bmp); DeleteObject(hdc as _); DestroyIcon(hicon_large); return Err("Failed to draw icon".into()); } // 从DC获取像素数据 GetDIBits( hdc, hbitmap, 0, 64, pixels.as_mut_ptr() as *mut _, &mut bmp_info, DIB_RGB_COLORS, ); // 清理资源 SelectObject(hdc, old_bmp); DeleteObject(hbitmap as _); DeleteObject(hdc as _); DestroyIcon(hicon_large); } Ok(Handle::from_pixels(64, 64, pixels)) }
关键修改点
- 替换
ExtractIconA为ExtractIconExA,明确提取大尺寸图标; - 使用
DrawIconEx将图标绘制到兼容DC中,确保获取到正确尺寸和颜色的图像; - 调整资源清理逻辑,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Barnabás
相关产品推荐
相关产品推荐

