使用Python保存屏幕截图(不使用PIL)
起因
在极客学院讲授《使用Python编写远程控制程序》的课程中,涉及到查看被控制电脑屏幕截图的功能。
如果使用PIL,这个需求只需要三行代码:
from PIL import ImageGrab
pic = ImageGrab.grab()
pic.save('1.jpg')
但是考虑到被控端应该尽量的精简,对其他模块尽量少的依赖,这样才能比较方便的部署,因此我考虑能否有一种方法,不依赖PIL来实现截图的功能。
思路
由于被控端使用了win32api, 因此有一个方法:
win32api.keybd_event
这个方法可以模拟键盘的按键动作。因此,解决方法就比较的明显了:
- 模拟键盘上面的“Print Screen” 键按下
- 从剪贴板中读取出截图
- 将截图保存到本地
第一步非常的简单,实用win32api 和 win32con,两行代码就能实现:
import win32api
import win32con
win32api.keybd_event(win32con.VK_SNAPSHOT, 0)
其中win32con这个库里面包含了很多定义好的和Windows相关的常量,而VK_SNAPSHOT就是Print Screen键的键位码。后面的数字0表示截取整个屏幕。如果改成数字1,表示截取当前窗口。
那么现在问题来了,在不实用PIL的情况下,如何将剪贴板你们的图片保存到本地?
win32api有一个模块 win32clipboard 是负责剪贴板相关的操作。它有一个方法:
win32clipboard.GetClipboardData(formats)
这个方法可以从剪贴板里面读取数据。但是需要指定数据的格式。从这里可以查看到更多的标准剪贴板格式(Standard Clipboard Formats).
一开始我使用的formats是CF_BITMAP,程序返回的是一串整数,怀疑应该是一个内存地址。这也和这个format的描述:
A handle to a bitmap (HBITMAP).
是一致的,它是一个handle。
我也尝试过CF_TIFF, 不过程序直接报错了,可见我使用Print Screen截图以后,剪贴板里面的图片格式并不是TIFF。
经过查阅其他资料,我最后确定使用了CF_DIB。
A memory object containing a BITMAPINFO structure followed by the bitmap bits.
这个描述说明,CF_DIB返回的是一个内存对象,包含了BIT格式图片的信息。经过测试使用:
win32clipboard.GetClipboardData(win32con.CF_DIB)
以后,可以得到一个很大的字符串。显然这个字符串就是图片的内容了。但是当我把这个字符串写入到bmp格式的文件后,却发现图片无法打开。
解决办法
在StackOverflow上,我遇到了一个非常好的老先生: Mr. martineau他为了解答了问题,并给我提供了解决办法。以下内容翻译自martineau先生的回答,原文请戳->http://stackoverflow.com/a/35885108/3922976
你的方法的主要问题在于,你写入文件的字符串缺少了.bmp 文件头,这个文件头是
BITMAPFILEHEADER结构。
为了创建这个文件头,使用
GetClipboardData()返回的字符串必须要进行解码(decoded)。对于CF_DIB格式来说,返回的字符串的前面一部分就是BOTMAPINFOHEADER。
对于各种各样有不同种类压缩的
DIB来说,这种文件头结构是非常的普遍的。不过幸好对截图来说,只需要简单的无压缩的RGBA像素。
由于
BOTMAPFILEHEADER被放在了bf0ffBits的区域里,所以事情就变得很容易了。而其他的情况,例如大尺度的颜色表跟在BITMAPINFOHEADER和像素数组的开头。
(这一段我看不太懂,还请如果有能正确解释这段话的朋友指正。原文是:
That fact makes things much easier because otherwise determining the value to put in the bfOffBits field of the BITMAPFILEHEADER would be complicated by the fact that in most other cases there's also a variably-sized color table following the BITMAPINFOHEADER and the start of the pixel array.)
下面的代码是一个简单的例子(仅仅针对这个需求):
import ctypes
from ctypes.wintypes import *
import win32clipboard
from win32con import *
import sys
class BITMAPFILEHEADER(ctypes.Structure):
_pack_ = 1 # structure field byte alignment
_fields_ = [
('bfType', WORD), # file type ("BM")
('bfSize', DWORD), # file size in bytes
('bfReserved1', WORD), # must be zero
('bfReserved2', WORD), # must be zero
('bfOffBits', DWORD), # byte offset to the pixel array
]
SIZEOF_BITMAPFILEHEADER = ctypes.sizeof(BITMAPFILEHEADER)
class BITMAPINFOHEADER(ctypes.Structure):
_pack_ = 1 # structure field byte alignment
_fields_ = [
('biSize', DWORD),
('biWidth', LONG),
('biHeight', LONG),
('biPLanes', WORD),
('biBitCount', WORD),
('biCompression', DWORD),
('biSizeImage', DWORD),
('biXPelsPerMeter', LONG),
('biYPelsPerMeter', LONG),
('biClrUsed', DWORD),
('biClrImportant', DWORD)
]
SIZEOF_BITMAPINFOHEADER = ctypes.sizeof(BITMAPINFOHEADER)
win32clipboard.OpenClipboard()
try:
if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB):
data = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
else:
print('clipboard does not contain an image in DIB format')
sys.exit(1)
finally:
win32clipboard.CloseClipboard()
bmih = BITMAPINFOHEADER()
ctypes.memmove(ctypes.pointer(bmih), data, SIZEOF_BITMAPINFOHEADER)
if bmih.biCompression != BI_BITFIELDS: # RGBA?
print('insupported compression type {}'.format(bmih.biCompression))
sys.exit(1)
bmfh = BITMAPFILEHEADER()
ctypes.memset(ctypes.pointer(bmfh), 0, SIZEOF_BITMAPFILEHEADER) # zero structure
bmfh.bfType = ord('B') | (ord('M') << 8)
bmfh.bfSize = SIZEOF_BITMAPFILEHEADER + len(data) # file size
SIZEOF_COLORTABLE = 0
bmfh.bfOffBits = SIZEOF_BITMAPFILEHEADER + SIZEOF_BITMAPINFOHEADER + SIZEOF_COLORTABLE
bmp_filename = 'clipboard.bmp'
with open(bmp_filename, 'wb') as bmp_file:
bmp_file.write(bmfh)
bmp_file.write(data)
print('file "{}" created from clipboard image'.format(bmp_filename))
经过测试,这一段代码成功的实现了读取剪贴板的图片并保存到本地。
分析
这段代码使用ctypes库来实现指针的功能,从而在内存中操作数据。这里定义了两个结构体,BITMAPFILEHEADER 和BITMAPINFOHEADER,于是,使用sizeof获取到了他们的大小。那么使用指针,从使用GetClipboardData()获取到的数据的头部开始移动,分别移动这两个结构体的大小,也就获取到了这两个结构体在内存中的数据。
代码中使用了memmove和memset两个内存操作的方法。从ctypes的官方文档上,我们可以看到这两个方法有如下的定义:
ctypes.memmove(dst, src, count)
Same as the standard C memmove library function: copies count bytes from src to dst. dst and src must be integers or ctypes instances that can be converted to pointers.
ctypes.memset(dst, c, count)
Same as the standard C memset library function: fills the memory block at address dst with count bytes of value c. dst must be an integer specifying an address, or a ctypes instance.
所以可以看出,代码里面的:
bmih = BITMAPINFOHEADER()
ctypes.memmove(ctypes.pointer(bmih), data, SIZEOF_BITMAPINFOHEADER)
从内存中拷贝出来了BITMAPINFOHEADER这么大的一块的数据,并保存到了bmih这个变量中。
bmfh = BITMAPFILEHEADER()
ctypes.memset(ctypes.pointer(bmfh), 0, SIZEOF_BITMAPFILEHEADER)
这一段在内存中开辟出了BITMAPFILEHEADER这么大一块区域,并全部填充为0.
bmfh.bfType = ord('B') | (ord('M') << 8)
这一行代码使用了位操作。首先ord('B')的值为66,换成二进制就是1000010;ord('M')的值为77,换成二进制就是1001101,然后向左移动8位,得到100110100000000,这个值再与1000010取位或,得到100110101000010。
最后,使用:
bmfh.bfOffBits = SIZEOF_BITMAPFILEHEADER + SIZEOF_BITMAPINFOHEADER + SIZEOF_COLORTABLE
拼装出头部的大小。然后以二进制方式,首先写文件头, 再写剪贴板获取到的字符串到本地的.bmp文件中,完成图片的生成。
总结
Python一些轮子确实非常好的提高了开发效率,例如PIL,三行代码实现了我的需求。Python在快速开发方面确实非常的方便,但是涉及到底层的一些操作的时候,还是不得不使用C语言的一些接口来进行内存的操作。
使用Python保存屏幕截图(不使用PIL)的更多相关文章
- Python实现屏幕截图的两种方式
Python实现屏幕截图的两种方式 使用windows API 使用PIL中的ImageGrab模块 下面对两者的特点和用法进行详细解释. 一.Python调用windows API实现屏幕截图 好处 ...
- python爬虫基础15-python图像处理,PIL库
Python图像处理-Pillow 简介 Python传统的图像处理库PIL(Python Imaging Library ),可以说基本上是Python处理图像的标准库,功能强大,使用简单. 但是由 ...
- Python 保存数据的方法(4种方法)
Python 保存数据的方法: open函数保存 使用with open()新建对象 写入数据(这里使用的是爬取豆瓣读书中一本书的豆瓣短评作为例子) import requests from lxml ...
- 使用python获得屏幕截图并保存为位图文件
直接上代码: import win32gui import win32ui from ctypes import windll import Image hwnd = win32gui.FindWin ...
- [辛酸历程]在Mac中使用Python获取屏幕截图
一.起因 最近想做个小外挂玩玩,技术倒是不难,就是通过图片匹配加上一些判断方法来刷分.但是在最不起眼(却最容易出问题)的准备阶段卡住了. 为什么卡住了呢,简单说,因为我需要获取截屏的数据,所以就要找一 ...
- [Python] 图像简单处理(PIL or Pillow)
前几天弄了下django的图片上传,上传之后还需要做些简单的处理,python中PIL模块就是专门用来做这个事情的. 于是照葫芦画瓢做了几个常用图片操作,在这里记录下,以便备用. 这里有个字体文件,大 ...
- Python图片处理库之PIL
这个模块对于Python2.7 的windows64位电脑而言,还真的是不好找啊.这里分享一个下载链接吧,需要的朋友可以下载下来.PIL For Windows64 Python2.7下面分享一下这个 ...
- python图像处理:pytesseract和PIL
大概介绍下相关模块的概念: Python-tesseract 是光学字符识别Tesseract OCR引擎的Python封装类.能够读取任何常规的图片文件(JPG, GIF ,PNG , TIFF等) ...
- python抠图与pip install PIL报错
窗口命令pip install PIL(python3.6+selenium——2.53.1+pycharm) from PIL import Image from selenium import w ...
随机推荐
- 03.SQLServer性能优化之---存储优化系列
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 概 述:http://www.cnblogs.com/dunitian/p/60413 ...
- Android Studio配置 AndroidAnnotations——Hi_博客 Android App 开发笔记
以前用Eclicps 用习惯了现在 想学学 用Android Studio 两天的钻研终于 在我电脑上装了一个Android Studio 并完成了AndroidAnnotations 的配置. An ...
- 谈一下关于CQRS架构如何实现高性能
CQRS架构简介 前不久,看到博客园一位园友写了一篇文章,其中的观点是,要想高性能,需要尽量:避开网络开销(IO),避开海量数据,避开资源争夺.对于这3点,我觉得很有道理.所以也想谈一下,CQRS架构 ...
- 实现代理设置proxy
用户在哪些情况下是需要设置网络代理呢? 1. 内网上不了外网,需要连接能上外网的内网电脑做代理,就能上外网:多个电脑共享上外网,就要用代理: 2.有些网页被封,通过国外的代理就能看到这被封的网站:3. ...
- 谈谈一些有趣的CSS题目(二)-- 从条纹边框的实现谈盒子模型
开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉 ...
- JS判断鼠标进入容器方向的方法和分析window.open新窗口被拦截的问题
1.鼠标进入容器方向的判定 判断鼠标从哪个方向进入元素容器是一个经常碰到的问题,如何来判断呢?首先想到的是:获取鼠标的位置,然后经过一大堆的if..else逻辑来确定.这样的做法比较繁琐,下面介绍两种 ...
- 编写高质量代码:改善Java程序的151个建议(第8章:多线程和并发___建议126~128)
建议126:适时选择不同的线程池来实现 Java的线程池实现从根本上来说只有两个:ThreadPoolExecutor类和ScheduledThreadPoolExecutor类,这两个类还是父子关系 ...
- javascript排序
利用array中的sort()排序 w3cfunction sortNumber(a,b) { return a - b } var arr = new Array(6) arr[0] = " ...
- SpringMvc中初始化参数绑定
初始化参数绑定与类型转换很类似,初始化绑定时,主要是参数类型 ---单日期 在处理器类中配置绑定方法 使用@InitBinder注解 在这里首先注册一个用户编辑器 参数一为目标类型 proper ...
- HTML5- Canvas入门(五)
今天要介绍的是canvas对图形对象的操作,包括图像.视频绘制,和操作像素对象的方法. 图片/视频的绘制 在canvas中,我们可以通过 drawImage() 的方法来绘制图片或视频文件,其语法为: ...