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
中的包。
提取图标的时候有几点要注意:
- 图标文件本身内部并不仅仅只有一张图片,它可能是很多张图片,并且这些图片的格式可以不一样。
- 上面循环中得到的图片数据是一个图标文件中的某一张图片,如果该图片为png格式的,那可以直接保存,但如果不是,直接保存是无效的。
图标格式可以参考官方:Icons | Microsoft Learn。
说实话icon格式还是比较复杂的,经过我的实践,主要会遇到以下几个问题:
- 直接使用上面的示例代码提取图片,会导致没有png图片的icon图标应用提取失败
- 直接将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文件的路径,第二个参数为图标的保存位置。
逻辑并不难理解:
- 首先遍历exe文件中第一组icon中的所有图片,只要为png图片就对其进行保存,一般大图都放在了后面,这样可以保证最后保存的是最大的一张png图片。
- 如果前面没有找到png图片,那么就直接将整个icon图标数据写入文件中。