6. Windows程序图标提取方法

1 介绍

一些效率工具常常需要显示其它应用程序的图标,而找到电脑上其它安装的程序本身不难,难的是怎么才能拿到该程序的图标。

经过我的搜寻,网上并没有好用的现成库,所以本文介绍一下我的实现方案。

2 简单介绍

exe程序图标在编译的时候就已经被编译进了exe文件中,所以想要拿到图标,就得解析exe文件格式,也叫PE文件,其内部构造是比较复杂的,又尤其是各种资源。

但好在有现成的库可以帮我们完成这一解析操作:pelite

这里用的是rust库,其它语言一般也会有对应的库实现。

首先在依赖中引入该库:

[dependencies]
pelite = "0.10.0"

它的使用还是比较简单的:

use pelite::pe32::{Pe, PeFile};
use pelite::FileMap;

fn main() {
    let file_map = FileMap::open(r"D:\Install\TreeSizeProv9.exe").unwrap();
    let file = PeFile::from_bytes(file_map.as_ref()).unwrap_or_else(|e| {
        println!("{}", e);
        panic!();
    });
    let resources = file.resources().unwrap();
    for (name, group) in resources.icons().filter_map(Result::ok) {
        for entry in group.entries() {
            match group.image(entry.nId) {
                Ok(image) => {
                    //得到图片数据
                }
                Err(err) => {
                    println!("{}: Error {}!", name, err)
                }
            }
        }
    }
}

上面的代码基本就是直接复制的该库实例代码,只不过由于我这里暂时是32位的程序,所以最前面导入的是pe32中的包。

提取图标的时候有几点要注意:

  1. 图标文件本身内部并不仅仅只有一张图片,它可能是很多张图片,并且这些图片的格式可以不一样。
  2. 上面循环中得到的图片数据是一个图标文件中的某一张图片,如果该图片为png格式的,那可以直接保存,但如果不是,直接保存是无效的。

图标格式可以参考官方:Icons | Microsoft Learn

说实话icon格式还是比较复杂的,经过我的实践,主要会遇到以下几个问题:

  1. 直接使用上面的示例代码提取图片,会导致没有png图片的icon图标应用提取失败
  2. 直接将icon图片整个保存,会导致某些图片呈现的效果非常模糊

3 图标提取

为了解决上面我遇到的两个问题,经过长时间摸索,最终总结出一个比较完美的方案:

fn get_icon_to_path(path: &str, save_path: &str) -> bool {
    //不是exe文件结尾,直接跳过。
    if !(path.ends_with(".exe") || path.ends_with(".EXE")) {
        return false;
    }
    let file_map = pelite::FileMap::open(path);
    if let Err(_e) = file_map {
        return false;
    }
    let file_map = file_map.unwrap();

    let file = pelite::PeFile::from_bytes(file_map.as_ref());
    if let Err(_e) = file {
        return false;
    }
    let file = file.unwrap();

    let mut icons = file.resources().unwrap().icons();

    let res = icons.next();
    if let None = res {
        return false;
    }
    let res = res.unwrap();
    if let Err(_e) = res {
        return false;
    }
    //只取第一组图标
    let (_, group) = res.unwrap();
    let mut f = false;
    for entry in group.entries() {
        // Fetch the image data for this entry
        match group.image(entry.nId) {
            Ok(image) => {
                // Check if the image data starts with the PNG magic bytes
                if image.starts_with(b"\x89PNG") {
                    std::fs::write(save_path, image).unwrap();
                    f = true;
                }
            }
            Err(_) => {}
        }
    }
    if f {
        return true;
    }
    let mut f = std::fs::File::create(save_path).unwrap();
    match group.write(&mut f) {
        Err(_) => false,
        Ok(_) => true,
    }
}

上面是我写的一个专门用于提取exe程序图标的函数,其中第一个参数为exe文件的路径,第二个参数为图标的保存位置。

逻辑并不难理解:

  1. 首先遍历exe文件中第一组icon中的所有图片,只要为png图片就对其进行保存,一般大图都放在了后面,这样可以保证最后保存的是最大的一张png图片。
  2. 如果前面没有找到png图片,那么就直接将整个icon图标数据写入文件中。
作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux