起因

在极客学院讲授《使用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屏幕截图并保存_用Python保存屏幕截图(不使用PIL)相关推荐

  1. python绘图内容怎么保存_将绘图保存到图像文件,而不是使用Matplotlib显示 - python...

    我正在编写一个快速脚本来动态生成绘图.我使用下面的代码(来自Matplotlib文档)作为起点: from pylab import figure, axes, pie, title, show # ...

  2. python截图黑屏_对Python获取屏幕截图的4种方法详解

    Python获取电脑截图有多种方式,具体如下: PIL中的ImageGrab模块 windows API PyQt pyautogui PIL中的ImageGrab模块 import time imp ...

  3. python大牛 关东升_《Python从小白到大牛》第4章 Python语法基础

    本章主要为大家介绍Python的一些语法,其中包括标识符.关键字.常量.变量.表达式.语句.注释.模块和包等内容. 标识符和关键字 任何一种计算机语言都离不开标识符和关键字,因此下面将详细介绍Pyth ...

  4. python剪辑视频 裁剪_用python进行视频剪辑

    一.目标 python,利用moviepy和pydub将一段视频进行区间切割 二.源码 import os from moviepy.video.io.VideoFileClip import Vid ...

  5. python怎么更新数据库_在Python的Django框架中更新数据库数据的方法

    先使用一些关键参数创建对象实例,如下: >>> p = Publisher(name='Apress', ... address='2855 Telegraph Ave.', ... ...

  6. python修复不了_修复 Python version 2.6 required, which was not found in the registry.

    如下: Portable Python是可移植的Python开发环境,它允许同一个系统环境下并存多个版本且相互独立的Python开发环境,也适合放在移动存储设备中作为一个完备的的.便携的开发环境,这两 ...

  7. python实现语音播放_用Python实现语音播报

    以下内容为带着儿子一起学Python的实现记录,为自己保存下来,也希望对其他学习者有用! 1. 确保已经安装python,本例使用python,操作系统为:Windows 10 专业版: 2. 设置环 ...

  8. python读取第二行_使用Python操作Excel(二):读取数据表

    上一节我们提到,使用openpyxl可以方便的对数据表进行操作,例如:抽象Excel数据并存入数据库 将数据库数据导出到Excel 给一个已存在的数据表追加信息 我们还介绍了一些Excel的基本术语, ...

  9. python写机器人程序_用Python写的一个多线程机器人聊天程序

    本人是从事php开发的, 近来想通过php实现即时通讯(兼容windows).后来发现实现起来特别麻烦, 就想到python.听说这家伙在什么地方都能发挥作用.所以想用python来做通讯模块...所 ...

  10. python 追加到字典_使用Python读取,写入和解析JSON

    JSON是用于数据交换的轻量级数据格式,可以很容易地被人类读取和写入,也可以由机器轻松解析和生成.它是一种完全独立于语言的文本格式.为了处理JSON数据,Python有一个名为的内置包json. 示例 ...

最新文章

  1. 如果在BackgroundWorker运行过程中关闭窗体…
  2. 【Linux】函数与参数
  3. fedora下重用ssh连接 类似secureCRT的clone session功能
  4. prometheus获取Consul上注册的服务
  5. 大数据平台应用 17 个知识点汇总
  6. 婚姻就像一双鞋,合不合适只有脚知道
  7. php限制ip访问次数 并发_PHP实现redis限制单ip、单用户的访问次数功能示例
  8. JSP+Servlet--简易的博客功能开发
  9. webpack 处理字体_不可错过的Webpack核心知识点
  10. px和毫米的换算_px和mm换算(px相当于多少毫米)
  11. ElementUI表格序号翻页后重置的解决办法
  12. 知识图谱 Freebase 的基本概念
  13. 微信公众号申请注意事项
  14. 公众号配图在哪里找?快来看看这里
  15. 一文8个步骤从0到1教你基于数据驱动的接口自动化框架封装
  16. 小白在线要饭系统源码
  17. Linux部署IPFS(分布式存储系统)私有网络
  18. txt文本QQ群推广方式
  19. IDEA编译项目报java: 类找不到符号
  20. 什么是总线?总线传输有何特点?

热门文章

  1. 75岁的霍金:地球即将毁灭,移民太空是人类的唯一生存途径
  2. 我的世界服务器金币抽奖系统,[娱乐|综合][PCD]Pcj ——【动态转盘GUI抽奖】多种奖励,多种功能[1.7.2-1.8.8]...
  3. IPv6技术-什么是IPv6
  4. 内核模式下关闭/开启写保护(WriteProtect)
  5. 轻松设置PPT页眉和页脚的正确姿势
  6. JSONP 是什么?
  7. 区块链游戏玩的是游戏还是区块链?
  8. ucinet计算聚类系数大于1怎么办_整体网分析--UCINET笔记
  9. RCSE-配置博客动态网站
  10. jQuery Validate密码验证的基本使用