将PIL/PILLOW图像复制到Windows剪贴板

3

我看过这个问题,并按照每个步骤进行操作,修改代码以满足我的要求,即Python3、Pillow和ctypes。库越少越好。

import ctypes
from PIL import ImageGrab, Image
from io import BytesIO

user32 = ctypes.windll.user32

img = ImageGrab.grab()
output = BytesIO()
img.convert("RGB").save(output, "BMP")
data = output.getvalue()[14:]
output.close()

user32.OpenClipboard()
user32.EmptyClipboard()
user32.SetClipboardData(user32.CF_DIB, data)
user32.CloseClipboard()

这是我脚本中的简化代码,我认为它是问题中相同的代码,根据我的要求进行了移植。运行时,它应该将当前桌面复制到剪贴板。但实际上我得到了以下结果:

File "C:\Users\Gcq\Documents\python\Screen\Screen.py", line 132, in shot
    user32.OpenClipboard()
ValueError: Procedure probably called with not enough arguments (4 bytes missing)

很抱歉在这里问一个(可能)简单的问题,但我真的不知道哪里出了问题,ctypes也不是我的强项。

2个回答

6
该示例使用Python封装Win32 API的pywin32,它隐藏了一些低级细节,如果您想使用ctypes,则需要自己处理这些细节。
下面是使用ctypes的方法,它添加了创建全局分配缓冲区并将data复制到该缓冲区的功能:
#!python

from PIL import Image
#from cStringIO import StringIO
from io import BytesIO
from ctypes import *
from ctypes.wintypes import *

HGLOBAL = HANDLE
SIZE_T = c_size_t
GHND = 0x0042
GMEM_SHARE = 0x2000

GlobalAlloc = windll.kernel32.GlobalAlloc
GlobalAlloc.restype = HGLOBAL
GlobalAlloc.argtypes = [UINT, SIZE_T]

GlobalLock = windll.kernel32.GlobalLock
GlobalLock.restype = LPVOID
GlobalLock.argtypes = [HGLOBAL]

GlobalUnlock = windll.kernel32.GlobalUnlock
GlobalUnlock.restype = BOOL
GlobalUnlock.argtypes = [HGLOBAL]

CF_DIB = 8

OpenClipboard = windll.user32.OpenClipboard
OpenClipboard.restype = BOOL 
OpenClipboard.argtypes = [HWND]

EmptyClipboard = windll.user32.EmptyClipboard
EmptyClipboard.restype = BOOL
EmptyClipboard.argtypes = None

SetClipboardData = windll.user32.SetClipboardData
SetClipboardData.restype = HANDLE
SetClipboardData.argtypes = [UINT, HANDLE]

CloseClipboard = windll.user32.CloseClipboard
CloseClipboard.restype = BOOL
CloseClipboard.argtypes = None

#################################################

image = Image.new("RGB", (200, 200), (255, 0, 0))

#output = StringIO()
output = BytesIO()
image.convert("RGB").save(output, "BMP")
data = output.getvalue()[14:]
output.close()

hData = GlobalAlloc(GHND | GMEM_SHARE, len(data))
pData = GlobalLock(hData)
memmove(pData, data, len(data))
GlobalUnlock(hData)

OpenClipboard(None)
EmptyClipboard()
SetClipboardData(CF_DIB, pData)
CloseClipboard()

4

哇,看起来win32clipboard库相对于ctypes确实简化了一些事情。仅仅尝试将一个替换为另一个是远远不够正确的。

所以我启动了我的Windows虚拟机,安装了Pillow并从两个其他答案中学习,重写了你的程序:

import io

import ctypes
msvcrt = ctypes.cdll.msvcrt
kernel32 = ctypes.windll.kernel32
user32 = ctypes.windll.user32

from PIL import ImageGrab

img = ImageGrab.grab()
output = io.BytesIO()
img.convert('RGB').save(output, 'BMP')
data = output.getvalue()[14:]
output.close()

CF_DIB = 8
GMEM_MOVEABLE = 0x0002

global_mem = kernel32.GlobalAlloc(GMEM_MOVEABLE, len(data))
global_data = kernel32.GlobalLock(global_mem)
msvcrt.memcpy(ctypes.c_char_p(global_data), data, len(data))
kernel32.GlobalUnlock(global_mem)
user32.OpenClipboard(None)
user32.EmptyClipboard()
user32.SetClipboardData(CF_DIB, global_mem)
user32.CloseClipboard()

更普遍地说,盲目地调用Win32 DLL函数从来不是一个好主意。至少它不会像在POSIX上那样经常崩溃...但你需要在MSDN上查找函数,发送正确的参数,并经常设置argtypes和/或restype,然后才能使用它。 - abarnert
是的,我现在感觉很愚蠢。它起作用了!现在我又有另一个问题:我无法粘贴图片。至少 Word 不接受它。 - gcq
@gcq 仍在努力弄清楚。显然比你想象的要困难得多。 - Oleh Prypin
global_data是一个指针,而global_mem也是一个指针(指向内部数据结构的句柄;它不是句柄表中的小索引)。你需要为这些声明restype,否则在Win64上你的代码将不正确。ctypes默认将数字转换为C int。此外,如果您不手动使用c_void_p进行包装,则还需要设置argtypes - Eryk Sun
嗯,我绝对不是这方面的专家。@gcq,你应该使用另一个答案(它现在已经适用于Python 3)。 - Oleh Prypin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接