I am creating an application that reads the input of a webcam and updates the following QGraphicsView
with QPixMap
s. Additionally the QGraphicsView has a rubberband
that is used to capture screenshots.
import logging
import os.path
import random
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QRect, QSize, QPoint, Qt
from PyQt5.QtGui import QMouseEvent, QPixmap, QImage, QRegion
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QRubberBand, QVBoxLayout, QLabel
import metaX.stylesheets as stylesheets
logger = logging.getLogger(__name__)
# TODO: maybe change the name of the module to video_output
def save_image(pixmap, photo_file_path):
try:
pixmap.save(photo_file_path)
except OSError as e:
logger.error(e)
class VideoContainerView(QGraphicsView):
snapshot_taken = pyqtSignal(str)
def __init__(self, mutex, wait_condition, config):
super().__init__()
self.mutex = mutex
self.wait_condition = wait_condition
self.config = config
self.scene = self.set_scene()
self.rubber_band = self.set_rubber_band()
self.setMouseTracking(True)
self._drawable = True
self.set_ui()
def set_scene(self):
scene = QGraphicsScene()
self.setScene(scene)
return scene
def set_rubber_band(self):
rubber_band = QRubberBand(QRubberBand.Rectangle, self)
# rubber_band.show()
return rubber_band
def set_ui(self):
self.setStyleSheet("background-color: transparent;")
def mouseMoveEvent(self, event: QMouseEvent) -> None:
self.draw_rubber_band(event)
def mousePressEvent(self, event: QMouseEvent) -> None:
self.take_snapshot()
def draw_rubber_band(self, event):
self.rubber_band.setGeometry(
QRect(QPoint(event.x() - self.config.crop_area / 2,
event.y() - self.config.crop_area / 2),
QSize(self.config.crop_area, self.config.crop_area)).normalized())
def take_snapshot(self):
if self.rubber_band.isVisible():
self.rubber_band.hide()
selected_area = self.rubber_band.geometry()
pixmap = self.grab(selected_area)
photo_file_path = f'{self.config.captures_path()}/{random.randint(1, 10000000)}.png'
photo_file_path = os.path.abspath((photo_file_path))
save_image(pixmap, photo_file_path)
self.rubber_band.show()
self.snapshot_taken.emit(photo_file_path)
@pyqtSlot(QImage)
def update_frame(self, current_frame):
if self._drawable:
self.mutex.lock()
try:
current_frame = current_frame
pixmap = QPixmap.fromImage(current_frame)
self.scene.clear()
self.resetTransform()
self.scene.addPixmap(pixmap)
self.scene.update()
finally:
self.mutex.unlock()
self.wait_condition.wakeAll()
@pyqtSlot()
def toggle_rubberband(self):
if self.rubber_band.isVisible():
self.rubber_band.hide()
else:
self.rubber_band.show()
def reset_scene(self):
print('reset_scene')
self.scene.clear()
self.scene.addPixmap(QPixmap())
self.scene.update()
@pyqtSlot()
def set_drawable(self, drawable):
self._drawable = drawable
print(f'The VideoContainerView is drawable:{self._drawable}')
Now I would like to decorate the UI using custom shapes drawn on Figma.
Using this image for example: https://i.stack.imgur.com/qncxT.jpg, I would like the QGraphicsScene to be shown "under" the inner rounded rectange, i.e. the center of the rounded rectangle should align with the center of the shown QPixmap
and only the part of the QPixmap that fits into the size of the inner rectangle should be shown, i.e. the inner rounded rectangle should act something like a mask.
I am not familiar with manually drawing mechanisms so any code snippet would be highly appreciated!