Skip to content

Commit

Permalink
Merge pull request #65 from cristii006/cursor_improvemnts
Browse files Browse the repository at this point in the history
Add some improvements to cursor display
  • Loading branch information
cristii006 authored Dec 15, 2020
2 parents 6f548c5 + 9f4161e commit b1d9299
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 31 deletions.
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ robotframework
robotstatuschecker
pillow
opencv-python == 4.2.0.32
pyautogui
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
REQUIREMENTS.append('imageio >= 2.6.1')
REQUIREMENTS.append('mss >= 4.0.3')
REQUIREMENTS.append('opencv-python >= 4.2.0.32')
REQUIREMENTS.append('pyautogui >= 0.9.52')
CLASSIFIERS = '''
Development Status :: 5 - Production/Stable
License :: OSI Approved :: Apache Software License
Expand Down
8 changes: 7 additions & 1 deletion src/ScreenCapLibrary/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ def wrap(*args, **kwargs):

class Client:

def __init__(self, screenshot_module=None, screenshot_directory=None, format='png', quality=50, delay=0):
def __init__(self, screenshot_module=None, screenshot_directory=None, format='png', quality=50, delay=0,
display_cursor=False):
self.screenshot_module = screenshot_module
self._given_screenshot_dir = _norm_path(screenshot_directory)
self._format = format
self._quality = quality
self._delay = delay
self._display_cursor = display_cursor
self.frames = []
self.name = 'screenshot'
self.path = None
Expand All @@ -62,6 +64,10 @@ def __init__(self, screenshot_module=None, screenshot_directory=None, format='pn
self._pause_condition = threading.Event()
self.futures = None

@property
def cursor(self):
return self._display_cursor

@property
def screenshot_dir(self):
return self._given_screenshot_dir or self._log_dir
Expand Down
15 changes: 12 additions & 3 deletions src/ScreenCapLibrary/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ class ScreenCapLibrary:
started_recordings = []
started_gifs = []

def __init__(self, screenshot_module=None, screenshot_directory=None, format='png', quality=50, delay=0):
def __init__(self, screenshot_module=None, screenshot_directory=None, format='png', quality=50, delay=0,
display_cursor=False):
"""
``screenshot_module`` specifies the module or tool to use when taking screenshots using this library.
If no tool or module is specified, ``mss`` will be used by default. For running
Expand All @@ -114,6 +115,13 @@ def __init__(self, screenshot_module=None, screenshot_directory=None, format='pn
``delay`` specifies the waiting time before taking a screenshot. See
`Time format` section for more information. By default the delay is 0.
``display_cursor`` displays a cursor which copies the mouse
movements in order to perform an enhanced visualization of mouse
position. By default ``display_cursor`` is set to False. See
`Boolean arguments` section for more details.
``display_cursor`` is new in ScreenCapLibrary 1.5.0.
Examples (use only one of these):
| =Setting= | =Value= | =Value= |
| Library | Screenshot | |
Expand All @@ -128,7 +136,8 @@ def __init__(self, screenshot_module=None, screenshot_directory=None, format='pn
screenshot_directory=screenshot_directory,
format=format,
quality=quality,
delay=delay
delay=delay,
display_cursor=display_cursor
)

def set_screenshot_directory(self, path):
Expand Down Expand Up @@ -317,7 +326,7 @@ def start_video_recording(self, alias=None, name="recording", fps=None, size_per
"""
if size_percentage <= 0 or size_percentage > 1:
raise Exception('Size percentage should take values > than 0 and <= to 1.')
video_client = VideoClient(self.client.screenshot_module, self.client.screenshot_dir, fps)
video_client = VideoClient(self.client.screenshot_module, self.client.screenshot_dir, fps, self.client.cursor)
self.started_recordings.append(video_client)
video_client.start_video_recording(alias, name, size_percentage, embed, embed_width, monitor)

Expand Down
33 changes: 25 additions & 8 deletions src/ScreenCapLibrary/pygtk.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
import os
import time
import numpy as np

try:
import pyautogui
except:
pass

from .utils import suppress_stderr
from robot.api import logger

Expand All @@ -40,6 +46,9 @@
except ImportError:
GdkPixbuf = None

cursor_x_list = [0, 8, 6, 14, 12, 4, 2, 0]
cursor_y_list = [0, 2, 4, 12, 14, 6, 8, 0]


def _gtk_quality(format, quality):
quality_setting = {}
Expand Down Expand Up @@ -178,13 +187,13 @@ def _take_partial_gtk_screenshot_py3(path, format, quality, left, top, width, he
return path


def _record_gtk(path, fps, size_percentage, stop, pause, monitor):
def _record_gtk(path, fps, size_percentage, stop, pause, monitor, display_cursor):
if not gdk and not Gdk:
raise RuntimeError('PyGTK not installed/supported on this platform.')
if gdk:
return _record_gtk_py2(path, fps, size_percentage, stop, pause, monitor)
elif Gdk:
return _record_gtk_py3(path, fps, size_percentage, stop, pause, monitor)
return _record_gtk_py3(path, fps, size_percentage, stop, pause, monitor, display_cursor)


def _record_gtk_py2(path, fps, size_percentage, stop, pause, monitor):
Expand Down Expand Up @@ -219,7 +228,7 @@ def record_gtk2(vid, width, height, size_percentage, monitor):
vid.write(frame)


def _record_gtk_py3(path, fps, size_percentage, stop, pause, monitor):
def _record_gtk_py3(path, fps, size_percentage, stop, pause, monitor, display_cursor):
window = Gdk.get_default_root_window()
if not window:
raise Exception('Monitor not available.')
Expand All @@ -232,23 +241,31 @@ def _record_gtk_py3(path, fps, size_percentage, stop, pause, monitor):

with suppress_stderr():
if not fps:
fps = benchmark_recording_performance_gtk(width, height, size_percentage, monitor)
fps = benchmark_recording_performance_gtk(width, height, size_percentage, monitor, display_cursor)
vid = cv2.VideoWriter('%s' % path, fourcc, fps, (int(width * size_percentage), int(height * size_percentage)))
while not stop.isSet():
if pause.isSet():
continue
record_gtk3(vid, width, height, size_percentage, monitor)
record_gtk3(vid, width, height, size_percentage, monitor, display_cursor)
vid.release()
cv2.destroyAllWindows()


def record_gtk3(vid, width, height, size_percentage, monitor):
def record_gtk3(vid, width, height, size_percentage, monitor, display_cursor=False):
pb = _grab_screenshot_gtk_py3(monitor)
if display_cursor:
mouse_x, mouse_y = pyautogui.position()
numpy_array = _convert_pixbuf_to_numpy(pb)
resized_array = cv2.resize(numpy_array, dsize=(int(width * size_percentage), int(height * size_percentage)),
interpolation=cv2.INTER_AREA) \
if size_percentage != 1 else numpy_array
frame = cv2.cvtColor(resized_array, cv2.COLOR_RGB2BGR)
if display_cursor:
cursor_x = [x+mouse_x for x in cursor_x_list]
cursor_y = [y+mouse_y for y in cursor_y_list]
cursor_points = list(zip(cursor_x, cursor_y))
cursor_points = np.array(cursor_points, 'int32')
cv2.fillPoly(frame, [cursor_points], color=[0, 255, 255])
vid.write(frame)


Expand All @@ -264,7 +281,7 @@ def _convert_pixbuf_to_numpy(pixbuf):
return b.reshape((h, w, c))


def benchmark_recording_performance_gtk(width, height, size_percentage, monitor):
def benchmark_recording_performance_gtk(width, height, size_percentage, monitor, display_cursor=False):
fps = 0
last_time = time.time()
fourcc = cv2.VideoWriter_fourcc(*'VP08')
Expand All @@ -275,7 +292,7 @@ def benchmark_recording_performance_gtk(width, height, size_percentage, monitor)
while time.time() - last_time < 2:
fps += 1
if Gdk:
record_gtk3(vid, width, height, size_percentage, monitor)
record_gtk3(vid, width, height, size_percentage, monitor, display_cursor)
else:
record_gtk2(vid, width, height, size_percentage, monitor)

Expand Down
41 changes: 22 additions & 19 deletions src/ScreenCapLibrary/videoclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,28 @@
from robot.utils import get_link_path, is_truthy
from robot.api import logger

import pyautogui
x_list = [0,8,6,14,12,4,2,0]
y_list = [0,2,4,12,14,6,8,0]
try:
import pyautogui
except:
pass

try:
import cv2
import numpy as np
except ImportError:
raise ImportError('Importing cv2 failed. Make sure you have opencv-python installed.')

cursor_x_list = [0, 8, 6, 14, 12, 4, 2, 0]
cursor_y_list = [0, 2, 4, 12, 14, 6, 8, 0]


class VideoClient(Client):

def __init__(self, screenshot_module, screenshot_directory, fps):
def __init__(self, screenshot_module, screenshot_directory, fps, display_cursor):
Client.__init__(self)
self.screenshot_module = screenshot_module
self._given_screenshot_dir = _norm_path(screenshot_directory)
self.display_cursor = is_truthy(display_cursor)
self._stop_condition = threading.Event()
self.alias = None
try:
Expand Down Expand Up @@ -62,7 +67,8 @@ def stop_video_recording(self):
@run_in_background
def capture_screen(self, path, fps, size_percentage, monitor):
if self.screenshot_module and self.screenshot_module.lower() == 'pygtk':
_record_gtk(path, fps, size_percentage, self._stop_condition, self._pause_condition, monitor)
_record_gtk(path, fps, size_percentage, self._stop_condition, self._pause_condition, monitor,
self.display_cursor)
else:
self._record_mss(path, fps, size_percentage, monitor)

Expand All @@ -82,29 +88,26 @@ def _record_mss(self, path, fps, size_percentage, monitor):
while not self._stop_condition.isSet():
if self._pause_condition.isSet():
continue
self.record(vid, width, height, size_percentage, monitor)
self.record(vid, width, height, size_percentage, monitor, display_cursor=self.display_cursor)
vid.release()
cv2.destroyAllWindows()

@staticmethod
def record(vid, width, height, size_percentage, monitor):
def record(vid, width, height, size_percentage, monitor, display_cursor=False):
with mss() as sct:
sct_img = sct.grab(sct.monitors[monitor])
mouse_x,mouse_y = pyautogui.position()
if display_cursor:
mouse_x, mouse_y = pyautogui.position()
numpy_array = np.array(sct_img)
resized_array = cv2.resize(numpy_array, dsize=(int(width * size_percentage), int(height * size_percentage)),
interpolation=cv2.INTER_AREA) if size_percentage != 1 else numpy_array
frame = cv2.cvtColor(resized_array, cv2.COLOR_RGBA2RGB)

# Synthesize mouse pointer
# the factor is used to zoom in/out the cursor size
factor = 2
x_this = [factor*x+mouse_x for x in x_list]
y_this = [factor*y+mouse_y for y in y_list]
points = list(zip(x_this,y_this))
points = np.array(points, 'int32')
cv2.fillPoly(frame,[points],color=[255,255,255])

if display_cursor:
cursor_x = [x+mouse_x for x in cursor_x_list]
cursor_y = [y+mouse_y for y in cursor_y_list]
cursor_points = list(zip(cursor_x, cursor_y))
cursor_points = np.array(cursor_points, 'int32')
cv2.fillPoly(frame, [cursor_points], color=[0, 255, 255])
vid.write(frame)

def _embed_video(self, path, width):
Expand All @@ -123,7 +126,7 @@ def benchmark_recording_performance(self, width, height, size_percentage, monito
# count the number of frames captured in 2 seconds
while time.time() - last_time < 2:
fps += 1
self.record(vid, width, height, size_percentage, monitor)
self.record(vid, width, height, size_percentage, monitor, self.display_cursor)

vid.release()
cv2.destroyAllWindows()
Expand Down

0 comments on commit b1d9299

Please sign in to comment.