def qimage_to_numpy(self, image: QImage) -> Image: image = image.convertToFormat(QImage.Format.Format_RGB32) width = image.width() height = image.height() ptr = image.constBits() arr = np.array(ptr).reshape((height, width, 4))[:, :, :3] return arr
def qimg_to_rgb_arr(qimg: QImage) -> RGBArrayF: """Convert a :class:`QtGui.QImage` to an :data:`RGBArrayF` """ fmt = QImage.Format_RGB32 if qimg.format() != fmt: qimg = qimg.convertToFormat(fmt) width, height = qimg.width(), qimg.height() num_pixels = width * height bfr = qimg.constBits() int_arr = np.frombuffer(bfr, dtype=np.uint8, count=num_pixels * 4) bgra_arr = int_arr.reshape((height, width, 4)) / 255 # Format_RGB32 stored as 0xffRRGGBB # so take only the first 3 items but in reverse rgb_arr = bgra_arr[..., 2::-1] return rgb_arr
class Canvas(QWidget): content_changed = Signal() _background_color = QColor.fromRgb(0, 0, 0) _foreground_color = QColor.fromRgb(255, 255, 255) def __init__(self, parent, w, h, pen_width, scale): super().__init__(parent) self.w = w self.h = h self.scaled_w = scale * w self.scaled_h = scale * h self.scale = scale # Set size self.setFixedSize(self.scaled_w, self.scaled_h) # Create image self.small_image = QImage(self.w, self.h, QImage.Format_RGB32) self.small_image.fill(self._background_color) self.large_image = QImage(self.scaled_w, self.scaled_h, QImage.Format_RGB32) self.large_image.fill(self._background_color) # Create pen self.pen = QPen() self.pen.setColor(self._foreground_color) self.pen.setJoinStyle(Qt.RoundJoin) self.pen.setCapStyle(Qt.RoundCap) self.pen.setWidthF(scale * pen_width) # There is currently no path self.currentPath = None self.content_changed.connect(self.repaint) def _get_painter(self, paintee): painter = QPainter(paintee) painter.setPen(self.pen) painter.setRenderHint(QPainter.Antialiasing, True) return painter def _derive_small_image(self, large_image=None): if large_image is None: large_image = self.large_image # Downsample image self.small_image = large_image.scaled(self.w, self.h, mode=Qt.SmoothTransformation) self.content_changed.emit() def _current_path_updated(self, terminate_path=False): # Determine whether to draw on the large image directly or whether to make a temporary copy paintee = self.large_image if terminate_path else self.large_image.copy( ) # Draw path on the large image of choice painter = self._get_painter(paintee) if self.currentPath.elementCount() != 1: painter.drawPath(self.currentPath) else: painter.drawPoint(self.currentPath.elementAt(0)) painter.end() # Optionally terminate the path if terminate_path: self.currentPath = None # Downsample image self._derive_small_image(paintee) def _clear_image(self): self.large_image.fill(self._background_color) self._derive_small_image() def get_content(self): return np.asarray(self.small_image.constBits()).reshape( (self.h, self.w, -1)) def set_content(self, image_rgb): for row in range(image_rgb.shape[0]): for col in range(image_rgb.shape[1]): self.small_image.setPixel(col, row, image_rgb[row, col]) self.large_image = self.small_image.scaled( self.scaled_w, self.scaled_h, mode=Qt.SmoothTransformation) self._derive_small_image() self.content_changed.emit() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: # Create new path self.currentPath = QPainterPath() self.currentPath.moveTo(event.pos()) self._current_path_updated() def mouseMoveEvent(self, event): if (event.buttons() & Qt.LeftButton) and self.currentPath is not None: # Add point to current path self.currentPath.lineTo(event.pos()) self._current_path_updated() def mouseReleaseEvent(self, event): if (event.button() == Qt.LeftButton) and self.currentPath is not None: # Add terminal point to current path self.currentPath.lineTo(event.pos()) self._current_path_updated(terminate_path=True) elif event.button() == Qt.RightButton: self._clear_image() def paintEvent(self, event): paint_rect = event.rect() # Only paint the surface that needs painting painter = self._get_painter(self) # Draw image painter.scale(self.scale, self.scale) painter.drawImage(paint_rect, self.small_image, paint_rect) painter.end() painter = self._get_painter(self) #if self.currentPath is not None: # painter.drawPath(self.currentPath) @Slot() def repaint(self): super().repaint()