Commit 9c15c234 authored by dmMaze's avatar dmMaze
Browse files

split canvasUndoStack into drawing & textediting

parent 95d48de4
Loading
Loading
Loading
Loading
+88 −19
Original line number Diff line number Diff line
@@ -6,10 +6,10 @@ from qtpy.QtCore import Qt, QDateTime, QRectF, QPointF, QPoint, Signal, QSizeF,
from qtpy.QtGui import QPixmap, QHideEvent, QKeyEvent, QWheelEvent, QResizeEvent, QKeySequence, QPainter, QPen, QPainterPath

try:
    from qtpy.QtWidgets import QUndoStack
    from qtpy.QtWidgets import QUndoStack, QUndoCommand

except:
    from qtpy.QtGui import QUndoStack
    from qtpy.QtGui import QUndoStack, QUndoCommand

from .misc import ndarray2pixmap
from .imgtrans_proj import ProjImgTrans
@@ -17,7 +17,7 @@ from .textitem import TextBlkItem, TextBlock
from .texteditshapecontrol import TextBlkShapeControl
from .stylewidgets import FadeLabel
from .image_edit import ImageEditMode, DrawingLayer, StrokeImgItem
from .page_search_widget import PageSearchWidget, ReplaceOneCommand, ReplaceAllCommand
from .page_search_widget import PageSearchWidget
from . import constants as C

CANVAS_SCALE_MAX = 3.0
@@ -123,8 +123,6 @@ class Canvas(QGraphicsScene):
            self.gv.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)

        self.search_widget = PageSearchWidget(self.gv)
        self.search_widget.replace_one.connect(self.on_search_replace_one)
        self.search_widget.replace_all.connect(self.on_search_replace_all)
        
        self.ctrl_relesed = self.gv.ctrl_released
        self.vscroll_bar = self.gv.verticalScrollBar()
@@ -134,8 +132,13 @@ class Canvas(QGraphicsScene):
        self.rubber_band.hide()
        self.rubber_band_origin = None

        self.undoStack = QUndoStack(self)
        self.undoStack.indexChanged.connect(self.on_undostack_changed)
        self.draw_undo_stack = QUndoStack(self)
        self.draw_undo_stack.indexChanged.connect(self.on_drawstack_changed)
        self.text_undo_stack = QUndoStack(self)
        self.text_undo_stack.indexChanged.connect(self.on_textstack_changed)
        self.saved_drawundo_step = 0
        self.saved_textundo_step = 0

        self.scaleFactorLabel = FadeLabel(self.gv)
        self.scaleFactorLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.scaleFactorLabel.setText('100%')
@@ -174,6 +177,15 @@ class Canvas(QGraphicsScene):
        self.mid_btn_pressed = False
        self.pan_initial_pos = QPoint(0, 0)

        self.saved_textundo_step = 0
        self.saved_drawundo_step = 0

    def textEditMode(self) -> bool:
        return self.editor_index == 1

    def drawMode(self) -> bool:
        return self.editor_index == 0

    def scaleUp(self):
        self.scaleImage(1 + CANVAS_SCALE_SPEED)

@@ -265,10 +277,10 @@ class Canvas(QGraphicsScene):
        if self.editing_textblkitem is not None:
            return super().keyPressEvent(event)
        if event == QKeySequence.Undo:
            self.undoStack.undo()
            self.undo()
            self.txtblkShapeControl.updateBoundingRect()
        elif event == QKeySequence.Redo:
            self.undoStack.redo()
            self.redo()
            self.txtblkShapeControl.updateBoundingRect()
        elif event.key() == Qt.Key.Key_Alt:
            self.alt_pressed = True
@@ -491,10 +503,6 @@ class Canvas(QGraphicsScene):
        if self.stroke_img_item is not None:
            self.removeItem(self.stroke_img_item)

    def on_undostack_changed(self):
        if self.undoStack.count() != 0:
            self.setProjSaveState(True)

    def setProjSaveState(self, un_saved: bool):
        if un_saved == self.projstate_unsaved:
            return
