如何使用Python从exe文件中提取ico/png格式图标文件
从EXE文件提取图标适配Tkinter程序启动器的实现方案
你可以根据自己的需求选下面两种方案,都能拿到可直接给Tkinter按钮用的图标,也支持导出为.ico/.png格式本地文件。
方案1:调用Windows系统原生API(无重型依赖,兼容性最好,推荐)
Windows自带的SHGetFileInfo接口可以直接读取任意exe(包括加壳程序)的图标,和资源管理器显示的图标完全一致,不需要手动解析PE文件结构。
- 依赖:只需要装Pillow做图片格式转换,Tkinter是Python标准库自带
pip install pillow - 可直接复用的代码:
import ctypes from ctypes import wintypes import tkinter as tk from PIL import Image, ImageTk # 系统API常量定义 SHGFI_ICON = 0x100 SHGFI_LARGEICON = 0x0 # 默认32*32大图标 SHGFI_SMALLICON = 0x1 # 16*16小图标 class SHFILEINFO(ctypes.Structure): _fields_ = [ ("hIcon", wintypes.HICON), ("iIcon", ctypes.c_int), ("dwAttributes", wintypes.DWORD), ("szDisplayName", wintypes.WCHAR * 260), ("szTypeName", wintypes.WCHAR * 80), ] def get_exe_icon_for_tk(exe_path: str, icon_size: tuple[int, int] = (36, 36)) -> ImageTk.PhotoImage: """ 输入exe路径,返回可直接赋值给Tkinter组件image属性的图标对象 不需要落地保存图标文件,直接在内存中完成转换 """ shfi = SHFILEINFO() # 获取图标句柄 ctypes.windll.shell32.SHGetFileInfoW( exe_path, 0, ctypes.byref(shfi), ctypes.sizeof(shfi), SHGFI_ICON | SHGFI_LARGEICON ) # 把图标句柄转为PIL Image对象 hdc = ctypes.windll.user32.GetDC(0) hbmp = ctypes.windll.gdi32.CreateCompatibleBitmap(hdc, icon_size[0], icon_size[1]) mem_dc = ctypes.windll.gdi32.CreateCompatibleDC(hdc) old_obj = ctypes.windll.gdi32.SelectObject(mem_dc, hbmp) ctypes.windll.user32.DrawIconEx(mem_dc, 0, 0, shfi.hIcon, icon_size[0], icon_size[1], 0, 0, 0x0003) bmpinfo = ctypes.create_string_buffer(40) ctypes.memmove(bmpinfo, ctypes.byref(ctypes.c_int(40)), 4) ctypes.memmove(ctypes.c_void_p(ctypes.addressof(bmpinfo)+4), ctypes.byref(ctypes.c_int(icon_size[0])), 4) ctypes.memmove(ctypes.c_void_p(ctypes.addressof(bmpinfo)+8), ctypes.byref(ctypes.c_int(icon_size[1])), 4) ctypes.memmove(ctypes.c_void_p(ctypes.addressof(bmpinfo)+12), ctypes.byref(ctypes.c_short(1)), 2) ctypes.memmove(ctypes.c_void_p(ctypes.addressof(bmpinfo)+14), ctypes.byref(ctypes.c_short(32)), 2) pixel_buffer = ctypes.create_string_buffer(icon_size[0] * icon_size[1] * 4) ctypes.windll.gdi32.GetDIBits(mem_dc, hbmp, 0, icon_size[1], pixel_buffer, bmpinfo, 0) # 释放系统资源 ctypes.windll.gdi32.SelectObject(mem_dc, old_obj) ctypes.windll.gdi32.DeleteDC(mem_dc) ctypes.windll.gdi32.DeleteObject(hbmp) ctypes.windll.user32.ReleaseDC(0, hdc) ctypes.windll.user32.DestroyIcon(shfi.hIcon) # 转为Tkinter可用的PhotoImage img = Image.frombuffer("RGBA", icon_size, pixel_buffer, "raw", "BGRA", 0, 1) return ImageTk.PhotoImage(img) # 调用示例:给按钮绑定图标 if __name__ == "__main__": root = tk.Tk() notepad_icon = get_exe_icon_for_tk("C:/Windows/notepad.exe") # 注意:一定要把图片对象绑定到组件属性上,否则会被GC回收导致图标不显示 test_btn = tk.Button(root, text="记事本", image=notepad_icon, compound="left") test_btn.image = notepad_icon test_btn.pack(padx=20, pady=20) root.mainloop()
方案2:使用icoextract库导出本地图标文件(适合需要缓存图标场景)
如果你需要把提取到的图标存成本地.ico/.png文件做缓存,避免每次启动程序都重新提取,可以用专门的icoextract库,它会自动解析exe的PE资源段提取图标。
- 安装依赖:
pip install icoextract pillow - 提取并保存图标的代码:
from icoextract import IconExtractor from PIL import Image import os def export_exe_icon(exe_path: str, save_path: str, target_size: tuple[int,int] = (48,48), fmt: str = "png"): """ 从exe提取主图标,保存为指定格式的本地文件 fmt支持ico/png/jpg等Pillow兼容的图片格式 """ extractor = IconExtractor(exe_path) # 读取主图标的字节流 icon_stream = extractor.get_icon(num=0) # 转成PIL对象调整尺寸 img = Image.open(icon_stream) img = img.resize(target_size, Image.Resampling.LANCZOS) img.save(save_path, format=fmt) # 调用示例 export_exe_icon("C:/Windows/notepad.exe", "./notepad_icon.png", target_size=(48,48), fmt="png")
注意事项
- 部分加壳、加密的exe无法通过解析PE结构的方式提取图标,这种场景优先用方案1,只要资源管理器能正常显示的图标,方案1都能拿到
- Tkinter的PhotoImage对象会被Python垃圾回收机制回收,绑定到按钮后一定要把图片对象赋值给按钮的
image属性或者全局变量,否则按钮会显示空白- 如果不需要缓存图标,优先用方案1,全程内存操作性能更好,不会产生临时文件
内容的提问来源于stack exchange,提问作者Manhunter1920
相关产品推荐
相关产品推荐

