def find_display_size(display: GameDisplay, view: QGraphicsView, target_size: QSize) -> QSize: max_width = None max_height = None min_width = min_height = 1 display_width = display.width() display_height = display.height() while True: scene_size = view.contentsRect().size() if scene_size.width() == target_size.width(): min_width = max_width = display_width elif scene_size.width() < target_size.width(): min_width = display_width + 1 else: max_width = display_width - 1 if scene_size.height() == target_size.height(): min_height = max_height = display_height elif scene_size.height() < target_size.height(): min_height = display_height + 1 else: max_height = display_height - 1 if max_width is None: display_width *= 2 else: display_width = (min_width + max_width) // 2 if max_height is None: display_height *= 2 else: display_height = (min_height + max_height) // 2 if min_width == max_width and min_height == max_height: return QSize(display_width, display_height) display.resize(display_width, display_height) view.grab() # Force layout recalculation.
def aspect_fit(self, size: QSize): win_gradient = size.height() / size.width() pix_gradient = self.pixmap().height() / self.pixmap().width() if win_gradient > pix_gradient: self.resize(size.width(), int(size.width() * pix_gradient)) self.factor = round(size.width() / self.pixmap().width(), 2) else: self.resize(int(size.height() / pix_gradient), size.height()) self.factor = round(size.height() / self.pixmap().height(), 2)
def calculateSize(self, sizeType): totalSize = QSize() for wrapper in self.list: position = wrapper.position itemSize = QSize() if sizeType == BorderLayout.MinimumSize: itemSize = wrapper.item.minimumSize() else: # sizeType == BorderLayout.SizeHint itemSize = wrapper.item.sizeHint() if position in (BorderLayout.North, BorderLayout.South, BorderLayout.Center): totalSize.setHeight(totalSize.height() + itemSize.height()) if position in (BorderLayout.West, BorderLayout.East, BorderLayout.Center): totalSize.setWidth(totalSize.width() + itemSize.width()) return totalSize
def printImage(self): #Ok """Imprimir Imagem """ printer = QPrinter() printer.setOutputFormat(QPrinter.NativeFormat) prnt_dlg = QPrintDialog(printer) if (prnt_dlg.exec_() == QPrintDialog.Accepted): painter = QPainter() painter.begin(printer) rect = QRect(painter.viewport()) size = QSize(self.img_lbl.pixmap().size()) size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.img_lbl.pixmap().rect()) painter.drawPixmap(0, 0, self.img_lbl.pixmap()) painter.end()
def save_image_to(self, path): TOP_MARGIN = 50 LEFT_MARGIN = 50 # Determine the size of the entire graph graph_size = self._graph_size() image_size = QSize(graph_size.width() + LEFT_MARGIN * 2, graph_size.height() + TOP_MARGIN * 2 ) image = QImage(image_size, QImage.Format_ARGB32) image.fill(Qt.white) # white background painter = QPainter(image) painter.translate(TOP_MARGIN, LEFT_MARGIN) painter.setRenderHint(QPainter.TextAntialiasing) self._paint(painter, QPoint(-TOP_MARGIN, -LEFT_MARGIN), QPoint(image_size.width(), image_size.height()) ) painter.end() image.save(path)
def SetData(self, pSize=None, dataLen=0, state="", waifuSize=None, waifuDataLen=0, waifuState="", waifuTick=0, isInit=False): self.UpdateSlider() self.epsLabel.setText("位置:{}/{}".format(self.readImg.curIndex + 1, self.readImg.maxPic)) if pSize or isInit: if not pSize: pSize = QSize(0, 0) self.resolutionLabel.setText("分辨率:{}x{}".format( str(pSize.width()), str(pSize.height()))) if dataLen or isInit: self.sizeLabel.setText("大小: " + ToolUtil.GetDownloadSize(dataLen)) if waifuSize or isInit: if not waifuSize: waifuSize = QSize(0, 0) self.resolutionWaifu.setText("分辨率:{}x{}".format( str(waifuSize.width()), str(waifuSize.height()))) if waifuDataLen or isInit: self.sizeWaifu.setText("大小:" + ToolUtil.GetDownloadSize(waifuDataLen)) if state or isInit: self.stateLable.setText("状态:" + state) if waifuState or isInit: if waifuState == QtFileData.WaifuStateStart: self.stateWaifu.setStyleSheet("color:red;") elif waifuState == QtFileData.WaifuStateEnd: self.stateWaifu.setStyleSheet("color:green;") elif waifuState == QtFileData.WaifuStateFail: self.stateWaifu.setStyleSheet("color:red;") else: self.stateWaifu.setStyleSheet("color:dark;") if config.CanWaifu2x: self.stateWaifu.setText("状态:" + waifuState) if waifuTick or isInit: self.tickLabel.setText("耗时:" + str(waifuTick) + "s")
def _turn(self, var: QSize): """ turns icon :param var: :return: """ rm = QMatrix() rm.rotate(var.width()) super().setIcon(self.icon.transformed(rm))
def drawForeground(self, painter, rect): gridSize = QSize(10, 10) if self._showGrid: rect = self.getImageDims() pen = QPen( QColor(styleColor[0], styleColor[1], styleColor[2], 150), 3) pen.setStyle(QtCore.Qt.CustomDashLine) pen.setDashPattern([1, 2]) painter.setPen(pen) d = rect.width() / gridSize.width() for y in range(1, gridSize.height()): painter.drawLine(QPoint(0, int(y * d)), QPoint(int(rect.width()), int(y * d))) for x in range(1, gridSize.width()): painter.drawLine(QPoint(int(x * d), 0), QPoint(int(x * d), int(rect.height())))
def _read_image(self, path: Path) -> Optional[QImage]: data = self._read_file(path) if data: image = QImage() image.loadFromData(data, path.suffix[1:]) max_size = QSize(600, 400) image_size = image.size() if image_size.width() > max_size.width() or image_size.height( ) > max_size.height(): image = image.scaled(max_size, Qt.AspectRatioMode.KeepAspectRatio) return image
def motion(self, event, size: QSize): w0, h0 = size.width(), size.height() self.mouse_pre_x, self.mouse_pre_y = self.mouse_x, self.mouse_y self.mouse_x = (2.0 * event.x() - w0) / w0 self.mouse_y = (h0 - 2.0 * event.y()) / h0 if self.button & Qt.LeftButton: if self.modifiers & Qt.AltModifier: self.camera.rotation(self.mouse_x, self.mouse_y, self.mouse_pre_x, self.mouse_pre_y) elif self.modifiers & Qt.ShiftModifier: self.camera.translation(self.mouse_x, self.mouse_y, self.mouse_pre_x, self.mouse_pre_y) self.updated.emit()
class %CLASS%(QWidget): def __init__(self, parent_node_instance): super(%CLASS%, self).__init__() # leave these lines ------------------------------ self.parent_node_instance = parent_node_instance # ------------------------------------------------ self.video_size = QSize(400, 300) self.timer = QTimer() self.capture = None self.image_label = QLabel() self.image_label.setFixedSize(self.video_size) self.main_layout = QVBoxLayout() self.main_layout.addWidget(self.image_label) self.setLayout(self.main_layout) self.setup_camera() self.resize(self.video_size) def setup_camera(self): self.capture = cv2.VideoCapture(0 + cv2.CAP_DSHOW) self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.video_size.width()) self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.video_size.height()) self.timer.timeout.connect(M(self.display_video_stream)) self.timer.start(30) def display_video_stream(self): _, frame = self.capture.read() frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # frame = cv2.flip(frame, 1) image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888) scaled_image = image.scaled(self.video_size) self.image_label.setPixmap(QPixmap.fromImage(scaled_image)) self.parent_node_instance.video_picture_updated(frame) def get_data(self): return {} # self.text() def set_data(self, data): pass #self.setText(data) def remove_event(self): self.timer.stop()
def __init__(self, ctx: RenderContext, size: QSize): self.ctx = ctx self._size = size self._handle = self._texture = self._rbo = 0 self.ctx.makeCurrent() self._handle = GL.glGenFramebuffers(1) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, self._handle) # Render to texture self._texture = GL.glGenTextures(1) GL.glBindTexture(GL.GL_TEXTURE_2D, self._texture) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, size.width(), size.height(), 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, None) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR) GL.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, self._texture, 0) # But use render buffer for depth and stencil buffers self._rbo = GL.glGenRenderbuffers(1) GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, self._rbo) GL.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL.GL_DEPTH24_STENCIL8, size.width(), size.height()) GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0) GL.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_STENCIL_ATTACHMENT, GL.GL_RENDERBUFFER, self._rbo) result = GL.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER) if result != GL.GL_FRAMEBUFFER_COMPLETE: raise ValueError(result) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0)
def read_settings(self): settings = get_settings() # noinspection PyArgumentList screen = QApplication.desktop().screenGeometry() h = min(screen.height() * 5 / 6., 900) size = QSize(min(screen.width() * 5 / 6., 1200), h) pos = settings.value("pos", None) savesize = settings.value("size", size) if savesize.width() > screen.width(): savesize.setWidth(size.width()) if savesize.height() > screen.height(): savesize.setHeight(size.height()) self.resize(savesize) if ((pos is None or pos.x() + savesize.width() > screen.width() or pos.y() + savesize.height() > screen.height())): self.move(screen.center() - self.rect().center()) else: self.move(pos)
def createFramebufferObject(self, size: QSize) -> QOpenGLFramebufferObject: qDebug('RendererHelper::createFramebufferObject()') macSize = QSize(size.width() / 2, size.height() / 2) format = QOpenGLFramebufferObjectFormat() format.setAttachment(QOpenGLFramebufferObject.Depth) # ifdef Q_OS_MAC # std::unique_ptr<QOpenGLFramebufferObject> framebufferObject(new QOpenGLFramebufferObject(macSize, format)) # else framebufferObject = QOpenGLFramebufferObject(size, format) # endif self.__m_vtkRenderWindow.SetBackLeftBuffer(GL.GL_COLOR_ATTACHMENT0) self.__m_vtkRenderWindow.SetFrontLeftBuffer(GL.GL_COLOR_ATTACHMENT0) self.__m_vtkRenderWindow.SetBackBuffer(GL.GL_COLOR_ATTACHMENT0) self.__m_vtkRenderWindow.SetFrontBuffer(GL.GL_COLOR_ATTACHMENT0) self.__m_vtkRenderWindow.SetSize(framebufferObject.size().width(), framebufferObject.size().height()) self.__m_vtkRenderWindow.SetOffScreenRendering(True) self.__m_vtkRenderWindow.Modified() framebufferObject.release() return framebufferObject
class ImageView(QWidget): button_timeout = QTimer() button_timeout.setInterval(100) button_timeout.setSingleShot(True) shortcut_timeout = QTimer() shortcut_timeout.setInterval(50) shortcut_timeout.setSingleShot(True) slider_timeout = QTimer() slider_timeout.setInterval(20) slider_timeout.setSingleShot(True) DEFAULT_SIZE = (800, 450) MARGIN = 400 MAX_SIZE = QSize(4096, 4096) def __init__(self, app, ui): """ Image Overlay frameless window that stays on top by default :param modules.main_app.ViewerApp app: Viewer QApplication :param modules.main_ui.ViewerWindow ui: Viewer QWidget main app window showing controls """ super(ImageView, self).__init__(ui) self.app, self.ui = app, ui self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.CustomizeWindowHint | Qt.Tool) self.setWindowIcon(IconRsc.get_icon('img')) self.setAttribute(Qt.WA_TranslucentBackground) self.setAttribute(Qt.WA_AcceptDrops, True) self.setStyleSheet("QWidget{background-color: darkgray;}") self.setFocusPolicy(Qt.StrongFocus) self.current_img = None self.img_list = list() self.img_index = 0 self.img_size_factor = 1.0 self.img_size = QSize(*self.DEFAULT_SIZE) self.img_loader = None # will be the loader thread # Save window position for drag self.oldPos = self.pos() self.current_opacity = 1.0 self.setWindowOpacity(1.0) # --- Image Loader --- self.img_load_controller = KnechtLoadImageController(self) self.img_load_controller.camera_available.connect(self.camera_data_available) # --- DG Send thread controller --- self.dg_thread_controller = SyncController(self) # --- Image canvas --- self.setLayout(QHBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(0) self.img_canvas = QLabel(self) self.layout().addWidget(self.img_canvas) self.img_canvas.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.img_canvas.setScaledContents(True) self.img_canvas.setObjectName('img_canvas') self.set_default_image() self.ui.reset() LOGGER.debug('Image View: %s', self.geometry()) self.slider_timeout.timeout.connect(self.set_opacity_from_slider) self.ui.opacity_slider.setToolTip(_('Transparenz der Bildfläche festlegen [W/S]')) self.ui.opacity_slider.sliderReleased.connect(self.slider_timeout.start) self.ui.opacity_slider.valueChanged.connect(self.slider_timeout.start) self.ui.zoom_box.currentIndexChanged.connect(self.combo_box_size) self.ui.zoom_box.setToolTip(_('Anzeigegröße der Bilddatei anpassen [Q/E]')) self.ui.back_btn.pressed.connect(self.iterate_bck) self.ui.back_btn.setToolTip(_('Datei zurück navigieren [A oder <= Pfeiltaste]')) self.ui.fwd_btn.pressed.connect(self.iterate_fwd) self.ui.fwd_btn.setToolTip(_('Datei vorwärts navigieren [D oder => Pfeiltaste]')) self.ui.top_btn.setToolTip(_('Bildfläche immer im Vordergrund')) self.ui.top_btn.released.connect(self.toggle_stay_on_top) self.ui.vis_btn.setToolTip(_('Sichtbarkeit der Bildfläche an/aus [Tab]')) self.ui.vis_btn.released.connect(self.toggle_img_canvas) self.ui.input_btn.setToolTip(_('Maus und Tastatureingabe für Bildfläche de-/aktivieren')) self.ui.input_btn.released.connect(self.toggle_input_transparency) # --- DeltaGen Sync --- self.ui.sync_btn.setText(_('Sync DeltaGen Viewer')) self.ui.sync_btn.setToolTip(_('Bildfläche periodisch zum DeltaGen Viewer verschieben [F]')) self.ui.sync_btn.toggled.connect(self.dg_toggle_sync) # Toggle sync off when idle self.ui.app.idle_event.connect(self._dg_toggle_sync_idle) # -- Change DeltaGen Port button self.ui.dg_btn.setToolTip('DeltaGen external command port') self.ui.dg_btn.pressed.connect(self.change_deltagen_port) # --- Help button --- self.ui.help_btn.setToolTip(_('Hilfe anzeigen')) self.ui.help_btn.pressed.connect(self.display_shortcuts) # --- Pull button (depreciated) --- self.ui.focus_btn.setText(_('Pull DeltaGen Focus')) self.ui.focus_btn.pressed.connect(self.dg_toggle_pull) # Pulling focus is no longer necessary self.ui.focus_btn.hide() # --- Shortcuts --- self.shortcuts = ViewerShortcuts(self, self.ui) self.shortcuts.set_shortcuts(self.ui) # --- Drag n Drop --- self.drag_drop = DragNDropHandler(self) self.drag_drop.file_dropped.connect(self.ui.file_changed) # --- Info Overlay --- self.info_overlay = InfoOverlay(self) self.place_in_screen_center() def changeEvent(self, event): """ Not necessary when UI is normal window and is our parent. Used to be necessary when control window was also frameless. :param QEvent event: :return: """ if event.type() == QEvent.WindowStateChange: if event.oldState() == Qt.WindowMinimized: LOGGER.debug('Restoring Image Overlay Window') if event.oldState() and Qt.WindowMinimized: LOGGER.debug('Image Overlay Window was restored') elif event.oldState() == Qt.WindowNoState: LOGGER.debug('Image Overlay Window minimized.') def change_deltagen_port(self): box = QMessageBox(self) box.setText(_('Port zwischen 3000-3999 angeben. ' 'Muss mit DeltaGen>Preferences>Tools>External Commands übereinstimmen.')) box.setWindowTitle(_('DeltaGen Kommando Port')) port = QSpinBox(box) port.setMinimum(3000) port.setMaximum(3999) port.setValue(KnechtSettings.app.get('port', DG_TCP_PORT)) box.layout().addWidget(port, box.layout().rowCount() - 1, 0, 1, box.layout().columnCount()) box.layout().addWidget(box.layout().takeAt(box.layout().rowCount() - 1).widget(), box.layout().rowCount(), 0, 1, box.layout().columnCount()) box.exec_() if 3000 <= port.value() <= 3999: KnechtSettings.app['port'] = port.value() box = QMessageBox(self) box.setText(_('Anwendung neu starten um geänderten Port zu übernehmen.')) box.setWindowTitle(_('Neustart erforderlich')) box.exec_() def display_shortcuts(self, keep_overlay: bool=True): msg = WELCOME_MSG.format(' '.join(self.img_load_controller.FILE_TYPES)) # Ensure visibility of image canvas if self.current_opacity < 0.7 or self.ui.vis_btn.isChecked(): self.set_window_opacity(1.0) if self.ui.vis_btn.isChecked(): self.ui.vis_btn.toggle() if keep_overlay: self.info_overlay.display_confirm(msg, (('[X]', None), )) else: self.info_overlay.display(msg, 6000) def set_default_image(self): self.current_img = IconRsc.get_pixmap('img_viewer_bg') self.img_canvas.setStyleSheet('background: rgba(0, 0, 0, 0);') self.img_canvas.setPixmap(self.current_img) self.img_size = self.current_img.size() self.img_size_factor = 1.0 self.change_viewer_size() # ------ DeltaGen Sync ------- def dg_toggle_btn(self, enabled: bool): """ Called by thread signal """ self.ui.sync_btn.setEnabled(enabled) def dg_check_btn(self, checked: bool): """ Called by thread signal """ self.ui.sync_btn.setChecked(checked) def dg_toggle_pull(self): """ Toggles pulling of the viewer window in front on/off """ self.dg_thread_controller.toggle_pull() def _dg_toggle_sync_idle(self): """ Switch off sync if app is idling """ if self.ui.sync_btn.isChecked(): LOGGER.debug('Toggling sync off while application is idling.') # Toggle sync off and do not interrupt user with viewer reset self.dg_thread_controller.toggle_sync(reset_viewer=False) def dg_toggle_sync(self): if not self.ui.sync_btn.isEnabled(): return self.dg_thread_controller.toggle_sync() # ------ IMAGES ------- def set_img_path(self, file_path: Path): self.img_load_controller.set_img_path(file_path) self.ui.path_util.set_path_text(self.img_load_controller.img_dir) def _can_iterate_images(self) -> bool: if self.button_timeout.isActive(): return False self.button_timeout.start() return True def iterate_fwd(self): if not self._can_iterate_images(): return self.img_load_controller.iterate_fwd() def iterate_bck(self): if not self._can_iterate_images(): return self.img_load_controller.iterate_bck() def no_image_found(self): self.set_default_image() self.ui.reset() self.info_overlay.display(_('Keine Bilddaten im Verzeichnis gefunden.')) def image_load_failed(self, error_msg=''): img_path = self.img_load_controller.current_image() if not error_msg: error_msg = img_path.as_posix() LOGGER.error('Could not load image file:\n%s', error_msg) self.set_default_image() self.ui.reset() self.img_loader = None self.info_overlay.display(_('Konnte keine Bilddaten laden: {}<br>{}').format(error_msg, img_path.as_posix()), 8000, immediate=True) def image_loaded(self, image): def valid_img_name(img_path: Path) -> str: name = img_path.name if len(name) >= 85: name = f'{name[:65]}~{name[-20:]}' return name if not image: self.image_load_failed() return self.current_img = image self.img_canvas.setPixmap(self.current_img) self.img_size = self.current_img.size() self.change_viewer_size() prev_img_name = valid_img_name(self.img_load_controller.prev_image()) img_name = valid_img_name(self.img_load_controller.current_image()) next_img_name = valid_img_name(self.img_load_controller.next_image()) self.ui.setWindowTitle(img_name) i = f'<span style="color: rgb(180, 180, 180);">' \ f'{self.img_load_controller.get_valid_img_list_index(self.img_load_controller.img_index-1)+1:02d}/' \ f'{len(self.img_load_controller.img_list):02d} - {prev_img_name}</span><br />'\ f'{1+self.img_load_controller.img_index:02d}/' \ f'{len(self.img_load_controller.img_list):02d} - {img_name}<br />' \ f'<span style="color: rgb(180, 180, 180);">' \ f'{self.img_load_controller.get_valid_img_list_index(self.img_load_controller.img_index+1)+1:02d}' \ f'/{len(self.img_load_controller.img_list):02d} - {next_img_name}</span>' self.info_overlay.display(i, 3000, immediate=True) # ------ RESIZE ------- def combo_box_size(self, idx): self.set_img_size_factor_from_combo_box() self.change_viewer_size() def set_img_size_factor_from_combo_box(self): data = self.ui.zoom_box.currentData() if data: self.img_size_factor = data def set_size_box_index(self, add_idx: int=0): cb = self.ui.zoom_box new_idx = min(cb.count() - 1, max(cb.currentIndex() + add_idx, 0)) cb.setCurrentIndex(new_idx) def increase_size(self): self.set_size_box_index(1) self.set_img_size_factor_from_combo_box() self.change_viewer_size() def decrease_size(self): self.set_size_box_index(-1) self.set_img_size_factor_from_combo_box() self.change_viewer_size() def change_viewer_size(self): self.img_size_factor = max(0.01, min(self.img_size_factor, MAX_SIZE_FACTOR)) w = round(self.img_size.width() * self.img_size_factor) h = round(self.img_size.height() * self.img_size_factor) new_size = QSize(w, h) self.resize_image_viewer(new_size) def resize_image_viewer(self, new_size: QSize): width = max(50, min(new_size.width(), self.MAX_SIZE.width())) height = max(50, min(new_size.height(), self.MAX_SIZE.height())) new_size = QSize(width, height) self.resize(new_size) # ------ OPACITY ------- def increase_window_opacity(self): opacity = self.windowOpacity() + 0.15 self.update_opacity_slider() self.set_window_opacity(opacity) def decrease_window_opacity(self): opacity = self.windowOpacity() - 0.15 self.update_opacity_slider() self.set_window_opacity(opacity) def update_opacity_slider(self): self.ui.opacity_slider.setValue(round(self.windowOpacity() * self.ui.opacity_slider.maximum())) def set_opacity_from_slider(self): opacity = self.ui.opacity_slider.value() * 0.1 self.set_window_opacity(opacity) def set_window_opacity(self, opacity): if self.shortcut_timeout.isActive(): return opacity = max(0.05, min(1.0, opacity)) self.current_opacity = opacity self.setWindowOpacity(opacity) self.shortcut_timeout.start() # --- Camera ---- def camera_data_available(self, available: int): pal = QPalette() pal.setColor(QPalette.Button, QColor(240, 240, 240)) if available == 0: self.ui.cam_btn.setEnabled(False) elif available == 1: self.ui.cam_btn.setEnabled(True) elif available == 2: pal.setColor(QPalette.Button, QColor(240, 150, 150)) self.ui.cam_btn.setEnabled(True) self.ui.cam_btn.setAutoFillBackground(True) self.ui.cam_btn.setPalette(pal) self.ui.cam_btn.update() # ------ VISIBILITY ------- def toggle_img_canvas(self): if self.shortcut_timeout.isActive(): return if not self.ui.vis_btn.isChecked(): self.setWindowOpacity(self.current_opacity) else: self.setWindowOpacity(0.0) self.shortcut_timeout.start() def _toggle_window_flag(self, window_flag: Qt) -> bool: enabled = False if self.windowFlags() & window_flag else True self.hide() self.setWindowFlag(window_flag, enabled) self.show() return enabled def switch_stay_on_top(self) -> bool: enabled = self._toggle_window_flag(Qt.WindowStaysOnTopHint) KnechtSettings.app['img_stay_on_top'] = enabled return enabled def toggle_stay_on_top(self): if self.shortcut_timeout.isActive(): return enabled = self.switch_stay_on_top() self.info_overlay.display(_('Bildfläche erscheint nun nicht mehr im Vordergrund') if not enabled else _('Bildfläche erscheint nun immer im Vordergrund'), immediate=True) self.shortcut_timeout.start() def switch_input_transparency(self): enabled = self._toggle_window_flag(Qt.WindowTransparentForInput) KnechtSettings.app['img_input_transparent'] = enabled return enabled def toggle_input_transparency(self): if self.shortcut_timeout.isActive(): return enabled = self.switch_input_transparency() self.info_overlay.display(_('Maus und Tastatureingabe für Bildfläche aktiviert.') if not enabled else _('Maus und Tastatureingabe für Bildfläche deaktiviert.'), immediate=True) self.shortcut_timeout.start() def hide_all(self): # self.ui.hide() self.hide() def show_all(self): self.place_inside_screen() self.showNormal() self.current_opacity = 1.0 self.ui.opacity_slider.setValue(self.ui.opacity_slider.maximum()) # ------ OVERRIDES ------- def moveEvent(self, event): if self.moved_out_of_limit(): event.ignore() return event.accept() def resizeEvent(self, event): if self.moved_out_of_limit(): event.ignore() return event.accept() def moved_out_of_limit(self): limit = self.calculate_screen_limits() pos = self.geometry().topLeft() if not self.is_inside_limit(limit, pos): x = min(limit.width(), max(limit.x(), pos.x())) y = min(limit.height(), max(limit.y(), pos.y())) self.move(x, y) return True return False def place_inside_screen(self): limit = self.calculate_screen_limits() pos = self.geometry().topLeft() if not self.is_inside_limit(limit, pos): self.place_in_screen_center() def place_in_screen_center(self): screen = self.app.desktop().availableGeometry(self) center_x = screen.center().x() - self.geometry().width() / 2 center_y = screen.center().y() - self.geometry().height() / 2 self.move(center_x, center_y) def calculate_screen_limits(self): screen = QRect(self.app.desktop().x(), self.app.desktop().y(), self.app.desktop().width(), self.app.desktop().availableGeometry().height()) width_margin = round(self.geometry().width() / 2) height_margin = round(self.geometry().height() / 2) # Case where secondary screen has negative values desktop_width = screen.x() + screen.width() min_x = screen.x() - width_margin min_y = screen.y() - height_margin max_x = desktop_width - width_margin max_y = screen.height() - height_margin return QRect(min_x, min_y, max_x, max_y) def closeEvent(self, QCloseEvent): self.dg_thread_controller.exit() self.ui.close() QCloseEvent.accept() def mousePressEvent(self, event): self.oldPos = event.globalPos() def mouseMoveEvent(self, event): delta = QPoint(event.globalPos() - self.oldPos) self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = event.globalPos() @staticmethod def is_inside_limit(limit: QRect, pos: QPoint): if pos.x() < limit.x() or pos.x() > limit.width(): return False elif pos.y() < limit.y() or pos.y() > limit.height(): return False return True
class Check(QFrame): def __init__(self, name, default=False): super().__init__() # Variables self.name = name self.hovered = False self.enabled = default self.padding = 5 # Objects self.image = QImageReader() self.imageSize = QSize(20, 20) # Styling self.font = QFont('Arial', 8) self.font.setWeight(QFont.Bold) self.nameHeight = QFontMetrics(self.font).height() self.nameWidth = QFontMetrics(self.font).width(self.name) self.image.setScaledSize(self.imageSize) self.setFixedSize( self.imageSize.width() + self.padding + self.nameWidth, self.imageSize.height()) def getEnabled(self): return self.enabled def setPadding(self, param): self.padding = param self.update() def enterEvent(self, event): super().enterEvent(event) self.hovered = True self.update() def leaveEvent(self, event): super().leaveEvent(event) self.hovered = False self.update() def mouseReleaseEvent(self, event): super().mousePressEvent(event) if event.button() == Qt.LeftButton: self.enabled = not self.enabled self.stateChanged.emit(self.enabled) self.update() def paintEvent(self, event): # Set checked depending on state self.image.setFileName(ThemeManager.CHECKED_PATH if self. enabled else ThemeManager.UNCHECKED_PATH) # Init painter and draw image at correct location painter = QPainter(self) pixmap = QPixmap.fromImageReader(self.image) painter.setRenderHint(QPainter.HighQualityAntialiasing, True) painter.drawPixmap(0, (self.height() - pixmap.height()) / 2, pixmap) # Style font and draw text at correct location painter.setFont(self.font) pen = QPen() pen.setColor(ThemeManager.LABEL_QC if any([self.hovered, self.enabled]) else ThemeManager.LABEL_LOW_OPACITY_QC) painter.setPen(pen) painter.drawText(pixmap.width() + self.padding, self.height() / 2 + self.nameHeight / 4, self.name) stateChanged = Signal(object)
def mouse(self, event, size: QSize): self.mouse_x = (2.0 * event.x() - size.width()) / size.width() self.mouse_y = (size.height() - 2.0 * event.y()) / size.height() self.button = event.button() self.modifiers = event.modifiers() self.updated.emit()
class RandomWalker(object): MOVE_UP = 0 MOVE_DOWN = 1 MOVE_RIGHT = 2 MOVE_LEFT = 3 def __init__(self): self.rect = QRect() self.path = [] self.start_pos = QVector2D() self.pos = QVector2D() self.resolution = QSize() self.up_vector = QVector2D(0, -1) self.down_vector = QVector2D(0, 1) self.left_vector = QVector2D(-1, 0) self.right_vector = QVector2D(1, 0) self.head_color = QColor(250, 60, 50) self.tail_color = QColor(70, 70, 70) def reset(self): self.path = [] self.pos = self.start_pos def setup(self): self.reset() def get_option(self): choice = random.randint(0, 3) pos_copy = QVector2D(self.pos) if choice == self.MOVE_UP: pos_copy += self.up_vector elif choice == self.MOVE_DOWN: pos_copy += self.down_vector elif choice == self.MOVE_LEFT: pos_copy += self.left_vector elif choice == self.MOVE_RIGHT: pos_copy += self.right_vector return pos_copy def tick(self): """ Tick the random walker to do something """ while True: pos = self.get_option() is_valid = True if pos.x() > self.resolution.width(): is_valid = False elif pos.x() < 0: is_valid = False elif pos.y() > self.resolution.height(): is_valid = False elif pos.y() < 0: is_valid = False if is_valid: self.path.append(pos) self.pos = pos break def paint_cell(self, painter, pos, color): chunk_x = float(self.rect.width()) / self.resolution.width() chunk_y = float(self.rect.height()) / self.resolution.height() x = pos.x() * chunk_x y = pos.y() * chunk_y painter.fillRect(x - chunk_x / 2, y - chunk_y - 2, math.ceil(chunk_x), math.ceil(chunk_y), color) def paint(self, painter): for p in self.path: self.paint_cell(painter, p, self.tail_color) self.paint_cell(painter, self.pos, self.head_color)
def calculate_proper_size(self, from_size: QSize): minimum_length = min(from_size.width(), from_size.height()) return QSize(minimum_length - self.spacing(), minimum_length - self.spacing())
def calculate_center_location(from_size: QSize, item_size: QSize): x = max(from_size.width() // 2 - item_size.width() // 2, 0) y = max(from_size.height() // 2 - item_size.height() // 2, 0) return QPoint(x, y)
def fits_inside(size1: QSize, size2: QSize): return size1.width() <= size2.width() and size1.height( ) <= size2.height()
class LevyFlight(object): MOVE_UP = 0 MOVE_DOWN = 1 MOVE_RIGHT = 2 MOVE_LEFT = 3 def create_random_vector2d(self): a = random.random() * math.pi * 2 return QVector2D(math.cos(a), math.sin(a)) def __init__(self): self.rect = QRect() self.path = [] self.start_pos = QVector2D() self.pos = QVector2D() self.resolution = QSize() self.up_vector = QVector2D(0, -1) self.down_vector = QVector2D(0, 1) self.left_vector = QVector2D(-1, 0) self.right_vector = QVector2D(1, 0) self.head_color = QColor(250, 60, 50) self.tail_color = QColor(70, 70, 70) def reset(self): self.path = [] self.pos = self.start_pos def setup(self): self.reset() def get_next_pos(self): pos_copy = QVector2D(self.pos) v = self.create_random_vector2d() if random.random() < 0.01: pos_copy += v * random.randint(20, 40) else: pos_copy += v * random.randint(1, 5) return pos_copy def tick(self): """ Tick the random walker to do something """ while True: pos = self.get_next_pos() is_valid = True if pos.x() > self.resolution.width(): is_valid = False elif pos.x() < 0: is_valid = False elif pos.y() > self.resolution.height(): is_valid = False elif pos.y() < 0: is_valid = False if is_valid: self.path.append(pos) self.pos = pos break def paint(self, painter): chunk_x = float(self.rect.width()) / self.resolution.width() chunk_y = float(self.rect.height()) / self.resolution.height() painter_path = QPainterPath() for i, pos in enumerate(self.path): x = pos.x() * chunk_x y = pos.y() * chunk_y p = QPoint(x, y) if i == 0: painter_path.moveTo(p) else: painter_path.lineTo(p) painter.drawPath(painter_path)
def histogram(self, size=QSize(200, 200), bgColor=Qt.white, range=(0, 255), chans=channelValues.RGB, chanColors=Qt.gray, mode='RGB', addMode=''): """ Plot the image histogram with the specified color mode and channels. Histograms are smoothed using a Savisky-Golay filter and curves are scaled individually to fit the height of the plot. @param size: size of the histogram plot @type size: int or QSize @param bgColor: background color @type bgColor: QColor @param range: plot data range @type range: 2-uple of int or float @param chans: channels to plot b=0, G=1, R=2 @type chans: list of indices @param chanColors: color or 3-uple of colors @type chanColors: QColor or 3-uple of QColor @param mode: color mode ((one among 'RGB', 'HSpB', 'Lab', 'Luminosity') @type mode: str @param addMode: @type addMode: @return: histogram plot @rtype: QImage """ # convert size to QSize if type(size) is int: size = QSize(size, size) # alert threshold for clipped areas clipping_threshold = 0.02 # clipping threshold for black and white points # scaling factor for the bin edges spread = float(range[1] - range[0]) scale = size.width() / spread # per channel histogram function def drawChannelHistogram(painter, hist, bin_edges, color): # Draw the (smoothed) histogram for a single channel. # param painter: QPainter # param hist: histogram to draw # smooth the histogram (first and last bins excepted) for a better visualization of clipping. hist = np.concatenate(([hist[0]], SavitzkyGolayFilter.filter(hist[1:-1]), [hist[-1]])) M = max(hist[1:-1]) # draw histogram imgH = size.height() for i, y in enumerate(hist): try: h = int(imgH * y / M) except (ValueError, ArithmeticError): # don't draw the channel histogram if M is too small: # It may happen when channel values are concentrated # on the first and/or last bins. return h = min(h, imgH - 1) # height of rect must be < height of img, otherwise fillRect does nothing rect = QRect(int((bin_edges[i] - range[0]) * scale), max(img.height() - h, 0), int((bin_edges[i + 1] - bin_edges[i]) * scale+1), h) painter.fillRect(rect, color) # clipping indicators if i == 0 or i == len(hist)-1: left = bin_edges[0 if i == 0 else -1] if range[0] < left < range[1]: continue left = left - (10 if i > 0 else 0) percent = hist[i] * (bin_edges[i+1]-bin_edges[i]) if percent > clipping_threshold: # calculate the color of the indicator according to percent value nonlocal gPercent gPercent = min(gPercent, np.clip((0.05 - percent) / 0.03, 0, 1)) painter.fillRect(left, 0, 10, 10, QColor(255, 255*gPercent, 0)) # green percent for clipping indicators gPercent = 1.0 bufL = cv2.cvtColor(QImageBuffer(self)[:, :, :3], cv2.COLOR_BGR2GRAY)[..., np.newaxis] # returns Y (YCrCb) : Y = 0.299*R + 0.587*G + 0.114*B buf = None # TODO added 5/11/18 validate if mode == 'RGB': buf = QImageBuffer(self)[:, :, :3][:, :, ::-1] # RGB elif mode == 'HSV': buf = self.getHSVBuffer() elif mode == 'HSpB': buf = self.getHspbBuffer() elif mode == 'Lab': buf = self.getLabBuffer() elif mode == 'Luminosity': chans = [] img = QImage(size.width(), size.height(), QImage.Format_ARGB32) img.fill(bgColor) qp = QPainter(img) try: if type(chanColors) is QColor or type(chanColors) is Qt.GlobalColor: chanColors = [chanColors]*3 # compute histograms # bins='auto' sometimes causes a huge number of bins ( >= 10**9) and memory error # even for small data size (<=250000), so we don't use it. # This is a numpy bug : in the module function_base.py # a reasonable upper bound for bins should be chosen to prevent memory error. if mode == 'Luminosity' or addMode == 'Luminosity': hist, bin_edges = np.histogram(bufL, bins=100, density=True) drawChannelHistogram(qp, hist, bin_edges, Qt.gray) hist_L, bin_edges_L = [0]*len(chans), [0]*len(chans) for i, ch in enumerate(chans): buf0 = buf[:, :, ch] hist_L[i], bin_edges_L[i] = np.histogram(buf0, bins=100, density=True) # to prevent artifacts, the histogram bins must be drawn # using the composition mode source_over. So, we use # a fresh QImage for each channel. tmpimg = QImage(size, QImage.Format_ARGB32) tmpimg.fill(bgColor) tmpqp = QPainter(tmpimg) try: drawChannelHistogram(tmpqp, hist_L[i], bin_edges_L[i], chanColors[ch]) finally: tmpqp.end() # add the channnel hist to img qp.drawImage(QPoint(0,0), tmpimg) # subsequent images are added using composition mode Plus qp.setCompositionMode(QPainter.CompositionMode_Plus) finally: qp.end() buf = QImageBuffer(img) # if len(chans) > 1, clip gray area to improve the aspect of the histogram if len(chans) > 1: buf[:, :, :3] = np.where(np.min(buf, axis=-1)[:, :, np.newaxis] >= 100, np.array((100, 100, 100))[np.newaxis, np.newaxis, :], buf[:, :, :3]) return img
def size_to_point(size: QSize) -> QPoint: return QPoint(size.width(), size.height())
class ViewpointsListModel(QAbstractListModel): """ Model class to the viewpoins list. It returns the name of a viewpoint, associated with the current topic as well as an icon of the snapshot file that is referenced in the viewpoint. If no snapshot is referenced then no icon is returned. An icon is 10x10 millimeters in dimension. The actual sizes and offsets, in pixels, are stored in variables containing 'Q'. All other sizes and offset variables hold values in millimeters.These sizes and offsets are scaled to the currently active screen, retrieved by `util.getCurrentQScreen()`. """ def __init__(self, snapshotModel, parent=None): QAbstractListModel.__init__(self, parent=None) self.viewpoints = [] """ List of instances of `ViewpointReference` """ # used to retrieve the snapshot icons. self.snapshotModel = snapshotModel """ Reference to SnapshotModel used to retrieve icons of the snapshots """ # set up the sizes and offsets self._iconQSize = None """ Size in pixels """ self._iconSize = QSize(10, 10) """ Size in millimeters """ self.calcSizes() def data(self, index, role=Qt.DisplayRole): """ Returns the file name and an icon of the snapshot associated to a viewpoint. """ if not index.isValid(): return None viewpoint = self.viewpoints[index.row()] if role == Qt.DisplayRole: # return the name of the viewpoints file return str(viewpoint.file) + " (" + str(viewpoint.id) + ")" elif role == Qt.DecorationRole: # if a snapshot is linked, return an icon of it. filename = str(viewpoint.snapshot) icon = self.snapshotModel.imgFromFilename(filename) if icon is None: # snapshot is not listed in markup and cannot be loaded return None scaledIcon = icon.scaled(self._iconQSize, Qt.KeepAspectRatio) return scaledIcon def rowCount(self, parent=QModelIndex()): """ Returns the amount of viewpoints that shall be listed in the view """ return len(self.viewpoints) @Slot() def resetItems(self, topic=None): """ If `topic != None` load viewpoints associated with `topic`, else delete the internal state of the model """ self.beginResetModel() if topic is None: self.viewpoints = [] else: self.viewpoints = [vp[1] for vp in pI.getViewpoints(topic, False)] self.endResetModel() @Slot() def resetView(self): """ Reset the view of FreeCAD to the state before the first viewpoint was applied """ pI.resetView() @Slot() def calcSizes(self): """ Convert the millimeter sizes/offsets into pixels depending on the current screen. """ screen = util.getCurrentQScreen() # pixels per millimeter (not parts per million) ppm = screen.logicalDotsPerInch() / util.MMPI width = self._iconSize.width() * ppm height = self._iconSize.height() * ppm self._iconQSize = QSize(width, height) @Slot(QModelIndex) def activateViewpoint(self, index): """ Manipulates FreeCAD's object viewer according to the setting of the viewpoint. """ if not index.isValid() or index.row() >= len(self.viewpoints): return False vpRef = self.viewpoints[index.row()] camType = None if vpRef.viewpoint.oCamera is not None: camType = CamType.ORTHOGONAL elif vpRef.viewpoint.pCamera is not None: camType = CamType.PERSPECTIVE result = pI.activateViewpoint(vpRef.viewpoint, camType) if result == pI.OperationResults.FAILURE: return False return True def setIconSize(self, size: QSize): """ Size is expected to be given in millimeters. """ self._iconSize = size self.calcSizes()
class TColorPickerSV(QWidget): changed = Signal(QColor) doubleClicked = Signal() def __init__(self, parent): super(TColorPickerSV, self).__init__(parent) self.setStatusTip("Saturation, Value. To control use Arrows") self._size = QSize(25, 25) self._scale = 11 self.setMinimumSize(self._size * self._scale) self._color = QColor(Qt.white) self._pixmap = QPixmap() self._pos = QPoint(24, 24) self._hue = 0 self._saturation = 255 self._value = 255 def set_hue(self, hue): hue = clamp(hue, 0, 360) self._hue = hue if hue < 360 else 0 iw = self._size.width() ih = self._size.height() bpl = iw * 4 bits = bytearray(iw * ih * 4) color = QColor() for v in range(ih): for s in range(iw): color.setHsv(hue, 255.0 / (iw - 1) * s, 255.0 - 255.0 / (ih - 1) * v) color = qcolor_linear_to_srgb(color) (r, g, b, a) = color.getRgb() # For 3ds Max use bits[index] = chr(value) bits[bpl * v + 4 * s + 0] = b bits[bpl * v + 4 * s + 1] = g bits[bpl * v + 4 * s + 2] = r # bits[bpl * v + 4*s + 3] = 0xff img = QImage(bits, iw, ih, QImage.Format_RGB32) self._pixmap = QPixmap.fromImage(img).scaled(self.minimumSize(), Qt.KeepAspectRatio, Qt.FastTransformation) self.set_pos(self._pos) def set_pos(self, pos): iw = self._size.width() - 1 ih = self._size.height() - 1 self._pos.setX(clamp(pos.x(), 0, iw)) self._pos.setY(clamp(pos.y(), 0, ih)) self._saturation = clamp(255 * self._pos.x() / iw, 0, 255) self._value = clamp(255 * self._pos.y() / ih, 0, 255) self._color = QColor.fromHsv(self._hue, self._saturation, self._value) self.update() self.changed.emit(self._color) def set_remap_pos(self, mouse_pos): # Remap mouse position pos = QPoint(int(mouse_pos.x() / self._scale), int(self._size.height() - mouse_pos.y() / self._scale)) self.set_pos(pos) def paintEvent(self, event): painter = QPainter(self) painter.drawPixmap(0, 0, self._pixmap) painter.save() painter.setCompositionMode(QPainter.RasterOp_SourceXorDestination) pen = QPen(QBrush(Qt.white), 2., Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin) painter.setPen(pen) s = self._scale painter.drawRect( QRect(self._pos.x() * s - 1, (self._size.height() - 1 - self._pos.y()) * s - 1, s + 2, s + 2)) painter.restore() def mouseMoveEvent(self, event): self.set_remap_pos(event.pos()) event.accept() def mousePressEvent(self, event): self.set_remap_pos(event.pos()) event.accept() def mouseDoubleClickEvent(self, event): self.doubleClicked.emit() event.accept() def keyPressEvent(self, event): key = event.key() if key in [Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down]: x, y = self._pos.x(), self._pos.y() if key == Qt.Key_Left: x -= 1 if key == Qt.Key_Right: x += 1 if key == Qt.Key_Up: y += 1 if key == Qt.Key_Down: y -= 1 self.set_pos(QPoint(x, y)) event.accept()
def resize_image_viewer(self, new_size: QSize): width = max(50, min(new_size.width(), self.MAX_SIZE.width())) height = max(50, min(new_size.height(), self.MAX_SIZE.height())) new_size = QSize(width, height) self.resize(new_size)
class SearchBarEditor(QTableView): """A Google-like search bar, implemented as a QTableView with a _CustomLineEditDelegate in the first row. """ data_committed = Signal() def __init__(self, parent, tutor=None): """Initializes instance. Args: parent (QWidget): parent widget tutor (QWidget, NoneType): another widget used for positioning. """ super().__init__(parent) self._tutor = tutor self._base_size = QSize() self._base_offset = QPoint() self._original_text = None self._orig_pos = None self.first_index = QModelIndex() self.model = QStandardItemModel(self) self.proxy_model = QSortFilterProxyModel(self) self.proxy_model.setSourceModel(self.model) self.proxy_model.filterAcceptsRow = self._proxy_model_filter_accepts_row self.setModel(self.proxy_model) self.verticalHeader().hide() self.horizontalHeader().hide() self.setShowGrid(False) self.setMouseTracking(True) self.setTabKeyNavigation(False) delegate = _CustomLineEditDelegate(self) delegate.text_edited.connect(self._handle_delegate_text_edited) self.setItemDelegateForRow(0, delegate) def set_data(self, current, items): """Populates model. Args: current (str) items (Sequence(str)) """ item_list = [QStandardItem(current)] for item in items: qitem = QStandardItem(item) item_list.append(qitem) qitem.setFlags(~Qt.ItemIsEditable) self.model.invisibleRootItem().appendRows(item_list) self.first_index = self.proxy_model.mapFromSource( self.model.index(0, 0)) def set_base_size(self, size): self._base_size = size def set_base_offset(self, offset): self._base_offset = offset def update_geometry(self): """Updates geometry. """ self.horizontalHeader().setDefaultSectionSize(self._base_size.width()) self.verticalHeader().setDefaultSectionSize(self._base_size.height()) self._orig_pos = self.pos() + self._base_offset if self._tutor: self._orig_pos += self._tutor.mapTo(self.parent(), self._tutor.rect().topLeft()) self.refit() def refit(self): self.move(self._orig_pos) table_height = self.verticalHeader().length() size = QSize(self._base_size.width(), table_height + 2).boundedTo(self.parent().size()) self.resize(size) # Adjust position if widget is outside parent's limits bottom_right = self.mapToGlobal(self.rect().bottomRight()) parent_bottom_right = self.parent().mapToGlobal( self.parent().rect().bottomRight()) x_offset = max(0, bottom_right.x() - parent_bottom_right.x()) y_offset = max(0, bottom_right.y() - parent_bottom_right.y()) self.move(self.pos() - QPoint(x_offset, y_offset)) def data(self): return self.first_index.data(Qt.EditRole) @Slot("QString") def _handle_delegate_text_edited(self, text): """Filters model as the first row is being edited.""" self._original_text = text self.proxy_model.setFilterRegExp("^" + text) self.proxy_model.setData(self.first_index, text) self.refit() def _proxy_model_filter_accepts_row(self, source_row, source_parent): """Always accept first row. """ if source_row == 0: return True return QSortFilterProxyModel.filterAcceptsRow(self.proxy_model, source_row, source_parent) def keyPressEvent(self, event): """Sets data from current index into first index as the user navigates through the table using the up and down keys. """ super().keyPressEvent(event) event.accept( ) # Important to avoid unhandled behavior when trying to navigate outside view limits # Initialize original text. TODO: Is there a better place for this? if self._original_text is None: self.proxy_model.setData(self.first_index, event.text()) self._handle_delegate_text_edited(event.text()) # Set data from current index in model if event.key() in (Qt.Key_Up, Qt.Key_Down): current = self.currentIndex() if current.row() == 0: self.proxy_model.setData(self.first_index, self._original_text) else: self.proxy_model.setData(self.first_index, current.data()) def currentChanged(self, current, previous): super().currentChanged(current, previous) self.edit_first_index() def edit_first_index(self): """Edits first index if valid and not already being edited. """ if not self.first_index.isValid(): return if self.isPersistentEditorOpen(self.first_index): return self.edit(self.first_index) def mouseMoveEvent(self, event): """Sets the current index to the one hovered by the mouse.""" if not self.currentIndex().isValid(): return index = self.indexAt(event.pos()) if index.row() == 0: return self.setCurrentIndex(index) def mousePressEvent(self, event): """Commits data.""" index = self.indexAt(event.pos()) if index.row() == 0: return self.proxy_model.setData(self.first_index, index.data(Qt.EditRole)) self.data_committed.emit()
class MainWidget(QWidget): def __init__(self, parent=None): super(MainWidget, self).__init__(parent) self.set_init_settings() self.setup_ui() def set_init_settings(self): self.video_size = QSize(800, 600) self.record_mode = False self.record_timer = None self.frame_count = 0 self.camera_id = 1 self.capture = None def setup_ui(self): """Initialize widgets. """ self.camera_label = QLabel("Camera ID: ") self.camera_line_edit = QLineEdit("{}".format(self.camera_id)) self.camera_width_label = QLabel("Width: ") self.camera_width_line_edit = QLineEdit("{}".format( self.video_size.width())) self.camera_height_label = QLabel("Height: ") self.camera_height_line_edit = QLineEdit("{}".format( self.video_size.height())) self.camera_load_button = QPushButton("Load Camera") self.camera_load_button.clicked.connect(self.setup_camera) self.camera_id_layout = QHBoxLayout() self.camera_id_layout.addWidget(self.camera_label) self.camera_id_layout.addWidget(self.camera_line_edit) self.camera_id_layout.addWidget(self.camera_width_label) self.camera_id_layout.addWidget(self.camera_width_line_edit) self.camera_id_layout.addWidget(self.camera_height_label) self.camera_id_layout.addWidget(self.camera_height_line_edit) self.camera_id_layout.addWidget(self.camera_load_button) self.image_label = CameraFrame() self.image_label.setFixedSize(self.video_size) self.record_button = QPushButton("Record") self.stop_button = QPushButton("Stop") self.record_button.clicked.connect(self.start_record) self.stop_button.clicked.connect(self.stop_record) self.record_button.setEnabled(False) self.stop_button.setEnabled(False) self.capture_setting = CaptureSettingWidget() self.sub1_layout = QVBoxLayout() self.sub1_layout.addLayout(self.camera_id_layout) self.sub1_layout.addWidget(self.image_label) self.sub12_layout = QHBoxLayout() self.sub12_layout.addWidget(self.record_button) self.sub12_layout.addWidget(self.stop_button) self.sub1_layout.addLayout(self.sub12_layout) self.sub2_layout = QVBoxLayout() self.sub2_layout.addWidget(self.capture_setting) self.main_layout = QHBoxLayout() self.main_layout.addLayout(self.sub1_layout) self.main_layout.addLayout(self.sub2_layout) self.setLayout(self.main_layout) self.record_timer = QTimer() self.record_timer.timeout.connect(self.save_data) def setup_camera(self): """Initialize camera. """ self.change_camera_setting() if self.record_mode is True: self.stop_record() self.change_record_mode(self.record_mode) if self.capture is not None: self.capture.release() self.capture = cv2.VideoCapture(self.camera_id) self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.video_size.width()) self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.video_size.height()) self.timer = QTimer() self.timer.timeout.connect(self.display_video_stream) self.timer.start(30) def change_camera_setting(self): self.camera_id = int(self.camera_line_edit.text()) self.video_size = QSize(int(self.camera_width_line_edit.text()), int(self.camera_height_line_edit.text())) self.image_label.setFixedSize(self.video_size) def display_video_stream(self): """Read frame from camera and repaint QLabel widget. """ _, frame = self.capture.read() frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) frame = cv2.flip(frame, 1) frame = self.draw_captured(frame) image = QImage( frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888, ) self.image_label.setPixmap(QPixmap.fromImage(image)) def draw_captured(self, frame): pos, r = self.get_pos_r() frame = cv2.circle( frame, pos, r, (255, 69, 0), thickness=1, lineType=cv2.LINE_AA, shift=0, ) frame = cv2.circle( frame, pos, 1, (255, 69, 0), thickness=-1, lineType=cv2.LINE_AA, shift=0, ) return frame def get_pos_r(self): if self.capture_setting.use_cursor: if self.image_label.npos is not None: return ( (self.image_label.npos.x(), self.image_label.npos.y()), int(self.capture_setting.RADIUS * self.image_label.r_amp), ) else: return (0, 0), self.capture_setting.RADIUS else: return ( (self.capture_setting.X, self.capture_setting.Y), self.capture_setting.RADIUS, ) def start_record(self): self.frame_count = 0 self.record_mode = True self.change_record_mode(self.record_mode) self.capture_setting.set_save_dirpath() self.start_record_timer() def stop_record(self): self.stop_record_timer() convert_valid_json_data(self.capture_setting.get_full_save_dirpath()) self.record_mode = False self.change_record_mode(self.record_mode) def start_record_timer(self): self.record_timer.start( int(1000 / self.capture_setting.captur_frame_per_s)) def stop_record_timer(self): self.record_timer.stop() def save_data(self): self.frame_count += 1 _, frame = self.capture.read() # frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) frame = cv2.flip(frame, 1) pos, r = self.get_pos_r() # base_name = detail_now_name() base_name = "{:04}_{:02}".format( int(self.frame_count / self.capture_setting.captur_frame_per_s), self.frame_count % self.capture_setting.captur_frame_per_s, ) dirpath = self.capture_setting.get_full_save_dirpath() image_name = "{}.png".format(base_name) # frame = cv2.resize(frame , (int(self.video_size.width()), int(self.video_size.height()))) self.write_image(frame, os.path.join(dirpath, image_name)) json_data = create_circle_labelme_json(pos, r, image_name, frame.shape[0], frame.shape[1], frame) json_path = "{}.json".format(base_name) with open(os.path.join(dirpath, json_path), "w") as f: json.dump(json_data, f, indent=2, ensure_ascii=False) def write_image(self, frame, image_path): cv2.imwrite(image_path, frame) def change_record_mode(self, new_mode): if new_mode: self.record_button.setEnabled(False) self.stop_button.setEnabled(True) else: self.stop_button.setEnabled(False) self.record_button.setEnabled(True) def __del__(self): if self.capture is not None: self.capture.release()
class RetroDrawWidget(QWidget): """ Defines widget for displaying and handling all retro drawing. """ def __init__(self, fgIndex, bgIndex, palette, parent=None): super(RetroDrawWidget, self).__init__(parent) self.canvasSize = QSize(256, 192) self.canvasCenter = QPoint(self.canvasSize.width() / 2, self.canvasSize.height() / 2) self.fgIndex = fgIndex self.bgIndex = bgIndex self.palette = palette self.scale = 4 self.screenSize = self.canvasSize * self.scale self.screenCenter = self.canvasCenter * self.scale self.grid = QImage(self.screenSize, QImage.Format_RGBA8888) self.grid.fill(QColor(0, 0, 0, 0)) for y in range(0, self.screenSize.height()): for x in range(0, self.screenSize.width(), 8 * self.scale): self.grid.setPixelColor(x, y, QColor(0, 0, 0, 255)) for x in range(0, self.screenSize.width()): for y in range(0, self.screenSize.height(), 8 * self.scale): self.grid.setPixelColor(x, y, QColor(0, 0, 0, 255)) self._gridEnabled = True self._gridOpacity = 0.2 self._guideFilename = None self._guide = None self._guideEnabled = True self._guideOpacity = 0.2 self._guideCoords = QPoint(0, 0) self._guideZoom = 1.0 self._scratch = QImage(self.screenSize, QImage.Format_RGBA8888) self._scratch.fill(QColor(0, 0, 0, 0)) self.drawable = ZXSpectrumBuffer() self.setCursor(Qt.CrossCursor) self._mouseLastPos = self.getLocalMousePos() self._mouseDelta = QPoint(0, 0) self._mousePressed = MouseButton.NONE self._drawMode = DrawingMode.DOTTED self._lineState = None def encodeToJSON(self): rdict = dict() rdict["fg_index"] = self.fgIndex rdict["bg_index"] = self.bgIndex rdict["palette"] = self.palette rdict["grid_enabled"] = self._gridEnabled rdict["grid_opacity"] = self._gridOpacity rdict["guide_filename"] = self._guideFilename rdict["guide_enabled"] = self._guideEnabled rdict["guide_opacity"] = self._guideOpacity rdict["guide_coords_x"] = self._guideCoords.x() rdict["guide_coords_y"] = self._guideCoords.y() rdict["guide_zoom"] = self._guideZoom rdict["drawable"] = self.drawable.encodeToJSON() return rdict def decodeFromJSON(self, json): self.fgIndex = json["fg_index"] self.bgIndex = json["bg_index"] self.palette = json["palette"] self._gridEnabled = json["grid_enabled"] self._gridOpacity = json["grid_opacity"] self._guideFilename = json["guide_filename"] self._guide = QPixmap(self._guideFilename) self._guideEnabled = json["guide_enabled"] self._guideOpacity = json["guide_opacity"] self._guideCoords.setX(json["guide_coords_x"]) self._guideCoords.setY(json["guide_coords_y"]) self._guideZoom = json["guide_zoom"] self.drawable.decodeFromJSON(json["drawable"]) def sizeHint(self): return self.screenSize def minimumSizeHint(self): return self.screenSize def getLocalMousePos(self): return self.mapFromGlobal(QCursor.pos()) def paintEvent(self, event): super(RetroDrawWidget, self).paintEvent(event) painter = QPainter(self) rectTarget = self.rect() rectSource = QRect(QPoint(0, 0), self.canvasSize) painter.drawPixmap(rectTarget, self.drawable.qpixmap, rectSource) if self._guide and self._guideEnabled: painter.setOpacity(self._guideOpacity) self._paintZoomedGuide(painter) if self._gridEnabled: painter.setOpacity(self._gridOpacity) painter.drawImage(rectTarget, self.grid, rectTarget) painter.setOpacity(1.0) painter.drawImage(rectTarget, self._scratch, rectTarget) painter.end() def mousePressEvent(self, event): self._mouseLastPos = self.getLocalMousePos() if event.button() == Qt.LeftButton: self._mousePressed = MouseButton.LEFT elif event.button() == Qt.RightButton: self._mousePressed = MouseButton.RIGHT if self._drawMode == DrawingMode.PEN: if self._mousePressed == MouseButton.LEFT: self.doDraw(event.localPos(), True) elif self._drawMode == DrawingMode.DOTTED: if self._mousePressed == MouseButton.LEFT: self.doDraw(event.localPos(), True) elif self._mousePressed == MouseButton.RIGHT: self.doDraw(event.localPos(), False) elif self._drawMode == DrawingMode.ERASE: if self._mousePressed == MouseButton.LEFT: self.doDraw(event.localPos(), False) elif self._drawMode == DrawingMode.LINE: if self._mousePressed == MouseButton.LEFT: self._lineState = [event.localPos(), event.localPos()] painter = QPainter(self._scratch) painter.setPen(Qt.black) painter.drawLine(self._lineState[0], self._lineState[1]) painter.end() self.update(self.rect()) elif self._drawMode == DrawingMode.ATTR: if self._mousePressed == MouseButton.LEFT: self.doDrawAttr(event.localPos()) def mouseReleaseEvent(self, event): if self._drawMode == DrawingMode.LINE: if self._mousePressed == MouseButton.LEFT and self._lineState: self._lineState[1] = event.localPos() painter = QPainter(self._scratch) painter.setPen(Qt.black) painter.drawLine(self._lineState[0], self._lineState[1]) self.doDrawLine(self._lineState[0], self._lineState[1]) painter.end() self._lineState = None self._scratch.fill(QColor(0, 0, 0, 0)) self.update(self.rect()) self._mousePressed = MouseButton.NONE def mouseMoveEvent(self, event): oldMousePos = self._mouseLastPos newMousePos = self.getLocalMousePos() self._mouseDelta = newMousePos - self._mouseLastPos self._mouseLastPos = newMousePos if self._drawMode == DrawingMode.PEN: if self._mousePressed == MouseButton.LEFT: self.doDrawLine(oldMousePos, newMousePos) if self._drawMode == DrawingMode.DOTTED: if self._mousePressed == MouseButton.LEFT: self.doDraw(newMousePos, True) elif self._mousePressed == MouseButton.RIGHT: self.doDraw(newMousePos, False) elif self._drawMode == DrawingMode.ERASE: if self._mousePressed == MouseButton.LEFT: self.doDraw(newMousePos, False) elif self._drawMode == DrawingMode.GUIDE: if self._mousePressed == MouseButton.LEFT: self._guideCoords += self._mouseDelta self.update(self.rect()) elif self._drawMode == DrawingMode.LINE: if self._mousePressed == MouseButton.LEFT and self._lineState: painter = QPainter(self._scratch) self._scratch.fill(QColor(0, 0, 0, 0)) painter.setPen(Qt.black) self._lineState[1] = event.localPos() painter.drawLine(self._lineState[0], self._lineState[1]) painter.end() self.update(self.rect()) elif self._drawMode == DrawingMode.ATTR: if self._mousePressed == MouseButton.LEFT: self.doDrawAttr(event.localPos()) def wheelEvent(self, event): if self._mousePressed: if self._drawMode == DrawingMode.GUIDE: delta = event.pixelDelta().y() * 0.01 if delta != 0.0: self._guideZoom += delta self._guideZoom = self.clamp(self._guideZoom, 0.1, 8.0) self.update(self.rect()) @staticmethod def clamp(value, min, max): if value < min: return min elif value > max: return max return value def doDraw(self, localPos, setPixel): x = localPos.x() // self.scale y = localPos.y() // self.scale if setPixel: self.drawable.setPixel(x, y, self.fgIndex, self.bgIndex, self.palette) else: self.drawable.erasePixel(x, y, self.fgIndex, self.bgIndex, self.palette) self.update(self.rect()) def doDrawAttr(self, localPos): x = localPos.x() // self.scale y = localPos.y() // self.scale self.drawable.setAttr(x, y, self.fgIndex, self.bgIndex, self.palette) self.update(self.rect()) def doDrawLine(self, localStartPos, localEndPos): x1 = localStartPos.x() // self.scale y1 = localStartPos.y() // self.scale x2 = localEndPos.x() // self.scale y2 = localEndPos.y() // self.scale self.drawable.drawLine(x1, y1, x2, y2, self.fgIndex, self.bgIndex, self.palette) self.update(self.rect()) def setColor(self, fgIndex, bgIndex, palette): self.fgIndex = fgIndex self.bgIndex = bgIndex self.palette = palette def saveImage(self, filename, format=None): self.drawable.saveBuffer(filename) def setGrid(self, checked): self._gridEnabled = checked self.repaint() def setGridOpacity(self, value): self._gridOpacity = value / 100.0 self.repaint() def setGuideImage(self, filename): self._guideFilename = filename self._guide = QPixmap(self._guideFilename) self.repaint() def setGuide(self, checked): self._guideEnabled = checked self.repaint() def setGuideOpacity(self, value): self._guideOpacity = value / 100.0 self.repaint() def setMode(self, mode): self._drawMode = mode def clear(self): self.drawable.clear(self.fgIndex, self.bgIndex, self.palette) self.repaint() def _paintZoomedGuide(self, painter): guideZoom = self._guide.scaled(self._guide.width() * self._guideZoom, self._guide.height() * self._guideZoom, Qt.KeepAspectRatio) pos = QPoint( self._guideCoords.x() + (self.screenCenter.x() - guideZoom.width() / 2), self._guideCoords.y() + (self.screenCenter.y() - guideZoom.height() / 2)) painter.drawPixmap(pos, guideZoom) def copyGuide(self): # This isn't the most efficient way to do this but it just needs to be # reasonably fast self.drawable.clear(self.fgIndex, self.bgIndex, self.palette) guide_copy = QImage(self.screenSize, QImage.Format_RGBA8888) guide_copy.fill(QColor("white")) painter = QPainter(guide_copy) self._paintZoomedGuide(painter) shrunk_guide = guide_copy.smoothScaled(self.canvasSize.width(), self.canvasSize.height()) mono_guide = shrunk_guide.convertToFormat(QImage.Format_Mono) for x in range(0, self.canvasSize.width()): for y in range(0, self.canvasSize.height()): if mono_guide.pixel(x, y) == QColor("black"): self.drawable.setPixel(x, y, self.fgIndex, self.bgIndex, self.palette) painter.end() self.repaint()