@@ -509,10 +517,71 @@ class Canvas(QGraphicsScene):
            self.stroke_img_item = None
            self.erase_img_key = None

    def on_search_replace_one(self):
        self.undoStack.push(ReplaceOneCommand(self.search_widget))
        pass
    def get_active_undostack(self) -> QUndoStack:
        if self.textEditMode():
            return self.text_undo_stack
        elif self.drawMode():
            return self.draw_undo_stack
        return None

    def push_undo_command(self, command: QUndoCommand):
        undo_stack = self.get_active_undostack()
        if undo_stack is not None:
            undo_stack.push(command)

    def on_drawstack_changed(self, index: int):
        if index != self.saved_drawundo_step:
            self.setProjSaveState(True)
        elif self.text_undo_stack.index() == self.saved_textundo_step:
            self.setProjSaveState(False)

    def on_textstack_changed(self, index: int):
        if index != self.saved_textundo_step:
            self.setProjSaveState(True)
        elif self.draw_undo_stack.index() == self.saved_drawundo_step:
            self.setProjSaveState(False)

    def redo_textedit(self):
        self.text_undo_stack.redo()

    def undo_textedit(self):
        self.text_undo_stack.undo()

    def redo(self):
        undo_stack = self.get_active_undostack()
        if undo_stack is not None:
            undo_stack.redo()

    def undo(self):
        undo_stack = self.get_active_undostack()
        if undo_stack is not None:
            undo_stack.undo()

    def clear_undostack(self, update_saved_step=False):
        if update_saved_step:
            self.saved_drawundo_step = 0
            self.saved_textundo_step = 0
        self.draw_undo_stack.clear()
        self.text_undo_stack.clear()

    def clear_text_stack(self):
        self.text_undo_stack.clear()

    def clear_draw_stack(self):
        self.draw_undo_stack.clear()

    def update_saved_undostep(self):
        self.saved_drawundo_step = self.draw_undo_stack.index()
        self.saved_textundo_step = self.text_undo_stack.index()

    def text_change_unsaved(self) -> bool:
        return self.saved_textundo_step == self.text_undo_stack.index()

    def draw_change_unsaved(self) -> bool:
        return self.saved_drawundo_step == self.draw_undo_stack.index()

    def on_search_replace_all(self):
        self.undoStack.push(ReplaceAllCommand(self.search_widget))
        pass
 No newline at end of file
    def prepareClose(self):
        self.blockSignals(True)
        self.disconnect()
        self.text_undo_stack.disconnect()
        self.draw_undo_stack.disconnect()
 No newline at end of file
+83 −0
Original line number Diff line number Diff line
from qtpy.QtCore import Signal, Qt, QPointF, QSize, QLineF, QDateTime, QRectF, QPoint
from qtpy.QtWidgets import QGridLayout, 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, QUndoStack
except:
    from qtpy.QtGui import QUndoCommand, QUndoStack

from typing import Union, Tuple, List
import numpy as np
import cv2

from utils.imgproc_utils import enlarge_window
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, PixmapItem, DrawingLayer, StrokeImgItem
from .configpanel import InpaintConfigPanel
from .stylewidgets import Widget, SeparatorWidget, ColorPicker, PaintQSlider
from .canvas import Canvas



class StrokeItemUndoCommand(QUndoCommand):
    def __init__(self, target_layer: DrawingLayer, rect: Tuple[int], qimg: QImage, erasing=False):
        super().__init__()
        self.qimg = qimg
        self.x = rect[0]
        self.y = rect[1]
        self.target_layer = target_layer
        self.key = str(QDateTime.currentMSecsSinceEpoch())
        if erasing:
            self.compose_mode = QPainter.CompositionMode.CompositionMode_DestinationOut
        else:
            self.compose_mode = QPainter.CompositionMode.CompositionMode_SourceOver
        
    def undo(self):
        if self.qimg is not None:
            self.target_layer.removeQImage(self.key)
            self.target_layer.update()

    def redo(self):
        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):
    def __init__(self, canvas: Canvas, inpainted: np.ndarray, mask: np.ndarray, inpaint_rect: List[int]):
        super().__init__()
        self.canvas = canvas
        img_array = self.canvas.imgtrans_proj.inpainted_array
        mask_array = self.canvas.imgtrans_proj.mask_array
        img_view = img_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        mask_view = mask_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        self.undo_img = np.copy(img_view)
        self.undo_mask = np.copy(mask_view)
        self.redo_img = inpainted
        self.redo_mask = mask
        self.inpaint_rect = inpaint_rect

    def redo(self) -> None:
        inpaint_rect = self.inpaint_rect
        img_array = self.canvas.imgtrans_proj.inpainted_array
        mask_array = self.canvas.imgtrans_proj.mask_array
        img_view = img_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        mask_view = mask_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        img_view[:] = self.redo_img
        mask_view[:] = self.redo_mask
        self.canvas.setInpaintLayer()
        self.canvas.setMaskLayer()

    def undo(self) -> None:
        inpaint_rect = self.inpaint_rect
        img_array = self.canvas.imgtrans_proj.inpainted_array
        mask_array = self.canvas.imgtrans_proj.mask_array
        img_view = img_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        mask_view = mask_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        img_view[:] = self.undo_img
        mask_view[:] = self.undo_mask
        self.canvas.setInpaintLayer()
        self.canvas.setMaskLayer()
 No newline at end of file
