Loading ballontranslator/ui/canvas.py +11 −10 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ from .misc import ndarray2pixmap, ProjImgTrans from .textitem import TextBlkItem, TextBlock from .texteditshapecontrol import TextBlkShapeControl from .stylewidgets import FadeLabel from .image_edit import StrokeItem, StrokeItem, ImageEditMode from .image_edit import StrokeItem, StrokeItem, ImageEditMode, DrawingLayer CANVAS_SCALE_MAX = 3.0 CANVAS_SCALE_MIN = 0.1 Loading Loading @@ -87,6 +87,7 @@ class Canvas(QGraphicsScene): imgtrans_proj: ProjImgTrans = None painting_pen = QPen() erasing_pen = QPen() image_edit_mode = ImageEditMode.NONE alt_pressed = False scale_tool_mode = False Loading @@ -111,7 +112,7 @@ class Canvas(QGraphicsScene): self.gv.scale_up_signal.connect(self.scaleUp) self.gv.view_resized.connect(self.onViewResized) self.gv.hide_canvas.connect(self.on_hide_canvas) self.gv.setRenderHint(QPainter.Antialiasing) self.gv.setRenderHint(QPainter.RenderHint.Antialiasing) self.ctrl_relesed = self.gv.ctrl_released self.vscroll_bar = self.gv.verticalScrollBar() self.hscroll_bar = self.gv.horizontalScrollBar() Loading Loading @@ -139,9 +140,8 @@ class Canvas(QGraphicsScene): self.inpaintLayer = QGraphicsPixmapItem() self.inpaintLayer.setTransformationMode(Qt.TransformationMode.SmoothTransformation) self.maskLayer = QGraphicsPixmapItem() self.maskLayer.setTransformationMode(Qt.TransformationMode.SmoothTransformation) self.drawingLayer = QGraphicsPixmapItem() self.drawingLayer.setTransformationMode(Qt.TransformationMode.SmoothTransformation) self.drawingLayer = DrawingLayer() self.drawingLayer.setTransformationMode(Qt.TransformationMode.FastTransformation) self.textLayer = QGraphicsPixmapItem() self.textLayer.setTransformationMode(Qt.TransformationMode.SmoothTransformation) Loading Loading @@ -259,9 +259,9 @@ class Canvas(QGraphicsScene): self.alt_pressed = False return super().keyReleaseEvent(event) def addStrokeItem(self, item: StrokeItem): def addStrokeItem(self, item: StrokeItem, pen: QPen): self.addItem(item) item.setPen(self.painting_pen) item.setPen(pen) item.setParentItem(self.drawingLayer) def startCreateTextblock(self, pos: QPointF, hide_control: bool = False): Loading Loading @@ -329,13 +329,14 @@ class Canvas(QGraphicsScene): self.begin_scale_tool.emit(event.scenePos()) elif self.painting and self.stroke_path_item is None: self.stroke_path_item = StrokeItem(self.imgLayer.mapFromScene(event.scenePos())) self.addStrokeItem(self.stroke_path_item) self.addStrokeItem(self.stroke_path_item, self.painting_pen) elif btn == Qt.MouseButton.RightButton: # user is drawing using eraser if self.painting and self.stroke_path_item is None: self.stroke_path_item = StrokeItem(self.imgLayer.mapFromScene(event.scenePos())) self.addStrokeItem(self.stroke_path_item) erasing = self.image_edit_mode == ImageEditMode.PenTool self.stroke_path_item = StrokeItem(self.imgLayer.mapFromScene(event.scenePos()), erasing) self.addStrokeItem(self.stroke_path_item, self.erasing_pen) else: # rubber band selection self.rubber_band_origin = event.scenePos() self.rubber_band.setGeometry(QRectF(self.rubber_band_origin, self.rubber_band_origin).normalized()) Loading ballontranslator/ui/drawingpanel.py +33 −12 Original line number Diff line number Diff line from qtpy.QtCore import Signal, Qt, QPointF, QSize, QLineF, QRect, QRectF from qtpy.QtWidgets import QPushButton, QComboBox, QSizePolicy, QBoxLayout, QCheckBox, QHBoxLayout, QGraphicsView, QStackedWidget, QVBoxLayout, QLabel, QGraphicsEllipseItem from qtpy.QtGui import QPen, QColor, QCursor, QPainter, QPixmap, QBrush, QFontMetrics from qtpy.QtCore import Signal, Qt, QPointF, QSize, QLineF, QDateTime, QRectF from qtpy.QtWidgets import QPushButton, QComboBox, QSizePolicy, QBoxLayout, QCheckBox, QHBoxLayout, QGraphicsView, QStackedWidget, QVBoxLayout, QLabel, QGraphicsPixmapItem, QGraphicsEllipseItem from qtpy.QtGui import QPen, QColor, QCursor, QPainter, QPixmap, QBrush, QFontMetrics, QImage try: from qtpy.QtWidgets import QUndoCommand Loading @@ -16,7 +16,7 @@ from utils.textblock_mask import canny_flood, connected_canny_flood from utils.logger import logger from .dl_manager import DLManager from .image_edit import ImageEditMode, StrokeItem, PixmapItem from .image_edit import ImageEditMode, StrokeItem, PixmapItem, DrawingLayer from .configpanel import InpaintConfigPanel from .stylewidgets import Widget, SeparatorWidget, ColorPicker, PaintQSlider from .canvas import Canvas Loading Loading @@ -213,7 +213,6 @@ class RectPanel(Widget): return self.autoChecker.isChecked() class DrawingPanel(Widget): scale_tool_pos: QPointF = None Loading Loading @@ -273,7 +272,7 @@ class DrawingPanel(Widget): self.canvas.painting_pen = self.pentool_pen = \ QPen(Qt.GlobalColor.black, 1, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin) self.canvas.erasing_pen = self.erasing_pen = QPen(Qt.GlobalColor.black, 1, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin) self.inpaint_pen = QPen(INPAINT_BRUSH_COLOR, 1, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin) self.setPenToolWidth(10) Loading Loading @@ -312,6 +311,7 @@ class DrawingPanel(Widget): def setPenToolWidth(self, width): self.pentool_pen.setWidthF(width) self.erasing_pen.setWidthF(width) if self.isVisible(): self.setPenCursor() Loading @@ -335,6 +335,7 @@ class DrawingPanel(Widget): self.currentTool = self.inpaintTool self.canvas.image_edit_mode = ImageEditMode.InpaintTool self.canvas.painting_pen = self.inpaint_pen self.canvas.erasing_pen = self.inpaint_pen self.toolConfigStackwidget.setCurrentWidget(self.inpaintConfigPanel) if self.isVisible(): self.canvas.gv.setDragMode(QGraphicsView.DragMode.NoDrag) Loading @@ -345,6 +346,7 @@ class DrawingPanel(Widget): self.currentTool.setChecked(False) self.currentTool = self.penTool self.canvas.painting_pen = self.pentool_pen self.canvas.erasing_pen = self.erasing_pen self.canvas.image_edit_mode = ImageEditMode.PenTool self.toolConfigStackwidget.setCurrentWidget(self.penConfigPanel) if self.isVisible(): Loading Loading @@ -484,7 +486,7 @@ class DrawingPanel(Widget): self.canvas.removeItem(stroke_item) return if self.currentTool == self.penTool: self.canvas.undoStack.push(StrokeItemUndoCommand(self.canvas, stroke_item)) self.canvas.undoStack.push(StrokeItemUndoCommand(self.canvas.drawingLayer, stroke_item)) elif self.currentTool == self.inpaintTool: self.mergeInpaintStroke(stroke_item) if self.canvas.gv.ctrl_pressed: Loading Loading @@ -570,6 +572,9 @@ class DrawingPanel(Widget): erased_img = inpaint_mask * inpainted + (1 - inpaint_mask) * origin self.canvas.undoStack.push(InpaintUndoCommand(self.canvas, erased_img, mask, inpaint_rect)) elif self.currentTool == self.penTool: self.canvas.undoStack.push(StrokeItemUndoCommand(self.canvas.drawingLayer, self.canvas.stroke_path_item, True)) def on_inpaint_failed(self): if self.currentTool == self.inpaintTool and self.inpaint_stroke is not None: self.clearInpaintItems() Loading Loading @@ -732,15 +737,31 @@ class DrawingPanel(Widget): self.canvas.image_edit_mode = ImageEditMode.InpaintTool class StrokeItemUndoCommand(QUndoCommand): def __init__(self, canvas: Canvas, stroke_item: StrokeItem): def __init__(self, target_layer: DrawingLayer, stroke_item: StrokeItem, erasing=False): super().__init__() self.stroke_pixmap = stroke_item.convertToPixmapItem() self.qimg = stroke_item.convertToQImg().convertToFormat(QImage.Format.Format_ARGB32_Premultiplied) pos = stroke_item.subBlockPos() self.x = pos.x() self.y = pos.y() self.target_layer = target_layer self.key = str(QDateTime.currentMSecsSinceEpoch()) target_layer.scene().removeItem(stroke_item) if erasing: self.compose_mode = QPainter.CompositionMode.CompositionMode_DestinationOut else: self.compose_mode = QPainter.CompositionMode.CompositionMode_SourceOver def undo(self): self.stroke_pixmap.hide() if self.qimg is not None: self.target_layer.removeQImage(self.key) self.target_layer.update() # self.stroke_pixmap.hide() def redo(self): self.stroke_pixmap.show() if self.qimg is not None: self.target_layer.addQImage(self.x, self.y, self.qimg, self.compose_mode, self.key) self.target_layer.scene().update() class InpaintUndoCommand(QUndoCommand): Loading ballontranslator/ui/image_edit.py +87 −37 Original line number Diff line number Diff line import numpy as np import cv2 from qtpy.QtCore import Signal, Qt, QPointF, QSize, QPoint from qtpy.QtWidgets import QStyleOptionGraphicsItem, QGraphicsPixmapItem, QWidget, QGraphicsPathItem, QGraphicsScene from qtpy.QtGui import QPen, QColor, QPainterPath, QCursor, QPainter, QPixmap from qtpy.QtCore import QRectF, Qt, QPointF, QSize, QPoint, QDateTime from qtpy.QtWidgets import QStyleOptionGraphicsItem, QGraphicsPixmapItem, QWidget, QGraphicsPathItem, QGraphicsItem from qtpy.QtGui import QPen, QColor, QPainterPath, QCursor, QPainter, QPixmap, QImage, QBrush try: from qtpy.QtWidgets import QUndoCommand Loading @@ -22,11 +21,38 @@ class ImageEditMode: PenTool = 2 RectTool = 3 class StrokeImgItem(QGraphicsItem): def __init__(self, pen: QPen, point: QPointF, size: QSize, format: QImage.Format = QImage.Format.Format_ARGB32, ): self._img = QImage(size, format) self._img.fill(Qt.GlobalColor.transparent) self.pen = pen self.painter = QPainter() self.painter.setPen(pen) self.setBoundingRegionGranularity(0) self.cur_point = point self.drawLine(point, point) self._br = QRectF(0, 0, size.width(), size.height()) def boundingRect(self) -> QRectF: return self._br def drawLine(self, new_pnt: QPointF): self.painter.begin(self._img) self.painter.drawLine(self.cur_point, new_pnt) self.painter.end() self.cur_point = new_pnt def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget) -> None: painter.drawImage(0, 0, self._img) class StrokeItem(QGraphicsPathItem): def __init__(self, origin_point: QPointF): def __init__(self, origin_point: QPointF, erasing: bool = False): super().__init__() # self.stroke = QPainterPath(QPointF(0, 0)) self.stroke = QPainterPath(QPointF(origin_point)) self.erasing = erasing self.last_point = origin_point self.setPath(self.stroke) self.setBoundingRegionGranularity(0) Loading @@ -48,12 +74,12 @@ class StrokeItem(QGraphicsPathItem): def convertToPixmapItem(self, convert_mask=False, remove_stroke=True, target_layer: QGraphicsPixmapItem = None) -> QGraphicsPixmapItem: if target_layer is None: target_layer = self.parentItem() # layer_size = target_layer.pixmap().size() img_array = self.getSubimg(convert_mask) if img_array is None: pixmap = self.getSubimg(convert_mask, return_pixmap=True) if pixmap is None: self.scene().removeItem(self) return None, None, None pixmap = ndarray2pixmap(img_array) # pixmap = ndarray2pixmap(img_array) pixmap_item = QGraphicsPixmapItem(pixmap) pixmap_item.setParentItem(target_layer) Loading @@ -66,6 +92,13 @@ class StrokeItem(QGraphicsPathItem): self.setZValue(3) return pixmap_item def convertToQImg(self, convert_mask=False) -> QImage: img_array = self.getSubimg(convert_mask) if img_array is None: return None qimg = ndarray2pixmap(img_array, return_qimg=True) return qimg def originOffset(self) -> QPointF: thickness = self.pen().widthF() / 2 return QPointF(thickness, thickness) - self.stroke.boundingRect().topLeft() - self.clip_offset Loading @@ -76,7 +109,7 @@ class StrokeItem(QGraphicsPathItem): pos.setY(int(round(max(0, pos.y())))) return pos.toPoint() def getSubimg(self, convert_mask=False) -> np.ndarray: def getSubimg(self, convert_mask=False, return_pixmap=False) -> np.ndarray: if self.isEmpty(): return None Loading Loading @@ -109,23 +142,28 @@ class StrokeItem(QGraphicsPathItem): if xyxy_clip[0] >= xyxy_clip[2] or xyxy_clip[1] >= xyxy_clip[3]: return None stroke_clip = xyxy_clip - xyxy stroke_clip[2] += stroke_size.width() stroke_clip[3] += stroke_size.height() pixmap = QPixmap(stroke_size) pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(pixmap) painter.translate(self.originOffset()) painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.translate(self.originOffset()) painter.setPen(self.pen()) painter.setBrush(self.brush()) painter.drawPath(self.stroke) painter.end() stroke_clip = xyxy_clip - xyxy stroke_clip[2] += stroke_size.width() stroke_clip[3] += stroke_size.height() self.clip_offset = QPointF(stroke_clip[0], stroke_clip[1]) if return_pixmap: return pixmap imgarray = pixmap2ndarray(pixmap, keep_alpha=True) imgarray = imgarray[stroke_clip[1]: stroke_clip[3], stroke_clip[0]: stroke_clip[2]] # print(imgarray.shape, stroke_clip) self.clip_offset = QPointF(stroke_clip[0], stroke_clip[1]) if convert_mask: mask = imgarray[..., -1] mask[mask > 0] = 255 Loading @@ -135,25 +173,6 @@ class StrokeItem(QGraphicsPathItem): return imgarray class PenStrokeCommand(QUndoCommand): def __init__(self, canvas: QGraphicsScene, stroke_item: StrokeItem): super().__init__() self.stroke_item = stroke_item self.canvas = canvas def redo(self) -> None: self.canvas.addItem(self.stroke_item) self.stroke_item.setParentItem(self.canvas.imgLayer) def undo(self): self.canvas.removeItem(self.stroke_item) def mergeWith(self, command: QUndoCommand) -> bool: if self.stroke_item == command.stroke_item: return True return False class PenCursor(QCursor): def __init__(self, *args, **kwargs): super().__init__() Loading @@ -169,7 +188,7 @@ class PenCursor(QCursor): cur_pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(cur_pixmap) painter.setPen(pen) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.drawEllipse(self.thickness, self.thickness, size-2*self.thickness, size-2*self.thickness) painter.end() Loading @@ -186,3 +205,34 @@ class PixmapItem(QGraphicsPixmapItem): painter.setPen(pen) return super().paint(painter, option, widget) class DrawingLayer(QGraphicsPixmapItem): def __init__(self): super().__init__() self.qimg_dict = {} self.drawing_items_info = {} def addQImage(self, x: int, y: int, qimg: QImage, compose_mode, key: str): self.qimg_dict[key] = qimg self.drawing_items_info[key] = {'pos': [x, y], 'compose': compose_mode} self.update() def removeQImage(self, key: str): if key in self.qimg_dict: self.qimg_dict.pop(key) # self.drawing_items_pos.pop(key) def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget): pixmap = self.pixmap() p = QPainter() p.begin(pixmap) for key in self.qimg_dict: item = self.qimg_dict[key] info = self.drawing_items_info[key] if isinstance(item, QImage): p.setCompositionMode(info['compose']) p.drawImage(info['pos'][0], info['pos'][1], item) p.end() painter.drawPixmap(self.offset(), pixmap) ballontranslator/ui/scene_textlayout.py +1 −1 Original line number Diff line number Diff line Loading @@ -396,7 +396,7 @@ class VerticalTextDocumentLayout(SceneTextLayout): num_rspaces, num_lspaces, char_yoffset_lst, line_pos = self.line_spaces_lst[blk_no][line.lineNumber()] y = char_yoffset_lst[cpos - line_pos] painter.setCompositionMode(QPainter.RasterOp_NotDestination) painter.setCompositionMode(QPainter.CompositionMode.RasterOp_NotDestination) painter.fillRect(QRectF(x, y, fm.height(), 2), painter.pen().brush()) if self.has_selection == has_selection: self.update.emit(QRectF(x, y, fm.height(), 2)) Loading ballontranslator/ui/stylewidgets.py +1 −1 Original line number Diff line number Diff line Loading @@ -227,7 +227,7 @@ class PaintQSlider(QSlider): self.initStyleOption(option) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 中间圆圈的位置 rect = self.style().subControlRect( Loading Loading
ballontranslator/ui/canvas.py +11 −10 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ from .misc import ndarray2pixmap, ProjImgTrans from .textitem import TextBlkItem, TextBlock from .texteditshapecontrol import TextBlkShapeControl from .stylewidgets import FadeLabel from .image_edit import StrokeItem, StrokeItem, ImageEditMode from .image_edit import StrokeItem, StrokeItem, ImageEditMode, DrawingLayer CANVAS_SCALE_MAX = 3.0 CANVAS_SCALE_MIN = 0.1 Loading Loading @@ -87,6 +87,7 @@ class Canvas(QGraphicsScene): imgtrans_proj: ProjImgTrans = None painting_pen = QPen() erasing_pen = QPen() image_edit_mode = ImageEditMode.NONE alt_pressed = False scale_tool_mode = False Loading @@ -111,7 +112,7 @@ class Canvas(QGraphicsScene): self.gv.scale_up_signal.connect(self.scaleUp) self.gv.view_resized.connect(self.onViewResized) self.gv.hide_canvas.connect(self.on_hide_canvas) self.gv.setRenderHint(QPainter.Antialiasing) self.gv.setRenderHint(QPainter.RenderHint.Antialiasing) self.ctrl_relesed = self.gv.ctrl_released self.vscroll_bar = self.gv.verticalScrollBar() self.hscroll_bar = self.gv.horizontalScrollBar() Loading Loading @@ -139,9 +140,8 @@ class Canvas(QGraphicsScene): self.inpaintLayer = QGraphicsPixmapItem() self.inpaintLayer.setTransformationMode(Qt.TransformationMode.SmoothTransformation) self.maskLayer = QGraphicsPixmapItem() self.maskLayer.setTransformationMode(Qt.TransformationMode.SmoothTransformation) self.drawingLayer = QGraphicsPixmapItem() self.drawingLayer.setTransformationMode(Qt.TransformationMode.SmoothTransformation) self.drawingLayer = DrawingLayer() self.drawingLayer.setTransformationMode(Qt.TransformationMode.FastTransformation) self.textLayer = QGraphicsPixmapItem() self.textLayer.setTransformationMode(Qt.TransformationMode.SmoothTransformation) Loading Loading @@ -259,9 +259,9 @@ class Canvas(QGraphicsScene): self.alt_pressed = False return super().keyReleaseEvent(event) def addStrokeItem(self, item: StrokeItem): def addStrokeItem(self, item: StrokeItem, pen: QPen): self.addItem(item) item.setPen(self.painting_pen) item.setPen(pen) item.setParentItem(self.drawingLayer) def startCreateTextblock(self, pos: QPointF, hide_control: bool = False): Loading Loading @@ -329,13 +329,14 @@ class Canvas(QGraphicsScene): self.begin_scale_tool.emit(event.scenePos()) elif self.painting and self.stroke_path_item is None: self.stroke_path_item = StrokeItem(self.imgLayer.mapFromScene(event.scenePos())) self.addStrokeItem(self.stroke_path_item) self.addStrokeItem(self.stroke_path_item, self.painting_pen) elif btn == Qt.MouseButton.RightButton: # user is drawing using eraser if self.painting and self.stroke_path_item is None: self.stroke_path_item = StrokeItem(self.imgLayer.mapFromScene(event.scenePos())) self.addStrokeItem(self.stroke_path_item) erasing = self.image_edit_mode == ImageEditMode.PenTool self.stroke_path_item = StrokeItem(self.imgLayer.mapFromScene(event.scenePos()), erasing) self.addStrokeItem(self.stroke_path_item, self.erasing_pen) else: # rubber band selection self.rubber_band_origin = event.scenePos() self.rubber_band.setGeometry(QRectF(self.rubber_band_origin, self.rubber_band_origin).normalized()) Loading
ballontranslator/ui/drawingpanel.py +33 −12 Original line number Diff line number Diff line from qtpy.QtCore import Signal, Qt, QPointF, QSize, QLineF, QRect, QRectF from qtpy.QtWidgets import QPushButton, QComboBox, QSizePolicy, QBoxLayout, QCheckBox, QHBoxLayout, QGraphicsView, QStackedWidget, QVBoxLayout, QLabel, QGraphicsEllipseItem from qtpy.QtGui import QPen, QColor, QCursor, QPainter, QPixmap, QBrush, QFontMetrics from qtpy.QtCore import Signal, Qt, QPointF, QSize, QLineF, QDateTime, QRectF from qtpy.QtWidgets import QPushButton, QComboBox, QSizePolicy, QBoxLayout, QCheckBox, QHBoxLayout, QGraphicsView, QStackedWidget, QVBoxLayout, QLabel, QGraphicsPixmapItem, QGraphicsEllipseItem from qtpy.QtGui import QPen, QColor, QCursor, QPainter, QPixmap, QBrush, QFontMetrics, QImage try: from qtpy.QtWidgets import QUndoCommand Loading @@ -16,7 +16,7 @@ from utils.textblock_mask import canny_flood, connected_canny_flood from utils.logger import logger from .dl_manager import DLManager from .image_edit import ImageEditMode, StrokeItem, PixmapItem from .image_edit import ImageEditMode, StrokeItem, PixmapItem, DrawingLayer from .configpanel import InpaintConfigPanel from .stylewidgets import Widget, SeparatorWidget, ColorPicker, PaintQSlider from .canvas import Canvas Loading Loading @@ -213,7 +213,6 @@ class RectPanel(Widget): return self.autoChecker.isChecked() class DrawingPanel(Widget): scale_tool_pos: QPointF = None Loading Loading @@ -273,7 +272,7 @@ class DrawingPanel(Widget): self.canvas.painting_pen = self.pentool_pen = \ QPen(Qt.GlobalColor.black, 1, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin) self.canvas.erasing_pen = self.erasing_pen = QPen(Qt.GlobalColor.black, 1, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin) self.inpaint_pen = QPen(INPAINT_BRUSH_COLOR, 1, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin) self.setPenToolWidth(10) Loading Loading @@ -312,6 +311,7 @@ class DrawingPanel(Widget): def setPenToolWidth(self, width): self.pentool_pen.setWidthF(width) self.erasing_pen.setWidthF(width) if self.isVisible(): self.setPenCursor() Loading @@ -335,6 +335,7 @@ class DrawingPanel(Widget): self.currentTool = self.inpaintTool self.canvas.image_edit_mode = ImageEditMode.InpaintTool self.canvas.painting_pen = self.inpaint_pen self.canvas.erasing_pen = self.inpaint_pen self.toolConfigStackwidget.setCurrentWidget(self.inpaintConfigPanel) if self.isVisible(): self.canvas.gv.setDragMode(QGraphicsView.DragMode.NoDrag) Loading @@ -345,6 +346,7 @@ class DrawingPanel(Widget): self.currentTool.setChecked(False) self.currentTool = self.penTool self.canvas.painting_pen = self.pentool_pen self.canvas.erasing_pen = self.erasing_pen self.canvas.image_edit_mode = ImageEditMode.PenTool self.toolConfigStackwidget.setCurrentWidget(self.penConfigPanel) if self.isVisible(): Loading Loading @@ -484,7 +486,7 @@ class DrawingPanel(Widget): self.canvas.removeItem(stroke_item) return if self.currentTool == self.penTool: self.canvas.undoStack.push(StrokeItemUndoCommand(self.canvas, stroke_item)) self.canvas.undoStack.push(StrokeItemUndoCommand(self.canvas.drawingLayer, stroke_item)) elif self.currentTool == self.inpaintTool: self.mergeInpaintStroke(stroke_item) if self.canvas.gv.ctrl_pressed: Loading Loading @@ -570,6 +572,9 @@ class DrawingPanel(Widget): erased_img = inpaint_mask * inpainted + (1 - inpaint_mask) * origin self.canvas.undoStack.push(InpaintUndoCommand(self.canvas, erased_img, mask, inpaint_rect)) elif self.currentTool == self.penTool: self.canvas.undoStack.push(StrokeItemUndoCommand(self.canvas.drawingLayer, self.canvas.stroke_path_item, True)) def on_inpaint_failed(self): if self.currentTool == self.inpaintTool and self.inpaint_stroke is not None: self.clearInpaintItems() Loading Loading @@ -732,15 +737,31 @@ class DrawingPanel(Widget): self.canvas.image_edit_mode = ImageEditMode.InpaintTool class StrokeItemUndoCommand(QUndoCommand): def __init__(self, canvas: Canvas, stroke_item: StrokeItem): def __init__(self, target_layer: DrawingLayer, stroke_item: StrokeItem, erasing=False): super().__init__() self.stroke_pixmap = stroke_item.convertToPixmapItem() self.qimg = stroke_item.convertToQImg().convertToFormat(QImage.Format.Format_ARGB32_Premultiplied) pos = stroke_item.subBlockPos() self.x = pos.x() self.y = pos.y() self.target_layer = target_layer self.key = str(QDateTime.currentMSecsSinceEpoch()) target_layer.scene().removeItem(stroke_item) if erasing: self.compose_mode = QPainter.CompositionMode.CompositionMode_DestinationOut else: self.compose_mode = QPainter.CompositionMode.CompositionMode_SourceOver def undo(self): self.stroke_pixmap.hide() if self.qimg is not None: self.target_layer.removeQImage(self.key) self.target_layer.update() # self.stroke_pixmap.hide() def redo(self): self.stroke_pixmap.show() if self.qimg is not None: self.target_layer.addQImage(self.x, self.y, self.qimg, self.compose_mode, self.key) self.target_layer.scene().update() class InpaintUndoCommand(QUndoCommand): Loading
ballontranslator/ui/image_edit.py +87 −37 Original line number Diff line number Diff line import numpy as np import cv2 from qtpy.QtCore import Signal, Qt, QPointF, QSize, QPoint from qtpy.QtWidgets import QStyleOptionGraphicsItem, QGraphicsPixmapItem, QWidget, QGraphicsPathItem, QGraphicsScene from qtpy.QtGui import QPen, QColor, QPainterPath, QCursor, QPainter, QPixmap from qtpy.QtCore import QRectF, Qt, QPointF, QSize, QPoint, QDateTime from qtpy.QtWidgets import QStyleOptionGraphicsItem, QGraphicsPixmapItem, QWidget, QGraphicsPathItem, QGraphicsItem from qtpy.QtGui import QPen, QColor, QPainterPath, QCursor, QPainter, QPixmap, QImage, QBrush try: from qtpy.QtWidgets import QUndoCommand Loading @@ -22,11 +21,38 @@ class ImageEditMode: PenTool = 2 RectTool = 3 class StrokeImgItem(QGraphicsItem): def __init__(self, pen: QPen, point: QPointF, size: QSize, format: QImage.Format = QImage.Format.Format_ARGB32, ): self._img = QImage(size, format) self._img.fill(Qt.GlobalColor.transparent) self.pen = pen self.painter = QPainter() self.painter.setPen(pen) self.setBoundingRegionGranularity(0) self.cur_point = point self.drawLine(point, point) self._br = QRectF(0, 0, size.width(), size.height()) def boundingRect(self) -> QRectF: return self._br def drawLine(self, new_pnt: QPointF): self.painter.begin(self._img) self.painter.drawLine(self.cur_point, new_pnt) self.painter.end() self.cur_point = new_pnt def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget) -> None: painter.drawImage(0, 0, self._img) class StrokeItem(QGraphicsPathItem): def __init__(self, origin_point: QPointF): def __init__(self, origin_point: QPointF, erasing: bool = False): super().__init__() # self.stroke = QPainterPath(QPointF(0, 0)) self.stroke = QPainterPath(QPointF(origin_point)) self.erasing = erasing self.last_point = origin_point self.setPath(self.stroke) self.setBoundingRegionGranularity(0) Loading @@ -48,12 +74,12 @@ class StrokeItem(QGraphicsPathItem): def convertToPixmapItem(self, convert_mask=False, remove_stroke=True, target_layer: QGraphicsPixmapItem = None) -> QGraphicsPixmapItem: if target_layer is None: target_layer = self.parentItem() # layer_size = target_layer.pixmap().size() img_array = self.getSubimg(convert_mask) if img_array is None: pixmap = self.getSubimg(convert_mask, return_pixmap=True) if pixmap is None: self.scene().removeItem(self) return None, None, None pixmap = ndarray2pixmap(img_array) # pixmap = ndarray2pixmap(img_array) pixmap_item = QGraphicsPixmapItem(pixmap) pixmap_item.setParentItem(target_layer) Loading @@ -66,6 +92,13 @@ class StrokeItem(QGraphicsPathItem): self.setZValue(3) return pixmap_item def convertToQImg(self, convert_mask=False) -> QImage: img_array = self.getSubimg(convert_mask) if img_array is None: return None qimg = ndarray2pixmap(img_array, return_qimg=True) return qimg def originOffset(self) -> QPointF: thickness = self.pen().widthF() / 2 return QPointF(thickness, thickness) - self.stroke.boundingRect().topLeft() - self.clip_offset Loading @@ -76,7 +109,7 @@ class StrokeItem(QGraphicsPathItem): pos.setY(int(round(max(0, pos.y())))) return pos.toPoint() def getSubimg(self, convert_mask=False) -> np.ndarray: def getSubimg(self, convert_mask=False, return_pixmap=False) -> np.ndarray: if self.isEmpty(): return None Loading Loading @@ -109,23 +142,28 @@ class StrokeItem(QGraphicsPathItem): if xyxy_clip[0] >= xyxy_clip[2] or xyxy_clip[1] >= xyxy_clip[3]: return None stroke_clip = xyxy_clip - xyxy stroke_clip[2] += stroke_size.width() stroke_clip[3] += stroke_size.height() pixmap = QPixmap(stroke_size) pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(pixmap) painter.translate(self.originOffset()) painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.translate(self.originOffset()) painter.setPen(self.pen()) painter.setBrush(self.brush()) painter.drawPath(self.stroke) painter.end() stroke_clip = xyxy_clip - xyxy stroke_clip[2] += stroke_size.width() stroke_clip[3] += stroke_size.height() self.clip_offset = QPointF(stroke_clip[0], stroke_clip[1]) if return_pixmap: return pixmap imgarray = pixmap2ndarray(pixmap, keep_alpha=True) imgarray = imgarray[stroke_clip[1]: stroke_clip[3], stroke_clip[0]: stroke_clip[2]] # print(imgarray.shape, stroke_clip) self.clip_offset = QPointF(stroke_clip[0], stroke_clip[1]) if convert_mask: mask = imgarray[..., -1] mask[mask > 0] = 255 Loading @@ -135,25 +173,6 @@ class StrokeItem(QGraphicsPathItem): return imgarray class PenStrokeCommand(QUndoCommand): def __init__(self, canvas: QGraphicsScene, stroke_item: StrokeItem): super().__init__() self.stroke_item = stroke_item self.canvas = canvas def redo(self) -> None: self.canvas.addItem(self.stroke_item) self.stroke_item.setParentItem(self.canvas.imgLayer) def undo(self): self.canvas.removeItem(self.stroke_item) def mergeWith(self, command: QUndoCommand) -> bool: if self.stroke_item == command.stroke_item: return True return False class PenCursor(QCursor): def __init__(self, *args, **kwargs): super().__init__() Loading @@ -169,7 +188,7 @@ class PenCursor(QCursor): cur_pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(cur_pixmap) painter.setPen(pen) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.drawEllipse(self.thickness, self.thickness, size-2*self.thickness, size-2*self.thickness) painter.end() Loading @@ -186,3 +205,34 @@ class PixmapItem(QGraphicsPixmapItem): painter.setPen(pen) return super().paint(painter, option, widget) class DrawingLayer(QGraphicsPixmapItem): def __init__(self): super().__init__() self.qimg_dict = {} self.drawing_items_info = {} def addQImage(self, x: int, y: int, qimg: QImage, compose_mode, key: str): self.qimg_dict[key] = qimg self.drawing_items_info[key] = {'pos': [x, y], 'compose': compose_mode} self.update() def removeQImage(self, key: str): if key in self.qimg_dict: self.qimg_dict.pop(key) # self.drawing_items_pos.pop(key) def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget): pixmap = self.pixmap() p = QPainter() p.begin(pixmap) for key in self.qimg_dict: item = self.qimg_dict[key] info = self.drawing_items_info[key] if isinstance(item, QImage): p.setCompositionMode(info['compose']) p.drawImage(info['pos'][0], info['pos'][1], item) p.end() painter.drawPixmap(self.offset(), pixmap)
ballontranslator/ui/scene_textlayout.py +1 −1 Original line number Diff line number Diff line Loading @@ -396,7 +396,7 @@ class VerticalTextDocumentLayout(SceneTextLayout): num_rspaces, num_lspaces, char_yoffset_lst, line_pos = self.line_spaces_lst[blk_no][line.lineNumber()] y = char_yoffset_lst[cpos - line_pos] painter.setCompositionMode(QPainter.RasterOp_NotDestination) painter.setCompositionMode(QPainter.CompositionMode.RasterOp_NotDestination) painter.fillRect(QRectF(x, y, fm.height(), 2), painter.pen().brush()) if self.has_selection == has_selection: self.update.emit(QRectF(x, y, fm.height(), 2)) Loading
ballontranslator/ui/stylewidgets.py +1 −1 Original line number Diff line number Diff line Loading @@ -227,7 +227,7 @@ class PaintQSlider(QSlider): self.initStyleOption(option) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 中间圆圈的位置 rect = self.style().subControlRect( Loading