Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Take screenshot including mouse cursor

I am currently trying to take a screenshot who include mouse cursor using PIL.ImageGrab.

This is my code:

import ctypes, win32gui, win32ui
from PIL import Image, ImageGrab

size = round(ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100 * 32)

cursor = get_cursor()

pixdata = cursor.load()
minsize = [size, None]

width, height = cursor.size
for y in range(height):
    for x in range(width):

        if pixdata[x, y] == (0, 0, 0, 255):
            pixdata[x, y] = (0, 0, 0, 0)

        else:
            if minsize[1] == None:
                minsize[1] = y

            if x < minsize[0]:
                minsize[0] = x

ratio = ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100

img = ImageGrab.grab(bbox=None, include_layered_windows=True)

pos_win = win32gui.GetCursorPos()
pos = (round(pos_win[0]*ratio), round(pos_win[1]*ratio))


img.paste(cursor, pos, cursor)

img.save("screenshot.png")

And this is my get_cursor() function:

def get_cursor():

    hcursor = win32gui.GetCursorInfo()[1]
    hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
    hbmp = win32ui.CreateBitmap()
    hbmp.CreateCompatibleBitmap(hdc, 36, 36)
    hdc = hdc.CreateCompatibleDC()
    hdc.SelectObject(hbmp)
    hdc.DrawIcon((0,0), hcursor)
    
    bmpinfo = hbmp.GetInfo()
    bmpbytes = hbmp.GetBitmapBits()
    bmpstr = hbmp.GetBitmapBits(True)
    cursor = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1).convert("RGBA")
    
    win32gui.DestroyIcon(hcursor)    
    win32gui.DeleteObject(hbmp.GetHandle())
    hdc.DeleteDC()


    pixdata = cursor.load()
    minsize = [32, None]

    width, height = cursor.size
    for y in range(height):
        for x in range(width):

            if pixdata[x, y] == (0, 0, 0, 255):
                pixdata[x, y] = (0, 0, 0, 0)

            else:
                if minsize[1] == None:
                    minsize[1] = y

                if x < minsize[0]:
                    minsize[0] = x


    return cursor

The problem is that some cursors are not pasted at the right position because they have pixels to the left of their position like this (do not pay attention to the quality).

How can I place the cursor image correctly (or otherwise solve the problem)?

like image 592
crazycat256 Avatar asked Oct 21 '25 17:10

crazycat256


2 Answers

Mouse cursors have a "hot spot" which is the position where the image is placed relative to the cursor position This "hot spot" can be obtained like this:

import win32gui

hcursor = win32gui.GetCursorInfo()[1]
hotspot = win32gui.GetIconInfo(hcursor)[1:3]

So the full code is:

import ctypes, win32gui, win32ui
from PIL import Image, ImageGrab


def get_cursor():
    hcursor = win32gui.GetCursorInfo()[1]
    hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
    hbmp = win32ui.CreateBitmap()
    hbmp.CreateCompatibleBitmap(hdc, 36, 36)
    hdc = hdc.CreateCompatibleDC()
    hdc.SelectObject(hbmp)
    hdc.DrawIcon((0,0), hcursor)
    
    bmpinfo = hbmp.GetInfo()
    bmpstr = hbmp.GetBitmapBits(True)
    cursor = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1).convert("RGBA")
    
    win32gui.DestroyIcon(hcursor)    
    win32gui.DeleteObject(hbmp.GetHandle())
    hdc.DeleteDC()


    pixdata = cursor.load()


    width, height = cursor.size
    for y in range(height):
        for x in range(width):

            if pixdata[x, y] == (0, 0, 0, 255):
                pixdata[x, y] = (0, 0, 0, 0)


    hotspot = win32gui.GetIconInfo(hcursor)[1:3]

    return (cursor, hotspot)

cursor, (hotspotx, hotspoty) = get_cursor()
cursor.save("cursor.png")


ratio = ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100

img = ImageGrab.grab(bbox=None, include_layered_windows=True)

pos_win = win32gui.GetCursorPos()
pos = (round(pos_win[0]*ratio - hotspotx), round(pos_win[1]*ratio - hotspoty))


img.paste(cursor, pos, cursor)

img.save("screenshot.png")
like image 99
crazycat256 Avatar answered Oct 23 '25 05:10

crazycat256


You can try using a pointer and pyautogui for the purpose. It's pretty simple and might serve your purpose.

import pyautogui
from PIL import ImageGrab, ImageDraw

# Take the screenshot
img = ImageGrab.grab()

# Get the cursor position
x, y = pyautogui.position()

# Create an ImageDraw object
draw = ImageDraw.Draw(img)

# Draw a circle at the cursor position
draw.ellipse((x, y, x+15, y+15), fill='white')

# Save the screenshot
img.save('screenshot_with_cursor.png')
like image 42
Ahmed Anwar Avatar answered Oct 23 '25 06:10

Ahmed Anwar



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!