将图像从剪贴板粘贴到MS Word具有错误的宽高比

时间:2014-06-25 22:52:33

标签: winapi python-3.x python-imaging-library ctypes

这个问题是this one的后续问题。

我使用第一个答案中的代码来获取桌面区域,并将其复制到剪贴板。看起来我似乎没有研究,但我做到了。问题是,这是我第一次接触ctypes,winapi和所有那些爵士乐。

MS Paint,Paint.net和LibreOffice可以完美地读取图像,但MS Word会改变宽高比;它只是将宽度和高度设置为15厘米。

我的问题是:Word期待什么样的数据?代码示例也很棒。

这是当前代码(与其他答案相同):

import ctypes
from ctypes import wintypes
from PIL import ImageGrab
from io import BytesIO

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

img = ImageGrab.grab()
output = 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()

更新

我认为使用winapi而不是使用PIL可能更容易。现在尺寸看起来正确,但图像到处都是黑色(LibreOffice,Word ...)。

class RECT(ctypes.Structure):
    _fields_ = [
        ('left', ctypes.c_long),
        ('top', ctypes.c_long),
        ('right', ctypes.c_long),
        ('bottom', ctypes.c_long)
    ]

rect = RECT()
# self.whandle is the result of user32.GetDesktopWindow()
user32.GetWindowRect(self.whandle, ctypes.byref(rect))

hdcScreen = user32.GetDC(None)
hdc = gdi32.CreateCompatibleDC(hdcScreen)
hbmp = gdi32.CreateCompatibleBitmap(
    hdcScreen,
    rect.right - rect.left,
    rect.bottom - rect.top
)
gdi32.SelectObject(hdc, hbmp)

PW_CLIENTONLY = 0x00000001

# This returns 0
user32.PrintWindow(self.whandle, hdc, PW_CLIENTONLY)

CF_BITMAP = 2

user32.OpenClipboard(None)
user32.EmptyClipboard()
user32.SetClipboardData(CF_BITMAP, hbmp)
user32.CloseClipboard()

gdi32.DeleteDC(hdc)
gdi32.DeleteObject(hbmp)
user32.ReleaseDC(None, hdcScreen)

PrintWindow返回0,因此失败:(

更新2

我尝试使用此代码段修补图片标头:

import struct

fmt = "LllHHLLllLL"

header_size = struct.calcsize(fmt)

# data comes from the first snippet
image_header = data[:header_size]
image_data = data[header_size:]

unpacked_header = list(struct.unpack_from(fmt, image_header))

# Indexes:
biWidth = 1
biHeight = 2
biXPelsPerMeter = 7
biYPelsPerMeter = 8

unpacked_header[biXPelsPerMeter] = 2835
unpacked_header[biYPelsPerMeter] = 2835

image_header = struct.pack(fmt, *unpacked_header)

data = image_header + image_data

*PelsPerMeter的值是从here中提取的。

1 个答案:

答案 0 :(得分:2)

Windows位图的分辨率由`BITMAPINFOHEADER'结构的biXPelsPerMeterbiYPelsPerMeter成员定义。

Python成像库(PIL)将分辨率写入每米1个像素的两个方向(BmpImagePlugin.py的第225行)。因此,Word认为位图的大小已达数百米,并且严重缩放。 PIL不提供改变分辨率的界面。

您可以通过将值修补到data对象中来进行hacky修复。此对象很可能以序列化BITMAPINFOHEADER开头,但您需要检查biSize(前四个字节)才能确定。然后,您可以分别在抵消24和28处找到biXPelsPerMeterbiYPelsPerMeter

一个不太常见的修复方法是向PIL提交补丁,选择合理的默认解决方案。