Screenshot function fixes
This commit is contained in:
@@ -107,7 +107,8 @@ class Scenario:
|
|||||||
def take_screenshot_with_tkinter():
|
def take_screenshot_with_tkinter():
|
||||||
"""
|
"""
|
||||||
Use Tkinter to let the user select a region of the screen for a screenshot.
|
Use Tkinter to let the user select a region of the screen for a screenshot.
|
||||||
Returns a dict with x, y, width, height.
|
The screen is not frozen.
|
||||||
|
Returns a dict with x, y, width, height, or None.
|
||||||
"""
|
"""
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
root.attributes("-alpha", 0.3)
|
root.attributes("-alpha", 0.3)
|
||||||
@@ -121,11 +122,7 @@ def take_screenshot_with_tkinter():
|
|||||||
start_x = None
|
start_x = None
|
||||||
start_y = None
|
start_y = None
|
||||||
|
|
||||||
im = ImageGrab.grab()
|
selection_rect = None
|
||||||
img = ImageTk.PhotoImage(im)
|
|
||||||
canvas.create_image(0,0,image=img,anchor="nw")
|
|
||||||
|
|
||||||
selection_rect = {"x": 0, "y": 0, "width": 0, "height": 0}
|
|
||||||
|
|
||||||
def on_button_press(event):
|
def on_button_press(event):
|
||||||
nonlocal start_x, start_y, rect
|
nonlocal start_x, start_y, rect
|
||||||
@@ -142,10 +139,16 @@ def take_screenshot_with_tkinter():
|
|||||||
nonlocal selection_rect
|
nonlocal selection_rect
|
||||||
end_x, end_y = (event.x, event.y)
|
end_x, end_y = (event.x, event.y)
|
||||||
|
|
||||||
selection_rect["x"] = min(start_x, end_x)
|
x1 = min(start_x, end_x)
|
||||||
selection_rect["y"] = min(start_y, end_y)
|
y1 = min(start_y, end_y)
|
||||||
selection_rect["width"] = abs(start_x - end_x)
|
x2 = max(start_x, end_x)
|
||||||
selection_rect["height"] = abs(start_y - end_y)
|
y2 = max(start_y, end_y)
|
||||||
|
|
||||||
|
width = x2 - x1
|
||||||
|
height = y2 - y1
|
||||||
|
|
||||||
|
if width > 0 and height > 0:
|
||||||
|
selection_rect = {"x": x1, "y": y1, "width": width, "height": height}
|
||||||
|
|
||||||
root.quit()
|
root.quit()
|
||||||
|
|
||||||
@@ -153,6 +156,12 @@ def take_screenshot_with_tkinter():
|
|||||||
canvas.bind("<B1-Motion>", on_mouse_drag)
|
canvas.bind("<B1-Motion>", on_mouse_drag)
|
||||||
canvas.bind("<ButtonRelease-1>", on_button_release)
|
canvas.bind("<ButtonRelease-1>", on_button_release)
|
||||||
|
|
||||||
|
def on_escape(event):
|
||||||
|
nonlocal selection_rect
|
||||||
|
selection_rect = None
|
||||||
|
root.quit()
|
||||||
|
root.bind("<Escape>", on_escape)
|
||||||
|
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
root.destroy()
|
root.destroy()
|
||||||
|
|
||||||
@@ -593,20 +602,18 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
"""
|
"""
|
||||||
Open the dialog to add a new step to the current scenario.
|
Open the dialog to add a new step to the current scenario.
|
||||||
"""
|
"""
|
||||||
self.setWindowState(QtCore.Qt.WindowState.WindowMinimized)
|
self._step_dialog = StepDialog(self)
|
||||||
try:
|
self._step_dialog.accepted.connect(self._on_add_step_accepted)
|
||||||
dlg = StepDialog(self)
|
self._step_dialog.show()
|
||||||
dlg.setWindowFlags(dlg.windowFlags() | QtCore.Qt.WindowType.WindowStaysOnTopHint)
|
|
||||||
# Only process if the dialog was accepted
|
def _on_add_step_accepted(self):
|
||||||
if dlg.exec():
|
step = self._step_dialog.get_step()
|
||||||
step = dlg.get_step()
|
self.current_scenario.steps.append(step)
|
||||||
self.current_scenario.steps.append(step)
|
self.current_scenario.save()
|
||||||
self.current_scenario.save()
|
self.refresh_lists()
|
||||||
self.refresh_lists()
|
logger.info(f'Added step: {step}')
|
||||||
logger.info(f'Added step: {step}')
|
self._step_dialog.deleteLater()
|
||||||
finally:
|
self._step_dialog = None
|
||||||
self.setWindowState(QtCore.Qt.WindowState.WindowNoState)
|
|
||||||
self.activateWindow()
|
|
||||||
|
|
||||||
def edit_step(self):
|
def edit_step(self):
|
||||||
"""
|
"""
|
||||||
@@ -616,19 +623,17 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
if idx is None or idx < 0 or idx >= len(self.current_scenario.steps):
|
if idx is None or idx < 0 or idx >= len(self.current_scenario.steps):
|
||||||
return
|
return
|
||||||
step = self.current_scenario.steps[idx]
|
step = self.current_scenario.steps[idx]
|
||||||
self.setWindowState(QtCore.Qt.WindowState.WindowMinimized)
|
self._step_dialog = StepDialog(self, step)
|
||||||
try:
|
self._step_dialog.accepted.connect(lambda: self._on_edit_step_accepted(idx))
|
||||||
dlg = StepDialog(self, step)
|
self._step_dialog.show()
|
||||||
dlg.setWindowFlags(dlg.windowFlags() | QtCore.Qt.WindowType.WindowStaysOnTopHint)
|
|
||||||
# Only process if the dialog was accepted
|
def _on_edit_step_accepted(self, idx):
|
||||||
if dlg.exec():
|
self.current_scenario.steps[idx] = self._step_dialog.get_step()
|
||||||
self.current_scenario.steps[idx] = dlg.get_step()
|
self.current_scenario.save()
|
||||||
self.current_scenario.save()
|
self.refresh_lists()
|
||||||
self.refresh_lists()
|
logger.info(f'Edited step at idx {idx}')
|
||||||
logger.info(f'Edited step at idx {idx}')
|
self._step_dialog.deleteLater()
|
||||||
finally:
|
self._step_dialog = None
|
||||||
self.setWindowState(QtCore.Qt.WindowState.WindowNoState)
|
|
||||||
self.activateWindow()
|
|
||||||
|
|
||||||
def delete_step(self):
|
def delete_step(self):
|
||||||
"""
|
"""
|
||||||
@@ -1089,7 +1094,6 @@ class StepDialog(QtWidgets.QDialog):
|
|||||||
"""
|
"""
|
||||||
return self.name_edit.text()
|
return self.name_edit.text()
|
||||||
|
|
||||||
|
|
||||||
def add_image_to_step(self):
|
def add_image_to_step(self):
|
||||||
"""
|
"""
|
||||||
Add a new image to the step by taking a screenshot and letting the user select a region.
|
Add a new image to the step by taking a screenshot and letting the user select a region.
|
||||||
@@ -1100,72 +1104,56 @@ class StepDialog(QtWidgets.QDialog):
|
|||||||
QtWidgets.QMessageBox.warning(self, "Step Name Required", "Please enter a name for the step before adding an image.")
|
QtWidgets.QMessageBox.warning(self, "Step Name Required", "Please enter a name for the step before adding an image.")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug("StepDialog.add_image_to_step: Minimizing all windows for screenshot.")
|
main_window.hide()
|
||||||
all_windows = QtWidgets.QApplication.topLevelWidgets()
|
self.hide()
|
||||||
for w in all_windows:
|
time.sleep(0.3)
|
||||||
w.setWindowState(QtCore.Qt.WindowState.WindowMinimized)
|
|
||||||
|
|
||||||
QtWidgets.QApplication.processEvents()
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
|
cropped_pil_image = None
|
||||||
|
rect_coords = None
|
||||||
try:
|
try:
|
||||||
screen = QtWidgets.QApplication.primaryScreen()
|
rect_coords = take_screenshot_with_tkinter()
|
||||||
if not screen:
|
if rect_coords:
|
||||||
logger.error("StepDialog.add_image_to_step: Could not get primary screen.")
|
cropped_pil_image = ImageGrab.grab(bbox=(
|
||||||
raise Exception("Could not get primary screen.")
|
rect_coords['x'],
|
||||||
# Use mss for more reliable screenshot
|
rect_coords['y'],
|
||||||
import mss.tools
|
rect_coords['x'] + rect_coords['width'],
|
||||||
with mss.mss() as sct:
|
rect_coords['y'] + rect_coords['height']
|
||||||
monitor = sct.monitors[1]
|
))
|
||||||
sct_img = sct.grab(monitor)
|
logger.debug("StepDialog.add_image_to_step: Screenshot selection finished.")
|
||||||
img = np.array(sct_img)
|
|
||||||
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)
|
|
||||||
height, width, channel = img.shape
|
|
||||||
bytes_per_line = 3 * width
|
|
||||||
qimg = QtGui.QImage(img.data, width, height, bytes_per_line, QtGui.QImage.Format.Format_RGB888)
|
|
||||||
full_screenshot_pixmap = QtGui.QPixmap.fromImage(qimg)
|
|
||||||
logger.debug("StepDialog.add_image_to_step: Screenshot taken with mss.")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"StepDialog.add_image_to_step: Screenshot failed: {e}")
|
logger.error(f"StepDialog.add_image_to_step: Screenshot failed: {e}")
|
||||||
QtWidgets.QMessageBox.critical(self, 'Error', f'Failed to take screenshot: {e}')
|
QtWidgets.QMessageBox.critical(self, 'Error', f'Failed to take screenshot: {e}')
|
||||||
for w in all_windows:
|
|
||||||
w.setWindowState(QtCore.Qt.WindowState.WindowNoState)
|
|
||||||
w.showNormal()
|
|
||||||
w.activateWindow()
|
|
||||||
return
|
|
||||||
finally:
|
finally:
|
||||||
for w in all_windows:
|
self.show()
|
||||||
w.setWindowState(QtCore.Qt.WindowState.WindowNoState)
|
main_window.show()
|
||||||
w.showNormal()
|
|
||||||
w.activateWindow()
|
|
||||||
|
|
||||||
rect_coords = take_screenshot_with_tkinter()
|
if cropped_pil_image and rect_coords:
|
||||||
|
img_np = np.array(cropped_pil_image.convert('RGB'))
|
||||||
if rect_coords and rect_coords["width"] > 0 and rect_coords["height"] > 0:
|
height, width, channel = img_np.shape
|
||||||
|
bytes_per_line = 3 * width
|
||||||
# Now, use the coordinates to crop the original full screenshot
|
qimage = QtGui.QImage(img_np.data, width, height, bytes_per_line, QtGui.QImage.Format.Format_RGB888)
|
||||||
rect = QtCore.QRect(rect_coords["x"], rect_coords["y"], rect_coords["width"], rect_coords["height"])
|
cropped_pixmap = QtGui.QPixmap.fromImage(qimage)
|
||||||
cropped_pixmap = full_screenshot_pixmap.copy(rect)
|
|
||||||
|
|
||||||
name_dialog = self.NameImageDialog(cropped_pixmap, self)
|
name_dialog = self.NameImageDialog(cropped_pixmap, self)
|
||||||
if name_dialog.exec():
|
if name_dialog.exec():
|
||||||
name = name_dialog.get_name()
|
name = name_dialog.get_name()
|
||||||
scenario_dir = main_window.current_scenario.get_scenario_dir()
|
if name:
|
||||||
step_images_dir = os.path.join(scenario_dir, "steps", step_name)
|
scenario_dir = main_window.current_scenario.get_scenario_dir()
|
||||||
if not os.path.exists(step_images_dir):
|
step_images_dir = os.path.join(scenario_dir, "steps", step_name)
|
||||||
os.makedirs(step_images_dir)
|
if not os.path.exists(step_images_dir):
|
||||||
|
os.makedirs(step_images_dir)
|
||||||
|
|
||||||
safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '_')).rstrip()
|
safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '_')).rstrip()
|
||||||
path = os.path.join(step_images_dir, f'{safe_name}.png')
|
path = os.path.join(step_images_dir, f'{safe_name}.png')
|
||||||
|
|
||||||
if cropped_pixmap.save(path, "PNG"):
|
if cropped_pixmap.save(path, "PNG"):
|
||||||
logger.info(f"Screenshot saved to {path}")
|
logger.info(f"Screenshot saved to {path}")
|
||||||
img_obj = {'path': path, 'region': [rect.x(), rect.y(), rect.width(), rect.height()], 'name': name, 'sensitivity': 0.9}
|
img_obj = {'path': path, 'region': [rect_coords['x'], rect_coords['y'], rect_coords['width'], rect_coords['height']], 'name': name, 'sensitivity': 0.9}
|
||||||
self.images.append(img_obj)
|
self.images.append(img_obj)
|
||||||
self.img_list.addItem(name)
|
self.img_list.addItem(name)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Failed to save screenshot to {path}")
|
logger.error(f"Failed to save screenshot to {path}")
|
||||||
QtWidgets.QMessageBox.critical(self, 'Error', 'Failed to save the screenshot file.')
|
QtWidgets.QMessageBox.critical(self, 'Error', 'Failed to save the screenshot file.')
|
||||||
|
|
||||||
def update_image_preview(self, current, previous):
|
def update_image_preview(self, current, previous):
|
||||||
"""
|
"""
|
||||||
@@ -1233,35 +1221,43 @@ class StepDialog(QtWidgets.QDialog):
|
|||||||
if idx < 0:
|
if idx < 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
all_windows = QtWidgets.QApplication.topLevelWidgets()
|
main_window = self.parent()
|
||||||
for w in all_windows:
|
main_window.hide()
|
||||||
w.setWindowState(QtCore.Qt.WindowState.WindowMinimized)
|
self.hide()
|
||||||
|
time.sleep(0.3)
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
|
cropped_pil_image = None
|
||||||
|
rect_coords = None
|
||||||
try:
|
try:
|
||||||
screen = QtWidgets.QApplication.primaryScreen()
|
rect_coords = take_screenshot_with_tkinter()
|
||||||
if not screen:
|
if rect_coords:
|
||||||
raise Exception("Could not get primary screen.")
|
cropped_pil_image = ImageGrab.grab(bbox=(
|
||||||
full_screenshot_pixmap = screen.grabWindow(0)
|
rect_coords['x'],
|
||||||
|
rect_coords['y'],
|
||||||
|
rect_coords['x'] + rect_coords['width'],
|
||||||
|
rect_coords['y'] + rect_coords['height']
|
||||||
|
))
|
||||||
|
logger.debug("StepDialog.retake_screenshot: Screenshot selection finished.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"StepDialog.retake_screenshot: Screenshot failed: {e}")
|
||||||
|
QtWidgets.QMessageBox.critical(self, 'Error', f'Failed to take screenshot: {e}')
|
||||||
finally:
|
finally:
|
||||||
for w in all_windows:
|
self.show()
|
||||||
w.setWindowState(QtCore.Qt.WindowState.WindowNoState)
|
main_window.show()
|
||||||
w.showNormal()
|
|
||||||
w.activateWindow()
|
|
||||||
|
|
||||||
rect_coords = take_screenshot_with_tkinter()
|
if cropped_pil_image and rect_coords:
|
||||||
|
img_np = np.array(cropped_pil_image.convert('RGB'))
|
||||||
if rect_coords and rect_coords["width"] > 0 and rect_coords["height"] > 0:
|
height, width, channel = img_np.shape
|
||||||
rect = QtCore.QRect(rect_coords["x"], rect_coords["y"], rect_coords["width"], rect_coords["height"])
|
bytes_per_line = 3 * width
|
||||||
cropped_pixmap = full_screenshot_pixmap.copy(rect)
|
qimage = QtGui.QImage(img_np.data, width, height, bytes_per_line, QtGui.QImage.Format.Format_RGB888)
|
||||||
|
cropped_pixmap = QtGui.QPixmap.fromImage(qimage)
|
||||||
|
|
||||||
img_obj = self.images[idx]
|
img_obj = self.images[idx]
|
||||||
path = img_obj['path']
|
path = img_obj['path']
|
||||||
|
|
||||||
if cropped_pixmap.save(path, "PNG"):
|
if cropped_pixmap.save(path, "PNG"):
|
||||||
logger.info(f"Screenshot retaken and saved to {path}")
|
logger.info(f"Screenshot retaken and saved to {path}")
|
||||||
img_obj['region'] = [rect.x(), rect.y(), rect.width(), rect.height()]
|
img_obj['region'] = [rect_coords['x'], rect_coords['y'], rect_coords['width'], rect_coords['height']]
|
||||||
self.update_image_preview(self.img_list.currentItem(), None)
|
self.update_image_preview(self.img_list.currentItem(), None)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Failed to save retaken screenshot to {path}")
|
logger.error(f"Failed to save retaken screenshot to {path}")
|
||||||
|
|||||||
Reference in New Issue
Block a user