Commit d680f33c authored by dmMaze's avatar dmMaze
Browse files

fix erasing

parent b61ce0d4
Loading
Loading
Loading
Loading
+11 −10
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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()
@@ -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)
        
@@ -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):
@@ -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())
+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
@@ -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
@@ -213,7 +213,6 @@ class RectPanel(Widget):
        return self.autoChecker.isChecked()



class DrawingPanel(Widget):

    scale_tool_pos: QPointF = None
@@ -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)
@@ -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()

@@ -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)
@@ -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():
@@ -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:
@@ -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()
@@ -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):
+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
@@ -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)
@@ -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)
@@ -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
@@ -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

@@ -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
@@ -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__()
@@ -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()

@@ -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)
+1 −1
Original line number Diff line number Diff line
@@ -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))
+1 −1
Original line number Diff line number Diff line
@@ -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(