import os
import sys
import numpy as np
from PyQt5.QtWidgets import (QGraphicsView, QGraphicsScene, QGraphicsPixmapItem,
QRubberBand, QApplication, QGraphicsPathItem, QMenu)
from PyQt5.QtGui import (QPixmap, QImage, QPainter, QPen, QColor, QBrush,
QKeyEvent, QTransform, QPainterPath, QCursor, QPolygonF)
from PyQt5.QtCore import (Qt, QPoint, QRect, QSize, QEvent, QPointF, QRectF,
pyqtSignal, QTimer, QLineF, QVariantAnimation)
class ImageCanvas(QGraphicsView):
# 信号定义
zoom_changed = pyqtSignal(float)
image_changed = pyqtSignal(QImage)
mouse_position_changed = pyqtSignal(QPointF, str)
selection_changed = pyqtSignal(QRectF)
tool_changed = pyqtSignal(int)
# 工具枚举
TOOL_SELECT = 0
TOOL_CROP = 1
TOOL_BRUSH = 2
TOOL_SHAPE = 3
TOOL_TEXT = 4
TOOL_ERASER = 5
def __init__(self, parent=None):
super().__init__(parent)
self.parent_window = parent
self.setRenderHint(QPainter.Antialiasing)
self.setRenderHint(QPainter.SmoothPixmapTransform)
self.setDragMode(QGraphicsView.ScrollHandDrag)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setInteractive(True)
self.viewport().setCursor(Qt.CrossCursor)
# 创建场景
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.scene.setBackgroundBrush(QBrush(QColor(50, 50, 50)))
# 初始化变量
self.zoom_factor = 1.0
self.min_zoom = 0.05
self.max_zoom = 50.0
self.original_image = QImage()
self.displayed_image = QImage()
self.current_image_item = None
self.saved_transforms = None
# 工具相关状态
self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.viewport())
self.rubber_band.hide()
self.selection_rect = QRectF()
self.selection_active = False
self.selection_start = QPointF()
# 当前工具状态
self.current_tool = self.TOOL_SELECT
self.tool_settings = {
'brush_size': 5,
'brush_color': QColor(255, 0, 0, 255),
'eraser_size': 20,
'shape_type': 0, # 0:矩形, 1:圆形, 2:直线
'antialias': True,
'opacity': 1.0
}
# 绘制相关状态
self.drawing_active = False
self.drawing_path = None
self.drawing_item = None
self.drawing_start = QPointF()
self.current_layer = 0
self.layers = []
# 初始化默认图层
self.add_new_layer("Background", QColor(255, 255, 255))
# 历史记录快照
self.snapshots = []
self.current_snapshot = -1
# 安装事件过滤器
self.viewport().installEventFilter(self)
# 状态提示定时器
self.status_timer = QTimer(self)
self.status_timer.setSingleShot(True)
self.status_timer.timeout.connect(self._clear_status)
# ==================== 图层管理功能 ====================
def add_new_layer(self, name, fill_color=QColor(0, 0, 0, 0)):
"""添加新图层"""
layer = {
'name': name,
'visible': True,
'locked': False,
'opacity': 1.0,
'image': QImage(self.displayed_image.size(), QImage.Format_ARGB32),
'item': None
}
layer['image'].fill(fill_color)
self.layers.append(layer)
self.current_layer = len(self.layers) - 1
self._render_layers()
return self.current_layer
def remove_layer(self, index):
"""移除指定图层"""
if 0 <= index < len(self.layers):
if self.layers[index]['item']:
self.scene.removeItem(self.layers[index]['item'])
del self.layers[index]
if index <= self.current_layer:
self.current_layer = max(0, self.current_layer - 1)
self._render_layers()
def set_current_layer(self, index):
"""设置当前活动图层"""
if 0 <= index < len(self.layers):
self.current_layer = index
return True
return False
def toggle_layer_visibility(self, index):
"""切换图层可见性"""
if 0 <= index < len(self.layers):
self.layers[index]['visible'] = not self.layers[index]['visible']
self._render_layers()
def _render_layers(self):
"""渲染所有可见图层到显示图像"""
if not self.layers:
return
# 创建一个临时图像来合成所有图层
size = self.displayed_image.size()
composite = QImage(size, QImage.Format_ARGB32)
composite.fill(Qt.transparent)
painter = QPainter(composite)
for layer in self.layers:
if not layer['visible']:
continue
painter.setOpacity(layer['opacity'])
painter.drawImage(0, 0, layer['image'])
painter.end()
# 更新显示
self.displayed_image = composite
self._show_image(composite)
# 更新UI
self.image_changed.emit(self.displayed_image)
# ==================== 图像管理功能 ====================
def set_image(self, image):
"""设置要显示的图像"""
if image.isNull():
self.clear()
return
self.original_image = image.copy()
self.displayed_image = image.copy()
self._clear_layers()
self.add_new_layer("Background", Qt.white)
self.layers[0]['image'] = image.copy()
self.reset_view()
self.image_changed.emit(self.displayed_image)
self.take_snapshot()
def update_image(self):
"""更新显示的图像"""
if not self.displayed_image.isNull():
self._show_image(self.displayed_image)
self.image_changed.emit(self.displayed_image)
def clear(self):
"""清除画布"""
self.scene.clear()
self.current_image_item = None
self.displayed_image = QImage()
self.original_image = QImage()
self.zoom_factor = 1.0
self.zoom_changed.emit(self.zoom_factor)
self.resetTransform()
self._clear_layers()
def reset_view(self):
"""重置视图到初始状态"""
if not self.displayed_image.isNull():
self._show_image(self.displayed_image)
self.fit_to_view()
def get_current_image(self):
"""获取当前显示的图像"""
return self.displayed_image
def get_original_image(self):
"""获取原始未编辑图像"""
return self.original_image
def get_pixel_at(self, pos):
"""获取指定位置的像素颜色"""
if self.displayed_image.isNull():
return None
if not self.current_image_item:
return None
# 转换到图像坐标
img_pos = self.map_to_image(pos)
x = int(img_pos.x())
y = int(img_pos.y())
# 检查边界
if 0 <= x < self.displayed_image.width() and 0 <= y < self.displayed_image.height():
color = QColor(self.displayed_image.pixel(x, y))
return color
return None
def map_to_image(self, scene_pos):
"""将场景坐标映射到图像坐标"""
if not self.current_image_item:
return QPointF()
return self.current_image_item.mapFromScene(scene_pos)
# ==================== 缩放功能 ====================
def zoom_in(self):
"""放大图像"""
self.set_zoom(self.zoom_factor * 1.2)
def zoom_out(self):
"""缩小图像"""
self.set_zoom(self.zoom_factor * 0.8)
def fit_to_view(self):
"""适应窗口大小"""
if self.current_image_item:
self.fitInView(self.scene.itemsBoundingRect(), Qt.KeepAspectRatio)
self._update_zoom_factor()
def set_zoom(self, factor):
"""设置特定缩放级别"""
factor = max(self.min_zoom, min(factor, self.max_zoom))
if abs(factor - self.zoom_factor) > 0.01:
self.zoom_factor = factor
self.resetTransform()
self.scale(factor, factor)
self.zoom_changed.emit(self.zoom_factor * 100)
def _update_zoom_factor(self):
"""更新缩放因子"""
view_rect = self.transform().mapRect(QRectF(0, 0, 1, 1))
self.zoom_factor = 1.0 / view_rect.width()
self.zoom_changed.emit(self.zoom_factor * 100)
# ==================== 工具控制功能 ====================
def set_tool(self, tool_id):
"""设置当前工具"""
self.current_tool = tool_id
self.tool_changed.emit(tool_id)
self._update_cursor()
def set_tool_setting(self, key, value):
"""设置工具参数"""
if key in self.tool_settings:
self.tool_settings[key] = value
self._update_cursor()
def _update_cursor(self):
"""根据当前工具更新光标"""
if self.current_tool == self.TOOL_SELECT:
self.viewport().setCursor(Qt.CrossCursor)
elif self.current_tool == self.TOOL_CROP:
self.viewport().setCursor(Qt.CrossCursor)
elif self.current_tool == self.TOOL_BRUSH:
# 创建画笔形状的光标
size = self.tool_settings['brush_size'] * self.zoom_factor
size = max(6, min(int(size), 100))
pixmap = QPixmap(size, size)
pixmap.fill(Qt.transparent)
painter = QPainter(pixmap)
painter.setPen(QPen(Qt.black, 1))
painter.setBrush(QBrush(self.tool_settings['brush_color']))
painter.drawEllipse(1, 1, size - 2, size - 2)
painter.end()
self.viewport().setCursor(QCursor(pixmap, size / 2, size / 2))
elif self.current_tool == self.TOOL_ERASER:
# 创建橡皮擦形状的光标
size = self.tool_settings['eraser_size'] * self.zoom_factor
size = max(6, min(int(size), 100))
pixmap = QPixmap(size, size)
pixmap.fill(Qt.transparent)
painter = QPainter(pixmap)
painter.setPen(QPen(Qt.black, 1))
painter.setBrush(QBrush(Qt.white))
painter.drawRect(1, 1, size - 2, size - 2)
painter.end()
self.viewport().setCursor(QCursor(pixmap, size / 2, size / 2))
elif self.current_tool in [self.TOOL_SHAPE, self.TOOL_TEXT]:
self.viewport().setCursor(Qt.CrossCursor)
# ==================== 历史记录功能 ====================
def take_snapshot(self):
"""创建当前状态的快照"""
# 只保留最近的20个快照
if self.current_snapshot < len(self.snapshots) - 1:
self.snapshots = self.snapshots[:self.current_snapshot + 1]
# 保存图层状态
snapshot = {
'layers': [],
'current_layer': self.current_layer
}
for layer in self.layers:
snapshot['layers'].append({
'image': layer['image'].copy(),
'visible': layer['visible'],
'opacity': layer['opacity']
})
self.snapshots.append(snapshot)
self.current_snapshot = len(self.snapshots) - 1
return True
def restore_snapshot(self, index=None):
"""恢复指定快照"""
if index is None:
index = self.current_snapshot
if 0 <= index < len(self.snapshots):
snapshot = self.snapshots[index]
# 恢复图层状态
self.layers = []
for layer_data in snapshot['layers']:
self.layers.append({
'image': layer_data['image'].copy(),
'visible': layer_data['visible'],
'opacity': layer_data['opacity'],
'locked': False,
'name': f"Layer {len(self.layers)}",
'item': None
})
self.current_layer = snapshot['current_layer']
self._render_layers()
self.current_snapshot = index
return True
return False
def undo(self):
"""撤销上一步操作"""
if self.current_snapshot > 0:
self.restore_snapshot(self.current_snapshot - 1)
return True
return False
def redo(self):
"""重做上一步操作"""
if self.current_snapshot < len(self.snapshots) - 1:
self.restore_snapshot(self.current_snapshot + 1)
return True
return False
# ==================== 绘图功能 ====================
def start_drawing(self, scene_pos):
"""开始绘图操作"""
if self.current_tool in [self.TOOL_BRUSH, self.TOOL_ERASER]:
self.drawing_active = True
self.drawing_start = scene_pos
self.drawing_path = QPainterPath()
self.drawing_path.moveTo(self.map_to_image(scene_pos))
if self.current_tool == self.TOOL_BRUSH:
pen = QPen(self.tool_settings['brush_color'], self.tool_settings['brush_size'])
pen.setCapStyle(Qt.RoundCap)
pen.setJoinStyle(Qt.RoundJoin)
else: # 橡皮擦
pen = QPen(QColor(255, 255, 255, 255), self.tool_settings['eraser_size'])
pen.setCapStyle(Qt.RoundCap)
if self.tool_settings['antialias']:
pen.setCosmetic(True)
self.drawing_item = QGraphicsPathItem()
self.drawing_item.setPen(pen)
self.drawing_item.setPath(self.drawing_path)
self.drawing_item.setOpacity(self.tool_settings['opacity'])
self.scene.addItem(self.drawing_item)
elif self.current_tool == self.TOOL_SHAPE:
self.drawing_active = True
self.drawing_start = scene_pos
self.drawing_item = self._create_shape(scene_pos, scene_pos)
if self.drawing_item:
self.scene.addItem(self.drawing_item)
elif self.current_tool == self.TOOL_TEXT:
# 文本输入通过专用方法处理
pass
def continue_drawing(self, scene_pos):
"""继续绘图操作"""
if not self.drawing_active:
return
if self.current_tool in [self.TOOL_BRUSH, self.TOOL_ERASER]:
# 添加路径点
new_point = self.map_to_image(scene_pos)
self.drawing_path.lineTo(new_point)
self.drawing_item.setPath(self.drawing_path)
elif self.current_tool == self.TOOL_SHAPE:
# 更新形状
if self.drawing_item:
self.scene.removeItem(self.drawing_item)
self.drawing_item = self._create_shape(self.drawing_start, scene_pos)
if self.drawing_item:
self.scene.addItem(self.drawing_item)
def stop_drawing(self):
"""完成绘图操作并应用到图层"""
if not self.drawing_active:
return
if self.drawing_item:
# 创建绘制到图层的画家
painter = QPainter(self.layers[self.current_layer]['image'])
painter.setRenderHint(QPainter.Antialiasing, self.tool_settings['antialias'])
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
painter.setOpacity(self.tool_settings['opacity'])
# 从绘图项获取路径
path = self.drawing_item.path()
# 根据工具应用不同绘制
if self.current_tool == self.TOOL_BRUSH:
painter.setPen(QPen(
self.tool_settings['brush_color'],
self.tool_settings['brush_size'],
Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin
))
painter.setBrush(Qt.NoBrush)
painter.drawPath(path)
elif self.current_tool == self.TOOL_ERASER:
# 使用橡皮擦
painter.setCompositionMode(QPainter.CompositionMode_Clear)
painter.setPen(QPen(
Qt.transparent,
self.tool_settings['eraser_size'],
Qt.SolidLine, Qt.RoundCap
))
painter.drawPath(path)
这个代码吗给全
最新发布