Search code examples
pythonwindowswinapiscreenshotpython-imaging-library

Image.frombuffer with 16-bit image data


If my windows is in 32-bit color depth mode, then the following code gets a nice PIL Image from a window:

def image_grab_native(window):
    hwnd = win32gui.GetDesktopWindow()

    left, top, right, bot = get_rect(window)
    w = right - left
    h = bot - top

    hwndDC = win32gui.GetWindowDC(hwnd)
    mfcDC  = win32ui.CreateDCFromHandle(hwndDC)
    saveDC = mfcDC.CreateCompatibleDC()

    saveBitMap = win32ui.CreateBitmap()
    saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)

    saveDC.SelectObject(saveBitMap)
    saveDC.BitBlt((0, 0), (w, h),  mfcDC,  (left, top),  win32con.SRCCOPY)

    bmpinfo = saveBitMap.GetInfo()
    bmpstr = saveBitMap.GetBitmapBits(True)

    im = Image.frombuffer(
        'RGB',
        (bmpinfo['bmWidth'], bmpinfo['bmHeight']),
        bmpstr, 'raw', 'BGRX', 0, 1)

    win32gui.DeleteObject(saveBitMap.GetHandle())
    saveDC.DeleteDC()
    mfcDC.DeleteDC()
    win32gui.ReleaseDC(hwnd, hwndDC)

    return im

However, when running in 16-bit mode, I get the error:

>>> image_grab_native(win)
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    image_grab_native(win)
  File "C:\claudiu\bumhunter\finderbot\ezpoker\utils\win32.py", line 204, in image_grab_native
    bmpstr, 'raw', 'BGRX', 0, 1)
  File "c:\python25\lib\site-packages\PIL\Image.py", line 1808, in frombuffer
    return apply(fromstring, (mode, size, data, decoder_name, args))
  File "c:\python25\lib\site-packages\PIL\Image.py", line 1747, in fromstring
    im.fromstring(data, decoder_name, args)
  File "c:\python25\lib\site-packages\PIL\Image.py", line 575, in fromstring
    raise ValueError("not enough image data")
ValueError: not enough image data

How should I form the frombuffer call to work in 16-bit mode? Also how can I make this function work in any bit depth mode, instead of say having to pass it as a parameter?

UPDATE: From this question I learned I must use "BGR;16" instead of "BGRX" for the 2nd mode parameter. It takes a correct picture, either specifying stride or not. The problem is that the pixel values are slightly off on some values:

x   y native           ImageGrab
280 0  (213, 210, 205) (214, 211, 206)
280 20 (156, 153, 156) (156, 154, 156)
280 40 (213, 210, 205) (214, 211, 206)
300 0  (213, 210, 205) (214, 211, 206)

just a sample of values taken from the same window. the screenshots look identical to the naked eye, but i have to do some pixel manipulation.. also the reason I want to use the native approach at all is that it's a bit faster and it behaves better when running inside virtual machines with dual monitors.. (yes pretty randomly complicated I know).


Solution

  • For the stride parameter, you need to give the row size in bytes. Your pixels are 16 bits each so you might naively assume stride = 2*bmpinfo['bmWidth']; unfortunately Windows adds padding to make the stride an even multiple of 32 bits. That means you'll have to round it to the next highest multiple of 4: stride = (stride + 3) / 4) * 4.

    The documentation doesn't mention a 16-bit raw format so you'll have to check the Unpack.c module to see what's available.

    The final thing you'll notice is that Windows likes to make its bitmaps upside down.

    Edit: Your final little problem is easily explained - the conversion from 16 bit to 24 bit is not precisely defined, and an off-by-one difference between two different conversions is perfectly normal. It wouldn't be hard to adjust the data after you've converted it, as I'm sure the differences are constant based on the value.