赞
踩
我有个程序有获取图标的需求,但是我不想使用pywin32这个大家伙,于是找上了同样可以调用winapi的ctypes。
以下是参考链接:
https://www.zhihu.com/question/425053417
https://www.cnblogs.com/ibingshan/p/11057390.html
mss.windows 模块
从zhihu和cnblogs找到了使用pywin32和ctypes的两个例子:
# zhihu 作者:lollipopnougat 链接:https://www.zhihu.com/question/425053417/answer/1524323338 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 import ctypes from ctypes.wintypes import HICON, LPCSTR, UINT, INT import clr import os # 简写 ExIcon = ctypes.windll.user32.PrivateExtractIconsA DesIcon = ctypes.windll.user32.DestroyIcon # 加载 .Net Drawing 程序集 clr.AddReference('System.Drawing') from System import IntPtr from System.Drawing import Icon from System.Drawing.Imaging import ImageFormat from System import Int32 # path: 资源文件(可以是exe、dll等)绝对路径, out_dir: 导出图标文件夹绝对路径 返回实际导出的图标数 def save_icons_from_resfile(path: str, out_dir: str): # 输出文件夹是否存在 if not os.path.exists(out_dir): return -1 # 指定 PrivateExtractIconsA 返回类型 UINT ExIcon.restype = UINT # 获取内含图标总数,注意path需要转bytes类型 icon_total_count = ExIcon(path.encode(), 0, 0, 0, None, None, 0, 0) # 指定 PrivateExtractIconsA 参数类型 ExIcon.argtypes = [ LPCSTR, INT, INT, INT, ctypes.POINTER(HICON * icon_total_count), ctypes.POINTER(UINT * icon_total_count), UINT, UINT ] # 初始化 Ctypes HICON 数组(存储导出的图标句柄) hIconArray = HICON * icon_total_count hicons = hIconArray() # 获取数组指针 p_hicons = ctypes.pointer(hicons) # 初始化 Ctypes UINT 数组(存储导出的图标ID) IDArray = UINT * icon_total_count ids = IDArray() p_ids = ctypes.pointer(ids) # 导出最大的图标(icon最大支持256*256),返回值为成功导出的图标数 success_count = ExIcon(path.encode(), 0, 256, 256, p_hicons, p_ids, icon_total_count, 0) # 实际图标数 actual_count = 0 for i in range(success_count): # 如果图标句柄是NULL if hicons[i] == 0: continue actual_count += 1 # .Net 类库解决...幸好之前写过C#解决类似问题的程序 hicon = IntPtr.Add(IntPtr.Zero, hicons[i]) ico = Icon.FromHandle(hicon) mybitmap = ico.ToBitmap() cs_id = Int32(ids[i]) mybitmap.Save(out_dir + '\\' + str(cs_id.ToString('000')) + '.ico', ImageFormat.Icon) # 用完后记得销毁 ICON 对象(句柄) DesIcon.argtypes = [HICON] hicon_c = HICON(hicons[i]) DesIcon(hicon_c) return actual_count
#cnblogs import win32ui import win32gui import win32con import win32api #ico_x = win32api.GetSystemMetrics(win32con.SM_CXICON) #ico_y = win32api.GetSystemMetrics(win32con.SM_CYICON) ico_x = 32 ico_y = 32 exePath = "c:/windows/system32/shell32.dll" large, small = win32gui.ExtractIconEx(exePath, 0) useIcon = large[0] destroyIcon = small[0] win32gui.DestroyIcon(destroyIcon) hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0)) hbmp = win32ui.CreateBitmap() hbmp.CreateCompatibleBitmap(hdc, ico_x, ico_x) hdc = hdc.CreateCompatibleDC() hdc.SelectObject(hbmp) hdc.DrawIcon((0,0), useIcon) savePath = "d:/test.bmp" hbmp.SaveBitmapFile(hdc, savePath)
本来lollipopnougat的已经很好了,但他不能使用winapi保存,使用了.net类库,但我不想这样。
随后我以lollipopnougat为母本,以cnblogs为参考,开始了改进。
我曾经分享过python截图库mss(文章)其中mss.windows可以实现纯win32api获取raw数据并保存。那么我们能否借用一下呢?mss github上面写了,mss是以MIT许可发布的,这意味着我们可以使用它的代码。
在Mss._grab_impl函数中,我们能看到gdi32的BitBlt和GetDIBits,用这些函数可以得到。将mss中有关代码复制下来:
from ctypes import POINTER, Structure, WINFUNCTYPE, c_void_p from ctypes.wintypes import ( BOOL, DOUBLE, DWORD, HBITMAP, HDC, HGDIOBJ, HWND, INT, LONG, LPARAM, RECT, UINT, WORD, ) CAPTUREBLT = 0x40000000 DIB_RGB_COLORS = 0 SRCCOPY = 0x00CC0020 class BITMAPINFOHEADER(Structure): """ Information about the dimensions and color format of a DIB. """ _fields_ = [ ("biSize", DWORD), ("biWidth", LONG), ("biHeight", LONG), ("biPlanes", WORD), ("biBitCount", WORD), ("biCompression", DWORD), ("biSizeImage", DWORD), ("biXPelsPerMeter", LONG), ("biYPelsPerMeter", LONG), ("biClrUsed", DWORD), ("biClrImportant", DWORD), ] class BITMAPINFO(Structure): """ Structure that defines the dimensions and color information for a DIB. """ _fields_ = [("bmiHeader", BITMAPINFOHEADER), ("bmiColors", DWORD * 3)] # ...... def __init__(self, **_): # type: (Any) -> None """ Windows initialisations. """ super().__init__() self.user32 = ctypes.WinDLL("user32") self.gdi32 = ctypes.WinDLL("gdi32") self._set_cfunctions() self._set_dpi_awareness() self._bbox = {"height": 0, "width": 0} self._data = ctypes.create_string_buffer(0) # type: ctypes.Array[ctypes.c_char] srcdc = self._get_srcdc() if not MSS.memdc: MSS.memdc = self.gdi32.CreateCompatibleDC(srcdc) bmi = BITMAPINFO() bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER) bmi.bmiHeader.biPlanes = 1 # Always 1 bmi.bmiHeader.biBitCount = 32 # See grab.__doc__ [2] bmi.bmiHeader.biCompression = 0 # 0 = BI_RGB (no compression) bmi.bmiHeader.biClrUsed = 0 # See grab.__doc__ [3] bmi.bmiHeader.biClrImportant = 0 # See grab.__doc__ [3] self._bmi = bmi def _get_srcdc(self): """ Retrieve a thread-safe HDC from GetWindowDC(). In multithreading, if the thread who creates *srcdc* is dead, *srcdc* will no longer be valid to grab the screen. The *srcdc* attribute is replaced with *_srcdc_dict* to maintain the *srcdc* values in multithreading. Since the current thread and main thread are always alive, reuse their *srcdc* value first. """ cur_thread, main_thread = threading.current_thread(), threading.main_thread() srcdc = MSS._srcdc_dict.get(cur_thread) or MSS._srcdc_dict.get(main_thread) if not srcdc: srcdc = MSS._srcdc_dict[cur_thread] = self.user32.GetWindowDC(0) return srcdc def _grab_impl(self, monitor): # type: (Monitor) -> ScreenShot """ Retrieve all pixels from a monitor. Pixels have to be RGB. In the code, there are few interesting things: [1] bmi.bmiHeader.biHeight = -height A bottom-up DIB is specified by setting the height to a positive number, while a top-down DIB is specified by setting the height to a negative number. https://msdn.microsoft.com/en-us/library/ms787796.aspx https://msdn.microsoft.com/en-us/library/dd144879%28v=vs.85%29.aspx [2] bmi.bmiHeader.biBitCount = 32 image_data = create_string_buffer(height * width * 4) We grab the image in RGBX mode, so that each word is 32bit and we have no striding. Inspired by https://github.com/zoofIO/flexx [3] bmi.bmiHeader.biClrUsed = 0 bmi.bmiHeader.biClrImportant = 0 When biClrUsed and biClrImportant are set to zero, there is "no" color table, so we can read the pixels of the bitmap retrieved by gdi32.GetDIBits() as a sequence of RGB values. Thanks to http://stackoverflow.com/a/3688682 """ srcdc, memdc = self._get_srcdc(), MSS.memdc width, height = monitor["width"], monitor["height"] if (self._bbox["height"], self._bbox["width"]) != (height, width): self._bbox = monitor self._bmi.bmiHeader.biWidth = width self._bmi.bmiHeader.biHeight = -height # Why minus? [1] self._data = ctypes.create_string_buffer(width * height * 4) # [2] if MSS.bmp: self.gdi32.DeleteObject(MSS.bmp) MSS.bmp = self.gdi32.CreateCompatibleBitmap(srcdc, width, height) self.gdi32.SelectObject(memdc, MSS.bmp) self.gdi32.BitBlt( memdc, 0, 0, width, height, srcdc, monitor["left"], monitor["top"], SRCCOPY | CAPTUREBLT, ) bits = self.gdi32.GetDIBits( memdc, MSS.bmp, 0, height, self._data, self._bmi, DIB_RGB_COLORS ) if bits != height: raise ScreenShotError("gdi32.GetDIBits() failed.") return self.cls_image(bytearray(self._data), monitor)
进行修改
如果你按照上面的一步步来,应该能得到下面的代码
import ctypes from ctypes.wintypes import HICON, LPCSTR, UINT, INT ExIcon = ctypes.windll.user32.PrivateExtractIconsA DesIcon = ctypes.windll.user32.DestroyIcon from ctypes import POINTER, Structure, WINFUNCTYPE, c_void_p from ctypes.wintypes import ( BOOL, DOUBLE, DWORD, HBITMAP, HDC, HGDIOBJ, HWND, INT, LONG, LPARAM, RECT, UINT, WORD, ) SRCCOPY = 0x00CC0020 user32 = ctypes.windll.user32 gdi32 = ctypes.windll.gdi32 class BITMAPINFOHEADER(Structure): " From mss.windows " _fields_ = [ ("biSize", DWORD), ("biWidth", LONG), ("biHeight", LONG), ("biPlanes", WORD), ("biBitCount", WORD), ("biCompression", DWORD), ("biSizeImage", DWORD), ("biXPelsPerMeter", LONG), ("biYPelsPerMeter", LONG), ("biClrUsed", DWORD), ("biClrImportant", DWORD), ] class BITMAPINFO(Structure): " From mss.windows " _fields_ = [("bmiHeader", BITMAPINFOHEADER), ("bmiColors", DWORD * 3)] def get(path): path = os.path.abspath(path) # From zhihu width = height = size ExIcon.restype = UINT icon_total_count = ExIcon(path.encode(), 0, 0, 0, None, None, 0, 0) ExIcon.argtypes = [ LPCSTR, INT, INT, INT, ctypes.POINTER(HICON * icon_total_count), ctypes.POINTER(UINT * icon_total_count), UINT, UINT ] hIconArray = HICON * icon_total_count hicons = hIconArray() p_hicons = ctypes.pointer(hicons) IDArray = UINT * icon_total_count ids = IDArray() p_ids = ctypes.pointer(ids) success_count = ExIcon(path.encode(), 0, 32, 32, p_hicons, p_ids, icon_total_count, 0) # From mss.windows srcdc = user32.GetWindowDC(0) memdc = gdi32.CreateCompatibleDC(srcdc) bmp = gdi32.CreateCompatibleBitmap(srcdc, width, height) gdi32.SelectObject(memdc,bmp) bmi = BITMAPINFO() bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER) bmi.bmiHeader.biPlanes = 1 # Always 1 bmi.bmiHeader.biBitCount = 32 # See grab.__doc__ [2] bmi.bmiHeader.biCompression = 0 # 0 = BI_RGB (no compression) bmi.bmiHeader.biClrUsed = 0 # See grab.__doc__ [3] bmi.bmiHeader.biClrImportant = 0 # See grab.__doc__ [3] bmi.bmiHeader.biWidth = width bmi.bmiHeader.biHeight = -height # Why minus? [1] data = ctypes.create_string_buffer(width * height * 4) self.gdi32.BitBlt( memdc, 0, 0, width, height, srcdc, 0, 0, SRCCOPY | CAPTUREBLT, ) bits = gdi32.GetDIBits( memdc, bmp, 0, height, data, ctypes.byref(bmi), 0 ) gdi32.DeleteObject(bmp) if bits != height: raise Exception("gdi32.GetDIBits() failed.") return bytearray(data) def rgb(raw,width=32,height=32): # From mss.screenshot rgb = bytearray(height * width * 3) rgb[0::3] = raw[2::4] rgb[1::3] = raw[1::4] rgb[2::3] = raw[0::4] return bytes(rgb) # 测试 data = rgb(get("C:\\windows\\explorer.exe",),32,32) import mss.tools mss.tools.to_png(data,(32,32),8,"explorer.png")
运行一下试试,你会发现,有问题!截取的是屏幕左上角32x32像素的内容!
出问题以后,我研究了2个多小时,从cnblogs中的代码得到启发:BitBlt是截屏,不能使用;DrawIcon才是重点。hicons中的数据是DrawIcon中被画近bmp的数据。
有了这个结论,就可以得到以下代码(已优化,可之间使用):
""" use ctypes to get image from exe/dll ref: https://www.zhihu.com/question/425053417 https://www.cnblogs.com/ibingshan/p/11057390.html mss.windows (mss grab library) """ import platform, os system = platform.system().lower() if system != "windows" and os.environ["IGNORE_SYSTEMCHECK"] != "True": raise Exception("GETICON is only avaliable on Windows!") import ctypes from ctypes.wintypes import HICON, LPCSTR, UINT, INT ExIcon = ctypes.windll.user32.PrivateExtractIconsA DesIcon = ctypes.windll.user32.DestroyIcon from ctypes import POINTER, Structure, WINFUNCTYPE, c_void_p from ctypes.wintypes import ( BOOL, DOUBLE, DWORD, HBITMAP, HDC, HGDIOBJ, HWND, INT, LONG, LPARAM, RECT, UINT, WORD, ) SRCCOPY = 0x00CC0020 user32 = ctypes.windll.user32 gdi32 = ctypes.windll.gdi32 class BITMAPINFOHEADER(Structure): " From mss.windows " _fields_ = [ ("biSize", DWORD), ("biWidth", LONG), ("biHeight", LONG), ("biPlanes", WORD), ("biBitCount", WORD), ("biCompression", DWORD), ("biSizeImage", DWORD), ("biXPelsPerMeter", LONG), ("biYPelsPerMeter", LONG), ("biClrUsed", DWORD), ("biClrImportant", DWORD), ] class BITMAPINFO(Structure): " From mss.windows " _fields_ = [("bmiHeader", BITMAPINFOHEADER), ("bmiColors", DWORD * 3)] def rgb(raw,width=32,height=32): # From mss.screenshot rgb = bytearray(height * width * 3) rgb[0::3] = raw[2::4] rgb[1::3] = raw[1::4] rgb[2::3] = raw[0::4] return bytes(rgb) def get_raw_data(path,index=0,size=32): path = os.path.abspath(path) # From zhihu width = height = size ExIcon.restype = UINT icon_total_count = ExIcon(path.encode(), 0, 0, 0, None, None, 0, 0) ExIcon.argtypes = [ LPCSTR, INT, INT, INT, ctypes.POINTER(HICON * icon_total_count), ctypes.POINTER(UINT * icon_total_count), UINT, UINT ] hIconArray = HICON * icon_total_count hicons = hIconArray() p_hicons = ctypes.pointer(hicons) IDArray = UINT * icon_total_count ids = IDArray() p_ids = ctypes.pointer(ids) success_count = ExIcon(path.encode(), 0, 32, 32, p_hicons, p_ids, icon_total_count, 0) # From mss.windows srcdc = user32.GetWindowDC(0) memdc = gdi32.CreateCompatibleDC(srcdc) bmp = gdi32.CreateCompatibleBitmap(srcdc, width, height) gdi32.SelectObject(memdc,bmp) user32.DrawIcon(memdc,0,0, hicons[index]) #From cnblogs bmi = BITMAPINFO() bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER) bmi.bmiHeader.biPlanes = 1 # Always 1 bmi.bmiHeader.biBitCount = 32 # See grab.__doc__ [2] bmi.bmiHeader.biCompression = 0 # 0 = BI_RGB (no compression) bmi.bmiHeader.biClrUsed = 0 # See grab.__doc__ [3] bmi.bmiHeader.biClrImportant = 0 # See grab.__doc__ [3] bmi.bmiHeader.biWidth = width bmi.bmiHeader.biHeight = -height # Why minus? [1] data = ctypes.create_string_buffer(width * height * 4) bits = gdi32.GetDIBits( memdc, bmp, 0, height, data, ctypes.byref(bmi), 0 ) gdi32.DeleteObject(bmp) if bits != height: raise Exception("gdi32.GetDIBits() failed.") return bytearray(data) def get_rgb_data(path): return rgb(get_raw_data(path)) if __name__ == "__main__": data = rgb(get_raw_data("C:\\windows\\explorer.exe",0,32),32,32) import mss.tools mss.tools.to_png(data,(32,32),8,"explorer.png")
忘写DestroyIcon了,不过也差不多了吧,收工。
多查资料,还要会用
本文发于CSDN于 2022/8/10 12:32
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。