Faster method of reading screen pixel in Python than PIL?

后端 未结 5 761
故里飘歌
故里飘歌 2020-12-13 22:28

Currently I\'m using a pixel reader via AutoItv3 to perform some actions in a program that is running direct X; A game. Right now the program works fine but as an exercise

相关标签:
5条回答
  • 2020-12-13 22:41

    This is the PIL's grabscreen source, Its does not accept any parameters, and Its grab the whole screen and convert it to bitmap.

    PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
    {
        int width, height;
        HBITMAP bitmap;
        BITMAPCOREHEADER core;
        HDC screen, screen_copy;
        PyObject* buffer;
    
        /* step 1: create a memory DC large enough to hold the
           entire screen */
    
        screen = CreateDC(";DISPLAY", NULL, NULL, NULL); 
        screen_copy = CreateCompatibleDC(screen); 
    
        width = GetDeviceCaps(screen, HORZRES);
        height = GetDeviceCaps(screen, VERTRES);
    
        bitmap = CreateCompatibleBitmap(screen, width, height);
        if (!bitmap)
            goto error;
    
        if (!SelectObject(screen_copy, bitmap))
            goto error;
    
        /* step 2: copy bits into memory DC bitmap */
    
        if (!BitBlt(screen_copy, 0, 0, width, height, screen, 0, 0, SRCCOPY))
            goto error;
    
        /* step 3: extract bits from bitmap */
    
        buffer = PyString_FromStringAndSize(NULL, height * ((width*3 + 3) & -4));
        if (!buffer)
            return NULL;
    
        core.bcSize = sizeof(core);
        core.bcWidth = width;
        core.bcHeight = height;
        core.bcPlanes = 1;
        core.bcBitCount = 24;
        if (!GetDIBits(screen_copy, bitmap, 0, height, PyString_AS_STRING(buffer),
                       (BITMAPINFO*) &core, DIB_RGB_COLORS))
            goto error;
    
        DeleteObject(bitmap);
        DeleteDC(screen_copy);
        DeleteDC(screen);
    
        return Py_BuildValue("(ii)N", width, height, buffer);
    
    error:
        PyErr_SetString(PyExc_IOError, "screen grab failed");
    
        DeleteDC(screen_copy);
        DeleteDC(screen);
    
        return NULL;
    }
    

    So, when I just go a little deep, found C approach is good

    http://msdn.microsoft.com/en-us/library/dd144909(VS.85).aspx

    And Python has ctypes, so here is my approach using ctypes (in Windows 10, winnt has been replaced with Windows):

    >>> from ctypes import *
    >>> user= windll.LoadLibrary("c:\\winnt\\system32\\user32.dll") #I am in windows 2000, may be yours will be windows
    >>> h = user.GetDC(0)
    >>> gdi= windll.LoadLibrary("c:\\winnt\\system32\\gdi32.dll")
    >>> gdi.GetPixel(h,1023,767)
    16777215 #I believe its white color of RGB or BGR value, #FFFFFF (according to msdn it should be RGB)
    >>> gdi.GetPixel(h,1024,767)
    -1 #because my screen is only 1024x768
    

    You could write a wrapper for function GetPixel like this

    from ctypes import windll
    dc= windll.user32.GetDC(0)
    
    def getpixel(x,y):
        return windll.gdi32.GetPixel(dc,x,y)
    

    Then you can use like getpixel(0,0), getpixel(100,0), etc...

    PS: Mine is Windows 2000, so I put winnt in the path, you may need to change it to windows or you chould completely remove path, just using user32.dll and gdi32.dll should work too.

    0 讨论(0)
  • 2020-12-13 22:47

    Comment on S.Mark's solution: user32 library is already loaded by windll into windll.user32, so instead of the dc = ... line you can do:

    def getpixel(x,y):
        return gdi.GetPixel(windll.user32.GetDC(0),x,y)
    

    ...or preferably:

    dc= windll.user32.GetDC(0)
    
    0 讨论(0)
  • 2020-12-13 22:48

    It's an old question, but it ranks quite highly on Google when searching for Python screen grab methods, so I think it might be useful to mention that the ImageGrab module now supports grabbing a region of the screen:

    PIL.ImageGrab.grab(bbox=None)
    Parameters: bbox – What region to copy. Default is the entire screen.
    Returns:    An image
    

    http://pillow.readthedocs.io/en/3.1.x/reference/ImageGrab.html

    Expanding the scope slightly, there is now also a replacement for ImageGrab called pyscreenshot, that also saves some part of the screen to a or PIL/Pillow image. This module also works on Linux, unlike ImageGrab which is Windows and OS X only.

    import pyscreenshot as ImageGrab
    im=ImageGrab.grab(bbox=(10,10,510,510)) # X1,Y1,X2,Y2
    im.show()
    

    https://pypi.python.org/pypi/pyscreenshot

    0 讨论(0)
  • 2020-12-13 22:58

    You might be able to do it via SDL (?). Based on this question, SDL can access the screen. And it has python bindings.

    Might be worth a shot? If it worked it would certainly be faster than doing a full screen capture in PIL.

    0 讨论(0)
  • 2020-12-13 23:02

    As an addition to answer of @YOU, this function returns the RGB value of a given pixel as a tuple. I compared the outputs with PIL.ImageGrab and this works fine:

    from ctypes import windll
    dc= windll.user32.GetDC(0)
    
    def getpixel(x,y):
        return tuple(int.to_bytes(windll.gdi32.GetPixel(dc,x,y), 3, "little"))
    

    Note that this code does not have error handling.

    0 讨论(0)
提交回复
热议问题