You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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包,但未找到合适的。

问题根源
  1. ExtractIconA的限制:该函数默认提取的是16x16的小图标,无法指定目标图标尺寸,所以你拿到的本来就是小图标,后续强制按64x64处理只会拉伸成模糊的灰度图。
  2. 错误的位图读取方式:直接读取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:35:01