+12 −78
Original line number Diff line number Diff line
from qtpy.QtCore import Signal, Qt, QPointF, QSize, QLineF, QDateTime, QRectF, QPoint
from qtpy.QtCore import Signal, Qt, QPointF, QSize, QLineF, QRectF
from qtpy.QtWidgets import QGridLayout, 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
except:
    from qtpy.QtGui import QUndoCommand
from qtpy.QtGui import QPen, QColor, QCursor, QPainter, QPixmap, QBrush, QFontMetrics

from typing import Union, Tuple, List
import numpy as np
@@ -16,12 +11,13 @@ 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, PixmapItem, DrawingLayer, StrokeImgItem
from .image_edit import ImageEditMode, PixmapItem, StrokeImgItem
from .configpanel import InpaintConfigPanel
from .stylewidgets import Widget, SeparatorWidget, ColorPicker, PaintQSlider
from .canvas import Canvas
from .misc import DrawPanelConfig, ndarray2pixmap, pixmap2ndarray
from .misc import DrawPanelConfig, ndarray2pixmap
from .constants import CONFIG_COMBOBOX_SHORT, CONFIG_COMBOBOX_HEIGHT
from .drawing_commands import InpaintUndoCommand, StrokeItemUndoCommand

INPAINT_BRUSH_COLOR = QColor(127, 0, 127, 127)
MAX_PEN_SIZE = 1000
@@ -504,7 +500,7 @@ class DrawingPanel(Widget):
        if self.currentTool == self.penTool:
            rect, _, qimg = stroke_item.clip()
            if rect is not None:
                self.canvas.undoStack.push(StrokeItemUndoCommand(self.canvas.drawingLayer, rect, qimg))
                self.canvas.push_undo_command(StrokeItemUndoCommand(self.canvas.drawingLayer, rect, qimg))
            self.canvas.removeItem(stroke_item)
        elif self.currentTool == self.inpaintTool:
            self.inpaint_stroke = stroke_item
@@ -538,7 +534,7 @@ class DrawingPanel(Widget):
            inpaint_mask = np.zeros_like(inpainted)
            inpaint_mask[mask > 0] = 1
            erased_img = inpaint_mask * inpainted + (1 - inpaint_mask) * origin
            self.canvas.undoStack.push(InpaintUndoCommand(self.canvas, erased_img, mask, inpaint_rect))
            self.canvas.push_undo_command(InpaintUndoCommand(self.canvas, erased_img, mask, inpaint_rect))
            self.canvas.removeItem(stroke_item)

        elif self.currentTool == self.penTool:
@@ -548,7 +544,7 @@ class DrawingPanel(Widget):
                self.canvas.erase_img_key = None
                self.canvas.stroke_img_item = None
            if rect is not None:
                self.canvas.undoStack.push(StrokeItemUndoCommand(self.canvas.drawingLayer, rect, qimg, True))
                self.canvas.push_undo_command(StrokeItemUndoCommand(self.canvas.drawingLayer, rect, qimg, True))
        

    def runInpaint(self, inpaint_dict=None):
@@ -590,7 +586,7 @@ class DrawingPanel(Widget):
        inpaint_rect = inpaint_dict['inpaint_rect']
        mask_array = self.canvas.imgtrans_proj.mask_array
        mask = cv2.bitwise_or(inpaint_dict['mask'], mask_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]])
        self.canvas.undoStack.push(InpaintUndoCommand(self.canvas, inpainted, mask, inpaint_rect))
        self.canvas.push_undo_command(InpaintUndoCommand(self.canvas, inpainted, mask, inpaint_rect))
        self.clearInpaintItems()

    def on_inpaint_failed(self):
@@ -709,7 +705,7 @@ class DrawingPanel(Widget):
            else:   # erasing
                mask = np.zeros((y2 - y1, x2 - x1), dtype=np.uint8)
                erased = self.canvas.imgtrans_proj.img_array[y1: y2, x1: x2]
                self.canvas.undoStack.push(InpaintUndoCommand(self.canvas, erased, mask, [x1, y1, x2, y2]))
                self.canvas.push_undo_command(InpaintUndoCommand(self.canvas, erased, mask, [x1, y1, x2, y2]))
                self.canvas.image_edit_mode = ImageEditMode.RectTool
            self.setCrossCursor()

