2

This question is kind of a follow up to this one.

I'm using the code in the first answer to get a region of the desktop, and copying it to the clipboard. It might seem like i didn't research, but i did. The problem is that this is my first contact with ctypes, winapi and all that jazz.

MS Paint, Paint.net and LibreOffice can read the image perfectly, but MS Word changes the aspect ratio; it just sets width and height to 15cm.

My question is: what kind of data is Word expecting? Also a code example would be great.

This is the current code (same as the other answer):

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()

UPDATE

I thought that maybe it would be easier to use winapi for everything instead of using PIL. Now the dimensions look correct, but the image is black everywhere (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 returns 0 so it is failing :(

UPDATE 2

I tried patching the image header with this snippet:

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

The values for *PelsPerMeter were extracted from here.

Community
  • 1
  • 1
gcq
  • 937
  • 3
  • 13
  • 19
  • 1
    Maybe try setting the `biXPelsPerMeter` and `biYPelsPerMeter` fields in the `BITMAPINFOHEADER`? – Jonathan Potter Jun 26 '14 at 04:25
  • @JonathanPotter thanks for the comment.Saw that while researching. I don't know how to do it though, so i think i will ditch PIL and try to do it directly using winapi functions to see if i can overcome the postprocessing of the image, see update. – gcq Jun 26 '14 at 10:39
  • 1
    If you don't mind being a bit hacky you can patch `biXPelsPerMeter` and `biYPelsPerMeter` directly into `data`. It almost certainly contains a serialised [`BITMAPINFOHEADER`](http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376%28v=vs.85%29.aspx), though you need to check `biSize` (the first four bytes) to be sure. Then the two pels per metre values are at offsets 24 and 28. – arx Jun 26 '14 at 13:23
  • @arx it works! If you want to post an anwer i will accept it, if not, i will answer it myself. Thank you so much! – gcq Jun 26 '14 at 14:31

1 Answers1

2

The resolution of a windows bitmap is defined by the biXPelsPerMeter and biYPelsPerMeter members of the `BITMAPINFOHEADER' structure.

The Python Imaging Library (PIL) writes the resolution as 1 pixel per meter in both directions (line 225 of BmpImagePlugin.py). Thus Word thinks the bitmap is hundreds of metres in size and scales it badly. PIL does not provide an interface to change the resolution.

You can make a hacky fix by patching the values into your data object. This object most likely begins with a serialised BITMAPINFOHEADER, though you need to check biSize (the first four bytes) to be sure. You can then find biXPelsPerMeter and biYPelsPerMeter at offsets 24 and 28 respectively.

A less hacky fix would be to submit a patch to PIL that chooses a sensible default resolution.

arx
  • 16,686
  • 2
  • 44
  • 61