def paint( self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionGraphicsItem, widget: QtWidgets.QWidget = None, ): view = self.scene().views()[0] view_width = view.mapToScene(0, 0, self.width, 1).boundingRect().width() width, unit = self.getWidthAndUnit(view_width) # Current scale text = f"{width * self.units[self.unit] / self.units[unit]:.3g} {unit}" width = width * view.transform().m11() fm = QtGui.QFontMetrics(self.font, painter.device()) path = QtGui.QPainterPath() path.addText( self.width / 2.0 - fm.boundingRect(text).width() / 2.0, fm.ascent(), self.font, text, ) painter.strokePath(path, QtGui.QPen(QtCore.Qt.black, 2.0)) painter.fillPath(path, QtGui.QBrush(self.color, QtCore.Qt.SolidPattern)) # Draw the bar rect = QtCore.QRectF(self.width / 2.0 - width / 2.0, fm.height(), width, self.height) painter.setPen(QtGui.QPen(QtCore.Qt.black, 1.0)) painter.setBrush(QtGui.QBrush(self.color, QtCore.Qt.SolidPattern)) painter.drawRect(rect)
def paintEvent(self, event): super().paintEvent(event) painter = QPainter(self) brush = QBrush() brush.setColor(self.color) brush.setStyle(Qt.SolidPattern) rect = QRect(3, 3, -6 + self.width(), -6 + painter.device().height()) painter.setBrush(brush) painter.fillRect(rect, brush)
def assert_equal(self): __tracebackhide__ = True self.end() self.different_pixels = 0 actual_image: QImage = self.actual.device().toImage() expected_image: QImage = self.expected.device().toImage() diff_pixmap = QPixmap(actual_image.width(), actual_image.height()) diff = QPainter(diff_pixmap) try: white = QColor('white') diff.fillRect(0, 0, actual_image.width(), actual_image.height(), white) for x in range(actual_image.width()): for y in range(actual_image.height()): actual_colour = actual_image.pixelColor(x, y) expected_colour = expected_image.pixelColor(x, y) diff.setPen( self.diff_colour(actual_colour, expected_colour, x, y)) diff.drawPoint(x, y) finally: diff.end() diff_image: QImage = diff.device().toImage() display_diff(actual_image, diff_image, expected_image, self.different_pixels) if self.different_pixels == 0: return actual_image.save(str(self.work_dir / (self.name + '_actual.png'))) expected_image.save(str(self.work_dir / (self.name + '_expected.png'))) diff_path = self.work_dir / (self.name + '_diff.png') is_saved = diff_image.save(str(diff_path)) diff_width = self.diff_max_x - self.diff_min_x + 1 diff_height = self.diff_max_y - self.diff_min_y + 1 diff_section = QImage(diff_width, diff_height, QImage.Format_RGB32) diff_section_painter = QPainter(diff_section) try: diff_section_painter.drawPixmap(0, 0, diff_width, diff_height, QPixmap.fromImage(diff_image), self.diff_min_x, self.diff_min_y, diff_width, diff_height) finally: diff_section_painter.end() # To see an image dumped in the Travis CI log, copy the text from the # log, and paste it in test_pixmap_differ.test_decode_image. print(f'Encoded image of differing section ' f'({self.diff_min_x}, {self.diff_min_y}) - ' f'({self.diff_max_x}, {self.diff_max_y}):') print(encode_image(diff_section)) message = f'Found {self.different_pixels} different pixels, ' message += f'see' if is_saved else 'could not write' message += f' {diff_path.relative_to(Path(__file__).parent.parent)}.' assert self.different_pixels == 0, message
def paint( self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionGraphicsItem, widget: QtWidgets.QWidget = None, ): fm = QtGui.QFontMetrics(self.font, painter.device()) path = QtGui.QPainterPath() path.addText(0, fm.ascent(), self.font, self.text()) painter.strokePath(path, QtGui.QPen(QtCore.Qt.black, 2.0)) painter.fillPath(path, QtGui.QBrush(self.color, QtCore.Qt.SolidPattern))
def paint( self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionGraphicsItem, widget: QtWidgets.QWidget = None, ): width = painter.viewport().width() fm = QtGui.QFontMetrics(self.font, painter.device()) rect = QtCore.QRect(0, fm.height(), width, self.height) painter.drawPixmap(rect, self.pixmap) painter.setPen(QtGui.QPen(QtCore.Qt.black, 2.0)) painter.setBrush(QtGui.QBrush(QtCore.Qt.white, QtCore.Qt.NoBrush)) painter.drawRect(rect) path = QtGui.QPainterPath() # Todo: find a better pad value path.addText( width - fm.boundingRect(self.unit).width() - 10, fm.ascent(), self.font, self.unit, ) vrange = self.vmax - self.vmin if vrange <= 0.0: return for value in self.niceTextValues(7, trim=1): x = width * (value - self.vmin) / vrange text = f"{value:.6g}" path.addText( x - fm.boundingRect(text).width() / 2.0, fm.ascent(), self.font, text, ) if self.checkmarks: path.addRect( x - fm.lineWidth() / 2.0, fm.ascent() + fm.underlinePos(), fm.lineWidth() * 2.0, fm.underlinePos(), ) painter.strokePath(path, QtGui.QPen(QtCore.Qt.black, 2.0)) painter.fillPath(path, QtGui.QBrush(self.color, QtCore.Qt.SolidPattern))
def draw_monitor_label(self, painter: QPainter, rect: QRectF, txt: str): painter.save() font = self.monitor_label_font font_metrics = QFontMetrics(font, painter.device()) bounding_rect = font_metrics.boundingRect(rect.toRect(), 0, txt) x_factor = rect.width() / bounding_rect.width() y_factor = rect.height() / bounding_rect.height() factor = min(x_factor, y_factor) font.setPointSizeF(font.pointSizeF() * factor) painter.setFont(font) painter.setPen(self.monitor_label_font_color) painter.drawText(rect, Qt.AlignCenter, txt) painter.restore()
def paint( self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionGraphicsItem, widget: QtWidgets.QWidget = None, ): fm = QtGui.QFontMetrics(self.font, painter.device()) y = fm.ascent() for text, color in zip(self._texts, self.colors): path = QtGui.QPainterPath() path.addText(0, y, self.font, text) painter.strokePath(path, QtGui.QPen(QtCore.Qt.black, 2.0)) painter.fillPath(path, QtGui.QBrush(color, QtCore.Qt.SolidPattern)) y += fm.height()
def render_display(display: GameDisplay, painter: QPainter, is_closed: bool = True): """ Check scene size, render, then clear scene. You have to clear the scene to avoid a crash after running several unit tests. :param display: display widget whose children contain a QGraphicsView to render. :param painter: a canvas to render on :param is_closed: True if the display should be closed after rendering. Be sure to close the display before exiting the test, if it contains any items with reference cycles back to the scene. """ __tracebackhide__ = True try: for child in display.children(): if isinstance(child, QGraphicsView): view = child break else: raise ValueError("No QGraphicsView in display's children.") view.grab() # Force layout to recalculate, if needed. scene_size = view.contentsRect().size() painter_size = painter.device().size() if scene_size != painter_size: display_size = find_display_size(display, view, painter_size) message = (f"Try resizing display to " f"{display_size.width()}x{display_size.height()}.") painter.drawText( QRect(0, 0, painter_size.width(), painter_size.height()), Qt.AlignCenter | Qt.TextWordWrap, message) return assert scene_size == painter_size view.scene().render(painter) finally: if is_closed: display.close()
def paintEvent(self, _): """ Paints the widget contents """ painter = QPainter(self) device = painter.device() optimal_vertical_tile_size = device.height() // self.model.height optimal_horizontal_tile_size = device.width() // self.model.width tile_size = min(optimal_vertical_tile_size, optimal_horizontal_tile_size) brush = QBrush() brush.setStyle(Qt.SolidPattern) for pos in self.model.all_positions(): block = self.model.state[pos] if block is Block.EMPTY: continue if not block.visible: continue r, g, b = block.color q_color = QColor(r, g, b) brush.setColor(q_color) x, y = pos canvas_x = self.START_X + tile_size * x canvas_y = self.START_Y + tile_size * y rect = QRect(canvas_x, canvas_y, tile_size, tile_size) painter.fillRect(rect, brush) painter.end()
class PixmapDiffer: def __init__(self): self.name = None self.actual_pixmap = self.expected_pixmap = None self.actual = self.expected = None self.different_pixels = 0 self.diff_min_x = self.diff_min_y = None self.diff_max_x = self.diff_max_y = None self.max_diff = 0 self.names = set() self.work_dir: Path = (Path(__file__).parent.parent / 'tests' / 'pixmap_diffs') self.work_dir.mkdir(exist_ok=True) for work_file in self.work_dir.iterdir(): if work_file.name == 'README.md': continue assert work_file.suffix == '.png' work_file.unlink() @contextmanager def create_painters(self, width: int, height: int, name: str, max_diff: int = 0 ) -> typing.Iterator[typing.Tuple[QPainter, QPainter]]: self.max_diff = max_diff try: yield self.start(width, height, name) finally: self.end() self.assert_equal() def start(self, width: int, height: int, name: str) -> typing.Tuple[QPainter, QPainter]: """ Create painters for the actual and expected images. Caller must either call end() or assert_equal() to properly clean up the painters and pixmaps. Caller may either paint through the returned painters, or call the end() method and create a new painter on the same device. Order matters, though! """ assert name not in self.names, f'Duplicate name: {name!r}.' self.names.add(name) self.name = name white = QColor('white') self.actual_pixmap = QPixmap(width, height) self.actual_pixmap.fill(white) self.actual = QPainter(self.actual_pixmap) self.expected_pixmap = QPixmap(width, height) self.expected_pixmap.fill(white) self.expected = QPainter(self.expected_pixmap) return self.actual, self.expected def end(self): if self.actual and self.actual.isActive(): self.actual.end() if self.expected and self.expected.isActive(): self.expected.end() def assert_equal(self): __tracebackhide__ = True self.end() self.different_pixels = 0 actual_image: QImage = self.actual.device().toImage() expected_image: QImage = self.expected.device().toImage() diff_pixmap = QPixmap(actual_image.width(), actual_image.height()) diff = QPainter(diff_pixmap) try: white = QColor('white') diff.fillRect(0, 0, actual_image.width(), actual_image.height(), white) for x in range(actual_image.width()): for y in range(actual_image.height()): actual_colour = actual_image.pixelColor(x, y) expected_colour = expected_image.pixelColor(x, y) diff.setPen( self.diff_colour(actual_colour, expected_colour, x, y)) diff.drawPoint(x, y) finally: diff.end() diff_image: QImage = diff.device().toImage() display_diff(actual_image, diff_image, expected_image, self.different_pixels) if self.different_pixels == 0: return actual_image.save(str(self.work_dir / (self.name + '_actual.png'))) expected_image.save(str(self.work_dir / (self.name + '_expected.png'))) diff_path = self.work_dir / (self.name + '_diff.png') is_saved = diff_image.save(str(diff_path)) diff_width = self.diff_max_x - self.diff_min_x + 1 diff_height = self.diff_max_y - self.diff_min_y + 1 diff_section = QImage(diff_width, diff_height, QImage.Format_RGB32) diff_section_painter = QPainter(diff_section) try: diff_section_painter.drawPixmap(0, 0, diff_width, diff_height, QPixmap.fromImage(diff_image), self.diff_min_x, self.diff_min_y, diff_width, diff_height) finally: diff_section_painter.end() # To see an image dumped in the Travis CI log, copy the text from the # log, and paste it in test_pixmap_differ.test_decode_image. print(f'Encoded image of differing section ' f'({self.diff_min_x}, {self.diff_min_y}) - ' f'({self.diff_max_x}, {self.diff_max_y}):') print(encode_image(diff_section)) message = f'Found {self.different_pixels} different pixels, ' message += f'see' if is_saved else 'could not write' message += f' {diff_path.relative_to(Path(__file__).parent.parent)}.' assert self.different_pixels == 0, message def diff_colour(self, actual_colour: QColor, expected_colour: QColor, x: int, y: int): diff_size = (abs(actual_colour.red() - expected_colour.red()) + abs(actual_colour.green() - expected_colour.green()) + abs(actual_colour.blue() - expected_colour.blue())) if diff_size <= self.max_diff: diff_colour = actual_colour.toRgb() diff_colour.setAlpha(diff_colour.alpha() // 3) return diff_colour if self.different_pixels == 0: self.diff_min_x = self.diff_max_x = x self.diff_min_y = self.diff_max_y = y else: self.diff_min_x = min(self.diff_min_x, x) self.diff_max_x = max(self.diff_max_x, x) self.diff_min_y = min(self.diff_min_y, y) self.diff_max_y = max(self.diff_max_y, y) self.different_pixels += 1 # Colour dr = 0xff dg = (actual_colour.green() + expected_colour.green()) // 5 db = (actual_colour.blue() + expected_colour.blue()) // 5 # Opacity da = 0xff return QColor(dr, dg, db, da)