def __init__(self, parent=None): QWidget.__init__(self, parent) self.setAcceptDrops(True) self.setMouseTracking(True) self.setFocusPolicy(Qt.FocusPolicy.ClickFocus) self.selection_state = SelectionState() self.undo_stack = u = QUndoStack() u.setUndoLimit(10) u.canUndoChanged.connect(self.emit_undo_redo_state) u.canRedoChanged.connect(self.emit_undo_redo_state) self.original_image_data = None self.is_valid = False self.original_image_format = None self.current_image = None self.current_scaled_pixmap = None self.last_canvas_size = None self.target = QRectF(0, 0, 0, 0) self.undo_action = a = self.undo_stack.createUndoAction( self, _('Undo') + ' ') a.setIcon(QIcon(I('edit-undo.png'))) self.redo_action = a = self.undo_stack.createRedoAction( self, _('Redo') + ' ') a.setIcon(QIcon(I('edit-redo.png')))
def draw_selection_rect(self, painter): cr, sr = self.target, self.selection_state.rect painter.setPen(self.SELECT_PEN) painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) if self.selection_state.current_mode == 'selected': # Shade out areas outside the selection rect for r in ( QRectF(cr.topLeft(), QPointF(sr.left(), cr.bottom())), # left QRectF(QPointF(sr.left(), cr.top()), sr.topRight()), # top QRectF(QPointF(sr.right(), cr.top()), cr.bottomRight()), # right QRectF(sr.bottomLeft(), QPointF(sr.right(), cr.bottom())), # bottom ): painter.fillRect(r, self.SHADE_COLOR) dr = self.get_drag_rect() if self.selection_state.in_selection and dr is not None: # Draw the resize rectangle painter.save() painter.setCompositionMode( QPainter.CompositionMode.RasterOp_SourceAndNotDestination) painter.setClipRect(sr.adjusted(1, 1, -1, -1)) painter.drawRect(dr) painter.restore() # Draw the selection rectangle painter.setCompositionMode( QPainter.CompositionMode.RasterOp_SourceAndNotDestination) painter.drawRect(sr)
def __call__(self, canvas): if canvas.has_selection and canvas.selection_state.rect is not None: pimg = self.after_image img = self.after_image = QImage(canvas.current_image) rect = QRectF(*get_selection_rect(img, canvas.selection_state.rect, canvas.target)) p = QPainter(img) p.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True) p.drawImage(rect, pimg, QRectF(pimg.rect())) p.end() return self.after_image
def paint_background(self, painter): br = 12 # border_radius bw = 1 # border_width pal = self.palette() c = pal.color(QPalette.ColorRole.Window) c.setAlphaF(0.9) p = QPainterPath() p.addRoundedRect(QRectF(self.rect()), br, br) painter.fillPath(p, c) p.addRoundedRect(QRectF(self.rect()).adjusted(bw, bw, -bw, -bw), br, br) painter.fillPath(p, pal.color(QPalette.ColorRole.WindowText))
def draw_pixmap(self, painter): p = self.current_scaled_pixmap try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() width, height = int(p.width() / dpr), int(p.height() / dpr) pwidth, pheight = self.last_canvas_size x = int(abs(pwidth - width) / 2.) y = int(abs(pheight - height) / 2.) self.target = QRectF(x, y, width, height) painter.drawPixmap(self.target, p, QRectF(p.rect()))
def drawForeground(self, g, rect): g.setBrush(Color.flower) g.setPen(no_pen) for it in self.all(Cell): if it.flower: poly = _flower_poly.translated(it.scenePos()) g.drawPolygon(poly) g.setBrush(QBrush(qt.NoBrush)) pen = QPen(Color.flower_border, 2) pen.setCosmetic(True) g.setPen(pen) for it in self.all(Cell): if it.flower: poly = _flower_poly.translated(it.scenePos()) g.drawPolygon(poly) g.setPen(no_pen) g.setBrush(Color.beam) for it in self.all(Column): if it.beam: poly = QPolygonF(QRectF(-0.03, 0.525, 0.06, 1e6)) poly = QTransform().translate(it.scenePos().x(), it.scenePos().y()).rotate( it.rotation()).map(poly) poly = poly.intersected(QPolygonF(rect)) g.drawConvexPolygon(poly)
def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, self.dummy_index) painter.save() painter.setClipRect(QRectF(option.rect)) painter.translate(option.rect.topLeft()) self.to_doc(index, option).drawContents(painter) painter.restore()
def fit(self): rect = self.scene.itemsBoundingRect().adjusted(-0.3, -0.3, 0.3, 0.3) self.setSceneRect(rect) self.fitInView(rect, qt.KeepAspectRatio) zoom = self.transform().mapRect(QRectF(0, 0, 1, 1)).width() if zoom > 100: self.resetTransform() self.scale(100, 100)
def paint(self, painter, option, index): painter.save() painter.setClipRect(QRectF(option.rect)) if hasattr(QStyle, 'CE_ItemViewItem'): QApplication.style().drawControl(QStyle.ControlElement.CE_ItemViewItem, option, painter) elif option.state & QStyle.StateFlag.State_Selected: painter.fillRect(option.rect, option.palette.highlight()) painter.translate(option.rect.topLeft()) self.to_doc(index).drawContents(painter) painter.restore()
def get_drag_rect(self): sr = self.selection_state.rect dc = self.selection_state.drag_corner if None in (sr, dc): return dx, dy = self.dc_size if None in dc: # An edge if dc[0] is None: top = sr.top() if dc[1] == 'top' else sr.bottom() - dy ans = QRectF(sr.left() + dx, top, sr.width() - 2 * dx, dy) else: left = sr.left() if dc[0] == 'left' else sr.right() - dx ans = QRectF(left, sr.top() + dy, dx, sr.height() - 2 * dy) else: # A corner left = sr.left() if dc[0] == 'left' else sr.right() - dx top = sr.top() if dc[1] == 'top' else sr.bottom() - dy ans = QRectF(left, top, dx, dy) return ans
def __init__(self, scene): QGraphicsView.__init__(self, scene) self.scene = scene self.setBackgroundBrush(QBrush(qt.white)) self.setResizeAnchor(QGraphicsView.AnchorViewCenter) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setRenderHints(self.renderHints()|QPainter.Antialiasing) self.setHorizontalScrollBarPolicy(qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(qt.ScrollBarAlwaysOff) inf = -1e10 self.setSceneRect(QRectF(QPointF(-inf, -inf), QPointF(inf, inf))) self.scale(50, 50) #*1.00955
def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): painter.fillRect(rect, self.color1) r = QRect(0, int(title_block.position.y), rect.width(), title_block.height + subtitle_block.height + subtitle_block.line_spacing // 2 + title_block.leading) painter.save() p = QPainterPath() p.addRoundedRect(QRectF(r), 10, 10 * r.width()/r.height(), Qt.SizeMode.RelativeSize) painter.setClipPath(p) painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.fillRect(r, self.color2) painter.restore() r = QRect(0, 0, int(title_block.position.x), rect.height()) painter.fillRect(r, self.color2) return self.ccolor2, self.ccolor2, self.ccolor1
def wheelEvent(self, e): try: d = e.angleDelta().y() except AttributeError: d = e.delta() d = 1.0015**d #1.00005 zoom = self.transform().scale(d, d).mapRect(QRectF(0, 0, 1, 1)).width() if zoom<10 and d<1: return elif zoom>350 and d>1: return #self.resetTransform() self.scale(d, d)
def members(self): try: sr = self.scene().sceneRect() except AttributeError: return poly = QPolygonF(QRectF(-0.001, 0.05, 0.002, 2*max(sr.width(), sr.height()))) if abs(self.rotation())>1e-2: poly = QTransform().rotate(self.rotation()).map(poly) poly.translate(self.scenePos()) items = self.scene().items(poly) for it in items: if isinstance(it, Cell): if not poly.containsPoint(it.pos(), qt.OddEvenFill): continue yield it
def mouseMoveEvent(self, ev): changed = False if self.selection_state.in_selection: changed = True self.selection_state.in_selection = False self.selection_state.drag_corner = None pos = ev.pos() cursor = Qt.CursorShape.ArrowCursor try: if not self.target.contains(pos): return if ev.buttons() & Qt.MouseButton.LeftButton: if self.selection_state.last_press_point is not None and self.selection_state.current_mode is not None: if self.selection_state.current_mode == 'select': self.selection_state.rect = QRectF( self.selection_state.last_press_point, pos).normalized() changed = True elif self.selection_state.last_drag_pos is not None: self.selection_state.in_selection = True self.selection_state.drag_corner = self.selection_state.dragging dp = pos - self.selection_state.last_drag_pos self.selection_state.last_drag_pos = pos self.move_selection(dp) cursor = self.get_cursor() changed = True else: if self.selection_state.rect is None or not self.selection_state.rect.contains( pos): return if self.selection_state.current_mode == 'selected': if self.selection_state.rect is not None and self.selection_state.rect.contains( pos): self.selection_state.drag_corner = self.get_drag_corner( pos) self.selection_state.in_selection = True cursor = self.get_cursor() changed = True finally: if changed: self.update() self.setCursor(cursor)
class Canvas(QWidget): BACKGROUND = QColor(60, 60, 60) SHADE_COLOR = QColor(0, 0, 0, 180) SELECT_PEN = QPen(QColor(Qt.GlobalColor.white)) selection_state_changed = pyqtSignal(object) selection_area_changed = pyqtSignal(object) undo_redo_state_changed = pyqtSignal(object, object) image_changed = pyqtSignal(object) @property def has_selection(self): return self.selection_state.current_mode == 'selected' @property def is_modified(self): return self.current_image is not self.original_image # Drag 'n drop {{{ def dragEnterEvent(self, event): md = event.mimeData() if dnd_has_extension(md, image_extensions()) or dnd_has_image(md): event.acceptProposedAction() def dropEvent(self, event): event.setDropAction(Qt.DropAction.CopyAction) md = event.mimeData() x, y = dnd_get_image(md) if x is not None: # We have an image, set cover event.accept() if y is None: # Local image self.undo_stack.push( Replace(x.toImage(), _('Drop image'), self)) else: d = DownloadDialog(x, y, self.gui) d.start_download() if d.err is None: with open(d.fpath, 'rb') as f: img = QImage() img.loadFromData(f.read()) if not img.isNull(): self.undo_stack.push( Replace(img, _('Drop image'), self)) event.accept() def dragMoveEvent(self, event): event.acceptProposedAction() # }}} def __init__(self, parent=None): QWidget.__init__(self, parent) self.setAcceptDrops(True) self.setMouseTracking(True) self.setFocusPolicy(Qt.FocusPolicy.ClickFocus) self.selection_state = SelectionState() self.undo_stack = u = QUndoStack() u.setUndoLimit(10) u.canUndoChanged.connect(self.emit_undo_redo_state) u.canRedoChanged.connect(self.emit_undo_redo_state) self.original_image_data = None self.is_valid = False self.original_image_format = None self.current_image = None self.current_scaled_pixmap = None self.last_canvas_size = None self.target = QRectF(0, 0, 0, 0) self.undo_action = a = self.undo_stack.createUndoAction( self, _('Undo') + ' ') a.setIcon(QIcon(I('edit-undo.png'))) self.redo_action = a = self.undo_stack.createRedoAction( self, _('Redo') + ' ') a.setIcon(QIcon(I('edit-redo.png'))) def load_image(self, data): self.is_valid = False try: fmt = identify(data)[0].encode('ascii') except Exception: fmt = b'' self.original_image_format = fmt.decode('ascii').lower() self.selection_state.reset() self.original_image_data = data self.current_image = i = self.original_image = (QImage.fromData( data, format=fmt) if fmt else QImage.fromData(data)) self.is_valid = not i.isNull() self.current_scaled_pixmap = None self.update() self.image_changed.emit(self.current_image) def set_image(self, qimage): self.selection_state.reset() self.current_scaled_pixmap = None self.current_image = qimage self.is_valid = not qimage.isNull() self.update() self.image_changed.emit(self.current_image) def get_image_data(self, quality=90): if not self.is_modified: return self.original_image_data fmt = self.original_image_format or 'JPEG' if fmt.lower() not in { x.data().decode('utf-8') for x in QImageWriter.supportedImageFormats() }: if fmt.lower() == 'gif': data = image_to_data(self.current_image, fmt='PNG', png_compression_level=0) from PIL import Image i = Image.open(BytesIO(data)) buf = BytesIO() i.save(buf, 'gif') return buf.getvalue() else: raise ValueError('Cannot save %s format images' % fmt) return pixmap_to_data(self.current_image, format=fmt, quality=90) def copy(self): if not self.is_valid: return clipboard = QApplication.clipboard() if not self.has_selection or self.selection_state.rect is None: clipboard.setImage(self.current_image) else: trim = Trim(self) clipboard.setImage(trim.after_image) trim.before_image = trim.after_image = None def paste(self): clipboard = QApplication.clipboard() md = clipboard.mimeData() if md.hasImage(): img = QImage(md.imageData()) if not img.isNull(): self.undo_stack.push(Replace(img, _('Paste image'), self)) else: error_dialog(self, _('No image'), _('No image available in the clipboard'), show=True) def break_cycles(self): self.undo_stack.clear() self.original_image_data = self.current_image = self.current_scaled_pixmap = None def emit_undo_redo_state(self): self.undo_redo_state_changed.emit(self.undo_action.isEnabled(), self.redo_action.isEnabled()) @imageop def trim_image(self): if self.selection_state.rect is None: error_dialog( self, _('No selection'), _('No active selection, first select a region in the image, by dragging with your mouse' ), show=True) return False self.undo_stack.push(Trim(self)) return True @imageop def autotrim_image(self): self.undo_stack.push(AutoTrim(self)) return True @imageop def rotate_image(self): self.undo_stack.push(Rotate(self)) return True @imageop def resize_image(self, width, height): self.undo_stack.push(Scale(width, height, self)) return True @imageop def sharpen_image(self, sigma=3.0): self.undo_stack.push(Sharpen(sigma, self)) return True @imageop def blur_image(self, sigma=3.0): self.undo_stack.push(Blur(sigma, self)) return True @imageop def despeckle_image(self): self.undo_stack.push(Despeckle(self)) return True @imageop def normalize_image(self): self.undo_stack.push(Normalize(self)) return True @imageop def oilify_image(self, radius=4.0): self.undo_stack.push(Oilify(radius, self)) return True # The selection rectangle {{{ @property def dc_size(self): sr = self.selection_state.rect dx = min(75, sr.width() / 4) dy = min(75, sr.height() / 4) return dx, dy def get_drag_corner(self, pos): dx, dy = self.dc_size sr = self.selection_state.rect x, y = pos.x(), pos.y() hedge = 'left' if x < sr.x() + dx else 'right' if x > sr.right( ) - dx else None vedge = 'top' if y < sr.y() + dy else 'bottom' if y > sr.bottom( ) - dy else None return (hedge, vedge) if hedge or vedge else None def get_drag_rect(self): sr = self.selection_state.rect dc = self.selection_state.drag_corner if None in (sr, dc): return dx, dy = self.dc_size if None in dc: # An edge if dc[0] is None: top = sr.top() if dc[1] == 'top' else sr.bottom() - dy ans = QRectF(sr.left() + dx, top, sr.width() - 2 * dx, dy) else: left = sr.left() if dc[0] == 'left' else sr.right() - dx ans = QRectF(left, sr.top() + dy, dx, sr.height() - 2 * dy) else: # A corner left = sr.left() if dc[0] == 'left' else sr.right() - dx top = sr.top() if dc[1] == 'top' else sr.bottom() - dy ans = QRectF(left, top, dx, dy) return ans def get_cursor(self): dc = self.selection_state.drag_corner if dc is None: ans = Qt.CursorShape.OpenHandCursor if self.selection_state.last_drag_pos is None else Qt.CursorShape.ClosedHandCursor elif None in dc: ans = Qt.CursorShape.SizeVerCursor if dc[ 0] is None else Qt.CursorShape.SizeHorCursor else: ans = Qt.CursorShape.SizeBDiagCursor if dc in { ('left', 'bottom'), ('right', 'top') } else Qt.CursorShape.SizeFDiagCursor return ans def update(self): super().update() self.selection_area_changed.emit(self.selection_state.rect) def move_edge(self, edge, dp): sr = self.selection_state.rect horiz = edge in {'left', 'right'} func = getattr(sr, 'set' + capitalize(edge)) delta = getattr(dp, 'x' if horiz else 'y')() buf = 50 if horiz: minv = self.target.left() if edge == 'left' else sr.left() + buf maxv = sr.right() - buf if edge == 'left' else self.target.right() else: minv = self.target.top() if edge == 'top' else sr.top() + buf maxv = sr.bottom() - buf if edge == 'top' else self.target.bottom() func(max(minv, min(maxv, delta + getattr(sr, edge)()))) def move_selection_rect(self, x, y): sr = self.selection_state.rect half_width = sr.width() / 2.0 half_height = sr.height() / 2.0 c = sr.center() nx = c.x() + x ny = c.y() + y minx = self.target.left() + half_width maxx = self.target.right() - half_width miny, maxy = self.target.top() + half_height, self.target.bottom( ) - half_height nx = max(minx, min(maxx, nx)) ny = max(miny, min(maxy, ny)) sr.moveCenter(QPointF(nx, ny)) def move_selection(self, dp): dm = self.selection_state.dragging if dm is None: self.move_selection_rect(dp.x(), dp.y()) else: for edge in dm: if edge is not None: self.move_edge(edge, dp) def mousePressEvent(self, ev): if ev.button() == Qt.MouseButton.LeftButton and self.target.contains( ev.pos()): pos = ev.pos() self.selection_state.last_press_point = pos if self.selection_state.current_mode is None: self.selection_state.current_mode = 'select' elif self.selection_state.current_mode == 'selected': if self.selection_state.rect is not None and self.selection_state.rect.contains( pos): self.selection_state.drag_corner = self.selection_state.dragging = self.get_drag_corner( pos) self.selection_state.last_drag_pos = pos self.setCursor(self.get_cursor()) else: self.selection_state.current_mode = 'select' self.selection_state.rect = None self.selection_state_changed.emit(False) def mouseMoveEvent(self, ev): changed = False if self.selection_state.in_selection: changed = True self.selection_state.in_selection = False self.selection_state.drag_corner = None pos = ev.pos() cursor = Qt.CursorShape.ArrowCursor try: if not self.target.contains(pos): return if ev.buttons() & Qt.MouseButton.LeftButton: if self.selection_state.last_press_point is not None and self.selection_state.current_mode is not None: if self.selection_state.current_mode == 'select': self.selection_state.rect = QRectF( self.selection_state.last_press_point, pos).normalized() changed = True elif self.selection_state.last_drag_pos is not None: self.selection_state.in_selection = True self.selection_state.drag_corner = self.selection_state.dragging dp = pos - self.selection_state.last_drag_pos self.selection_state.last_drag_pos = pos self.move_selection(dp) cursor = self.get_cursor() changed = True else: if self.selection_state.rect is None or not self.selection_state.rect.contains( pos): return if self.selection_state.current_mode == 'selected': if self.selection_state.rect is not None and self.selection_state.rect.contains( pos): self.selection_state.drag_corner = self.get_drag_corner( pos) self.selection_state.in_selection = True cursor = self.get_cursor() changed = True finally: if changed: self.update() self.setCursor(cursor) def mouseReleaseEvent(self, ev): if ev.button() == Qt.MouseButton.LeftButton: self.selection_state.dragging = self.selection_state.last_drag_pos = None if self.selection_state.current_mode == 'select': r = self.selection_state.rect if r is None or max(r.width(), r.height()) < 3: self.selection_state.reset() else: self.selection_state.current_mode = 'selected' self.selection_state_changed.emit(self.has_selection) elif self.selection_state.current_mode == 'selected' and self.selection_state.rect is not None and self.selection_state.rect.contains( ev.pos()): self.setCursor(self.get_cursor()) self.update() def keyPressEvent(self, ev): k = ev.key() if k in ( Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down ) and self.selection_state.rect is not None and self.has_selection: ev.accept() delta = 10 if ev.modifiers( ) & Qt.KeyboardModifier.ShiftModifier else 1 x = y = 0 if k in (Qt.Key.Key_Left, Qt.Key.Key_Right): x = delta * (-1 if k == Qt.Key.Key_Left else 1) else: y = delta * (-1 if k == Qt.Key.Key_Up else 1) self.move_selection_rect(x, y) self.update() else: return QWidget.keyPressEvent(self, ev) # }}} # Painting {{{ @painter def draw_background(self, painter): painter.fillRect(self.rect(), self.BACKGROUND) @painter def draw_image_error(self, painter): font = painter.font() font.setPointSize(3 * font.pointSize()) font.setBold(True) painter.setFont(font) painter.setPen(QColor(Qt.GlobalColor.black)) painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, _('Not a valid image')) def load_pixmap(self): canvas_size = self.rect().width(), self.rect().height() if self.last_canvas_size != canvas_size: if self.last_canvas_size is not None and self.selection_state.rect is not None: self.selection_state.reset() # TODO: Migrate the selection rect self.last_canvas_size = canvas_size self.current_scaled_pixmap = None if self.current_scaled_pixmap is None: pwidth, pheight = self.last_canvas_size i = self.current_image width, height = i.width(), i.height() scaled, width, height = fit_image(width, height, pwidth, pheight) try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() if scaled: i = self.current_image.scaled( int(dpr * width), int(dpr * height), transformMode=Qt.TransformationMode.SmoothTransformation) self.current_scaled_pixmap = QPixmap.fromImage(i) self.current_scaled_pixmap.setDevicePixelRatio(dpr) @painter def draw_pixmap(self, painter): p = self.current_scaled_pixmap try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() width, height = int(p.width() / dpr), int(p.height() / dpr) pwidth, pheight = self.last_canvas_size x = int(abs(pwidth - width) / 2.) y = int(abs(pheight - height) / 2.) self.target = QRectF(x, y, width, height) painter.drawPixmap(self.target, p, QRectF(p.rect())) @painter def draw_selection_rect(self, painter): cr, sr = self.target, self.selection_state.rect painter.setPen(self.SELECT_PEN) painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) if self.selection_state.current_mode == 'selected': # Shade out areas outside the selection rect for r in ( QRectF(cr.topLeft(), QPointF(sr.left(), cr.bottom())), # left QRectF(QPointF(sr.left(), cr.top()), sr.topRight()), # top QRectF(QPointF(sr.right(), cr.top()), cr.bottomRight()), # right QRectF(sr.bottomLeft(), QPointF(sr.right(), cr.bottom())), # bottom ): painter.fillRect(r, self.SHADE_COLOR) dr = self.get_drag_rect() if self.selection_state.in_selection and dr is not None: # Draw the resize rectangle painter.save() painter.setCompositionMode( QPainter.CompositionMode.RasterOp_SourceAndNotDestination) painter.setClipRect(sr.adjusted(1, 1, -1, -1)) painter.drawRect(dr) painter.restore() # Draw the selection rectangle painter.setCompositionMode( QPainter.CompositionMode.RasterOp_SourceAndNotDestination) painter.drawRect(sr) def paintEvent(self, event): QWidget.paintEvent(self, event) p = QPainter(self) p.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform) try: self.draw_background(p) if self.original_image_data is None: return if not self.is_valid: return self.draw_image_error(p) self.load_pixmap() self.draw_pixmap(p) if self.selection_state.rect is not None: self.draw_selection_rect(p) finally: p.end()
def boundingRect(self): return QRectF(0, 0, self.width, self.height)