@@ -721,7 +717,7 @@ class DrawingPanel(Widget):
        ballon_mask = inpaint_dict['ballon_mask']
        if not need_inpaint and self.dl_manager.dl_config.check_need_inpaint:
            img[np.where(ballon_mask > 0)] = bground_bgr
            self.canvas.undoStack.push(InpaintUndoCommand(self.canvas, img, mask, inpaint_dict['inpaint_rect']))
            self.canvas.push_undo_command(InpaintUndoCommand(self.canvas, img, mask, inpaint_dict['inpaint_rect']))
            self.clearInpaintItems()
        else:
            self.runInpaint(inpaint_dict=inpaint_dict)
@@ -766,65 +762,3 @@ class DrawingPanel(Widget):
            self.inpaint_stroke = None
            if self.inpaintTool.isChecked():
                self.canvas.image_edit_mode = ImageEditMode.InpaintTool
 No newline at end of file

class StrokeItemUndoCommand(QUndoCommand):
    def __init__(self, target_layer: DrawingLayer, rect: Tuple[int], qimg: QImage, erasing=False):
        super().__init__()
        self.qimg = qimg
        self.x = rect[0]
        self.y = rect[1]
        self.target_layer = target_layer
        self.key = str(QDateTime.currentMSecsSinceEpoch())
        if erasing:
            self.compose_mode = QPainter.CompositionMode.CompositionMode_DestinationOut
        else:
            self.compose_mode = QPainter.CompositionMode.CompositionMode_SourceOver
        
    def undo(self):
        if self.qimg is not None:
            self.target_layer.removeQImage(self.key)
            self.target_layer.update()
        # self.stroke_pixmap.hide()

    def redo(self):
        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):
    def __init__(self, canvas: Canvas, inpainted: np.ndarray, mask: np.ndarray, inpaint_rect: List[int]):
        super().__init__()
        self.canvas = canvas
        img_array = self.canvas.imgtrans_proj.inpainted_array
        mask_array = self.canvas.imgtrans_proj.mask_array
        img_view = img_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        mask_view = mask_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        self.undo_img = np.copy(img_view)
        self.undo_mask = np.copy(mask_view)
        self.redo_img = inpainted
        self.redo_mask = mask
        self.inpaint_rect = inpaint_rect

    def redo(self) -> None:
        inpaint_rect = self.inpaint_rect
        img_array = self.canvas.imgtrans_proj.inpainted_array
        mask_array = self.canvas.imgtrans_proj.mask_array
        img_view = img_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        mask_view = mask_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        img_view[:] = self.redo_img
        mask_view[:] = self.redo_mask
        self.canvas.setInpaintLayer()
        self.canvas.setMaskLayer()

    def undo(self) -> None:
        inpaint_rect = self.inpaint_rect
        img_array = self.canvas.imgtrans_proj.inpainted_array
        mask_array = self.canvas.imgtrans_proj.mask_array
        img_view = img_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        mask_view = mask_array[inpaint_rect[1]: inpaint_rect[3], inpaint_rect[0]: inpaint_rect[2]]
        img_view[:] = self.undo_img
        mask_view[:] = self.undo_mask
        self.canvas.setInpaintLayer()
        self.canvas.setMaskLayer()
+0 −4
Original line number Diff line number Diff line
from qtpy.QtWidgets import QHBoxLayout, QComboBox, QSizePolicy, QLabel, QTreeView, QCheckBox, QMessageBox, QVBoxLayout, QStyle, QSlider, QStyle,  QGraphicsDropShadowEffect, QWidget
from qtpy.QtCore import Qt, QItemSelection, QSize, Signal, QUrl, QThread
from qtpy.QtGui import QKeyEvent, QTextCursor, QStandardItemModel, QStandardItem, QFontMetrics, QColor, QShowEvent, QSyntaxHighlighter, QTextCharFormat
try:
    from qtpy.QtWidgets import QUndoCommand
except:
    from qtpy.QtGui import QUndoCommand

from typing import List, Union, Tuple, Dict

+4 −1
Original line number Diff line number Diff line
@@ -4,6 +4,10 @@ import cv2
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
except:
    from qtpy.QtGui import QUndoCommand

from .misc import DrawPanelConfig, pixmap2ndarray, ndarray2pixmap
from utils.io_utils import imread, imwrite
@@ -159,4 +163,3 @@ class DrawingLayer(QGraphicsPixmapItem):
    def clearAllDrawings(self):
        self.qimg_dict.clear()
        self.drawing_items_info.clear()
Loading