diff --git a/assets/SP.mp4 b/assets/SP.mp4 deleted file mode 100644 index 485df27..0000000 Binary files a/assets/SP.mp4 and /dev/null differ diff --git a/assets/help.png b/assets/help.png new file mode 100644 index 0000000..0f427ec Binary files /dev/null and b/assets/help.png differ diff --git a/assets/newUI.png b/assets/newUI.png index c534d03..63ea626 100644 Binary files a/assets/newUI.png and b/assets/newUI.png differ diff --git a/ui/canvas_window.py b/ui/canvas_window.py index 4f2a85e..322e443 100644 --- a/ui/canvas_window.py +++ b/ui/canvas_window.py @@ -1,16 +1,39 @@ -# 文件路径: ComfyUI/custom_nodes/ComfyUI-Snap_Processing/ui/canvas_window.py - import os import sys import time from PyQt5.QtWidgets import ( QApplication, QDialog, QPushButton, QVBoxLayout, QLineEdit, QHBoxLayout, - QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QGraphicsItem, QMessageBox + QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QGraphicsItem, QMessageBox, + QMenuBar, QMenu, QAction ) -from PyQt5.QtGui import QPixmap, QImage, QColor, QPainter, QTransform, QPen +from PyQt5.QtGui import QPixmap, QImage, QColor, QPainter, QTransform from PyQt5.QtCore import pyqtSignal, Qt, QRectF, QPointF +class CustomGraphicsView(QGraphicsView): + def __init__(self, scene, parent=None): + super().__init__(scene, parent) + self.zoom_step = 1.1 + self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) + self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) + self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) + self.setFocusPolicy(Qt.StrongFocus) # 确保视图可以接收焦点 + + def wheelEvent(self, event): + modifiers = QApplication.keyboardModifiers() + # 修改为无需按下 Alt 键即可缩放视图 + if modifiers == Qt.NoModifier: + delta = event.angleDelta().y() + if delta > 0: + factor = self.zoom_step + else: + factor = 1 / self.zoom_step + self.scale(factor, factor) + event.accept() + else: + super().wheelEvent(event) + + class CanvasWindow(QDialog): save_signal = pyqtSignal() close_signal = pyqtSignal() @@ -18,7 +41,8 @@ class CanvasWindow(QDialog): def __init__(self, input_image, canvas_width=512, canvas_height=512): super().__init__() - self.setWindowTitle("PyQt5 Image Canvas") + self.setWindowTitle("Snap Canvas") + self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.input_image = input_image self.canvas_width = canvas_width self.canvas_height = canvas_height @@ -29,32 +53,43 @@ class CanvasWindow(QDialog): self.scale_factor = 1.0 # 初始缩放倍数 self.scene = QGraphicsScene() - self.view = QGraphicsView(self.scene) - self.view.setStyleSheet("border: 1px solid black;") + # 使用自定义的视图 + self.view = CustomGraphicsView(self.scene) + self.view.setStyleSheet("border: 1px solid black; background-color: #404040;") # 设置深灰色背景区分画布 # 设置滚动条策略为需要时显示 self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + # 启用拖动画布 + self.view.setDragMode(QGraphicsView.ScrollHandDrag) + # 创建白色画布 self.canvas = QImage(self.canvas_width, self.canvas_height, QImage.Format_RGB888) self.canvas.fill(QColor('white')) - # 添加画布背景 + # 添加画布到场景 self.canvas_pixmap_item = QGraphicsPixmapItem(QPixmap.fromImage(self.canvas)) self.scene.addItem(self.canvas_pixmap_item) # 添加可调整大小和可旋转的输入图像 - # 直接显示原始尺寸,不进行适应画布 original_pixmap = QPixmap.fromImage(self.input_image) - self.input_pixmap_item = ResizablePixmapItem(original_pixmap, self.canvas_width, self.canvas_height) + self.input_pixmap_item = ResizablePixmapItem(original_pixmap) + self.input_pixmap_item.setPos(self.canvas_width / 2, self.canvas_height / 2) # 将图像放置在画布中央 self.scene.addItem(self.input_pixmap_item) # 设置旋转锚点为图像中心 self.input_pixmap_item.setTransformOriginPoint(self.input_pixmap_item.boundingRect().center()) - # 添加画布边界线 - self.add_canvas_border() + # 创建菜单栏 + self.menu_bar = QMenuBar(self) + help_menu = QMenu("使用说明(Help)", self) + self.menu_bar.addMenu(help_menu) + + # 创建“使用方法”动作 + usage_action = QAction("使用方法", self) + usage_action.triggered.connect(self.show_usage) + help_menu.addAction(usage_action) # 创建按钮和输入框 save_button = QPushButton("保存并关闭", self) @@ -97,6 +132,7 @@ class CanvasWindow(QDialog): hbox.addWidget(scale_button) vbox = QVBoxLayout() + vbox.setMenuBar(self.menu_bar) # 将菜单栏添加到布局中 vbox.addWidget(self.view) vbox.addLayout(hbox) vbox.addWidget(save_button) @@ -109,16 +145,18 @@ class CanvasWindow(QDialog): # 设置窗口最小大小 self.setMinimumSize(600, 400) - def add_canvas_border(self): - """添加画布边界线以可视化画布边缘,但不包含在输出中""" - pen = QPen(QColor('red')) - pen.setWidth(2) - border_rect = QRectF(0, 0, self.canvas_width, self.canvas_height) - self.border_item = self.scene.addRect(border_rect, pen) - self.border_item.setZValue(1) # 确保边界线在最上层 - self.border_item.setFlag(QGraphicsItem.ItemIsSelectable, False) - self.border_item.setFlag(QGraphicsItem.ItemIsMovable, False) - self.border_item.setFlag(QGraphicsItem.ItemSendsGeometryChanges, False) + def show_usage(self): + usage_text = ( + "使用方法:\n" + "1. 滚动鼠标滚轮可缩放视图。\n" + "2. 按住 Ctrl 键并滚动鼠标滚轮可缩放图像。\n" + "3. 在输入框中输入旋转角度(度数),点击“旋转”按钮可旋转图像。\n" + "4. 在输入框中输入缩放倍数,点击“缩放”按钮可缩放图像。\n" + "5. 拖动图像可移动其位置。\n" + "6. 使用上方输入框可调整画布大小。\n" + "7. 点击“保存并关闭”按钮可保存当前图像。\n" + ) + QMessageBox.information(self, "使用方法", usage_text) def save_and_close(self): self.save_image() @@ -132,15 +170,9 @@ class CanvasWindow(QDialog): painter = QPainter(image) target_rect = QRectF(0, 0, self.canvas_width, self.canvas_height) - # 隐藏边界线 - self.border_item.setVisible(False) - # 渲染场景 self.scene.render(painter, target_rect, self.scene.sceneRect(), Qt.IgnoreAspectRatio) - # 显示边界线 - self.border_item.setVisible(True) - painter.end() self.modified_image = image @@ -151,7 +183,7 @@ class CanvasWindow(QDialog): self.top_left_y = int(top_left_point.y()) # 获取缩放倍数 - self.scale_factor = self.input_pixmap_item.get_current_scale() + self.scale_factor = self.input_pixmap_item.current_scale def closeEvent(self, event): self.close_signal.emit() @@ -165,32 +197,24 @@ class CanvasWindow(QDialog): QMessageBox.warning(self, "输入错误", "画布宽度和高度必须是整数。") return + # 更新画布宽度和高度 self.canvas_width = width self.canvas_height = height - # 更新画布图像 + # 更新场景和视图的画布大小 self.canvas = QImage(self.canvas_width, self.canvas_height, QImage.Format_RGB888) self.canvas.fill(QColor('white')) self.canvas_pixmap_item.setPixmap(QPixmap.fromImage(self.canvas)) - # 更新场景和视图 self.scene.setSceneRect(0, 0, self.canvas_width, self.canvas_height) self.view.setSceneRect(0, 0, self.canvas_width, self.canvas_height) - # 更新边界线 - self.scene.removeItem(self.border_item) - self.add_canvas_border() - - # 更新视图大小以适应新的画布大小 - self.view.updateGeometry() - def rotate_image(self): try: angle = float(self.rotate_input.text()) except ValueError: QMessageBox.warning(self, "输入错误", "旋转角度必须是数字。") return - self.input_pixmap_item.rotate_pixmap(angle) def scale_image(self): @@ -199,9 +223,8 @@ class CanvasWindow(QDialog): if scale_factor <= 0: raise ValueError except ValueError: - QMessageBox.warning(self, "输入错误", "缩放倍数必须是一个正数。") + QMessageBox.warning(self, "输入错误", "缩放倍数必须是正数。") return - self.input_pixmap_item.scale_pixmap(scale_factor) def get_modified_image(self): @@ -218,127 +241,66 @@ class CanvasWindow(QDialog): class ResizablePixmapItem(QGraphicsPixmapItem): - def __init__(self, pixmap, canvas_width, canvas_height): + def __init__(self, pixmap): super().__init__(pixmap) - self.canvas_width = canvas_width - self.canvas_height = canvas_height - self.original_pixmap = pixmap.copy() # 保持原始图像的副本 - self.original_size = pixmap.size() self.setFlags( QGraphicsItem.ItemIsSelectable | - QGraphicsItem.ItemIsMovable | - QGraphicsItem.ItemSendsGeometryChanges + QGraphicsItem.ItemIsMovable ) + self.setTransformationMode(Qt.SmoothTransformation) self.setAcceptHoverEvents(True) - self.is_resizing = False - self.resize_handle_size = 15 # 增大缩放手柄大小,便于操作 - self.aspect_ratio = pixmap.width() / pixmap.height() if pixmap.height() != 0 else 1 - self.setCursor(Qt.OpenHandCursor) - self.min_scale = 0.1 - self.max_scale = 10 - self.current_scale = 1.0 - self.current_rotation = 0 # 当前旋转角度 - - # 设置旋转锚点为图像中心 + self.setAcceptDrops(False) self.setTransformOriginPoint(self.boundingRect().center()) - - def hoverMoveEvent(self, event): - if self.is_in_resize_area(event.pos()): - self.setCursor(Qt.SizeFDiagCursor) - else: - self.setCursor(Qt.OpenHandCursor) - super().hoverMoveEvent(event) - - def mousePressEvent(self, event): - if event.button() == Qt.LeftButton and self.is_in_resize_area(event.pos()): - self.is_resizing = True - self.resize_start_pos = event.pos() - self.setCursor(Qt.SizeFDiagCursor) - event.accept() - else: - self.is_resizing = False - self.setCursor(Qt.ClosedHandCursor) - super().mousePressEvent(event) - - def mouseMoveEvent(self, event): - if self.is_resizing: - offset = event.pos() - self.resize_start_pos - self.resize_image(offset) - event.accept() - else: - super().mouseMoveEvent(event) - - def mouseReleaseEvent(self, event): - if self.is_resizing: - self.is_resizing = False - self.setCursor(Qt.OpenHandCursor) - event.accept() - else: - self.setCursor(Qt.OpenHandCursor) - super().mouseReleaseEvent(event) - - def boundingRect(self): - return QRectF(0, 0, self.pixmap().width(), self.pixmap().height()) - - def resize_image(self, offset): - # 调整缩放灵敏度 - scale_change = offset.x() / 500.0 # 增加分母以减少灵敏度 - scale_factor = 1 + scale_change - if scale_factor <= 0: - return # 防止缩放为零或负值 - - new_scale = self.current_scale * scale_factor - - if self.min_scale <= new_scale <= self.max_scale: - self.current_scale = new_scale - self.setScale(self.current_scale) - self.update() - - def is_in_resize_area(self, pos): - rect = self.boundingRect() - resize_rect = QRectF( - rect.right() - self.resize_handle_size, - rect.bottom() - self.resize_handle_size, - self.resize_handle_size, - self.resize_handle_size - ) - return resize_rect.contains(pos) - - def wheelEvent(self, event): - try: - if hasattr(event, 'angleDelta'): - delta = event.angleDelta().y() - elif hasattr(event, 'delta'): - delta = event.delta() - else: - delta = 0 - - factor = 1.1 if delta > 0 else 0.9 - self.resize_with_factor(factor) - except AttributeError: - pass - - def resize_with_factor(self, factor): - new_scale = self.current_scale * factor - - if self.min_scale <= new_scale <= self.max_scale: - self.current_scale = new_scale - self.setScale(self.current_scale) - self.update() - - def rotate_pixmap(self, angle): - """根据输入角度旋转图像,并保持旋转锚点为中心""" - self.current_rotation = (self.current_rotation + angle) % 360 - self.setRotation(self.current_rotation) + self.current_scale = 1.0 + self.current_rotation = 0.0 + self.min_scale = 0.1 + self.max_scale = 10.0 def scale_pixmap(self, scale_factor): - """手动设置缩放倍数""" - if scale_factor <= 0: - return if self.min_scale <= scale_factor <= self.max_scale: self.current_scale = scale_factor - self.setScale(self.current_scale) - self.update() + self.update_transform() + else: + QMessageBox.warning(None, "无效的缩放", f"缩放倍数必须在 {self.min_scale} 和 {self.max_scale} 之间。") - def get_current_scale(self): - return self.current_scale + def rotate_pixmap(self, angle): + self.current_rotation = angle % 360 + self.update_transform() + + def update_transform(self): + transform = QTransform() + center = self.boundingRect().center() + transform.translate(center.x(), center.y()) + transform.rotate(self.current_rotation) + transform.scale(self.current_scale, self.current_scale) + transform.translate(-center.x(), -center.y()) + self.setTransform(transform) + + def wheelEvent(self, event): + if QApplication.keyboardModifiers() == Qt.ControlModifier: + delta = event.delta() # 修改这里,使用 delta() + if delta > 0: + factor = 1.1 + else: + factor = 0.9 + new_scale = self.current_scale * factor + self.scale_pixmap(new_scale) + event.accept() + else: + event.ignore() # 确保事件能够传递给视图进行缩放 + + +if __name__ == "__main__": + app = QApplication(sys.argv) + # 示例输入图像,可以替换为您自己的图像路径 + input_image_path = "your_image.png" + if not os.path.exists(input_image_path): + # 创建一个示例图像 + input_image = QImage(200, 200, QImage.Format_RGB888) + input_image.fill(QColor('blue')) + else: + input_image = QImage(input_image_path) + + window = CanvasWindow(input_image) + window.show() + sys.exit(app.exec_())