图片编辑器

tech2025-04-08  8

filename:gui.py

#!/usr/bin/env python # -*- coding:utf-8 -*- import sys import numpy as np import algorithms as alg import os from typing import Optional from PyQt5.QtWidgets import ( QApplication, QMainWindow, qApp, QGraphicsScene, QGraphicsView, QGraphicsItem, QListWidget, QHBoxLayout, QWidget, QStyleOptionGraphicsItem, QMessageBox, QInputDialog, QFileDialog) from PyQt5.QtGui import QPainter, QMouseEvent, QColor, QImage, QPen from PyQt5.QtCore import QRectF, Qt, QDir class MyCanvas(QGraphicsView): def __init__(self, *args): super().__init__(*args) self.main_window = None self.list_widget = None self.item_dict = {} self.selected_id = '' self.status = '' self.temp_id = '' self.temp_item = None self.setStyleSheet("padding: 0px; border: 0px;") self.startX = self.startY = 0 self.oldX = self.oldY = 0 def insert_image(self, item_id): fileName = QFileDialog.getOpenFileName(self, "打开图片", QDir.currentPath())[0] if fileName: image = QImage(fileName) if image.isNull(): return item = MyItem(item_id, "image", [[0, 0], [600, 600]]) item.text = fileName self.scene().addItem(item) self.item_dict[item_id] = item self.list_widget.addItem(item_id) self.finish_draw() def start_draw_text(self, item_id): self.status = 'text' self.temp_id = item_id def start_draw_line(self, item_id): self.status = 'line' self.temp_id = item_id def start_translate(self): self.status = 'translate' self.temp_id = '' def start_rotate(self): self.status = 'rotate' self.temp_id = '' def start_scale(self): self.status = 'scale' self.temp_id = '' def finish_draw(self): self.temp_id = self.main_window.get_id() def clear_selection(self): if self.selected_id != '': self.item_dict[self.selected_id].selected = False self.selected_id = '' def selection_changed(self, selected): self.main_window.statusBar().showMessage('图元选择: %s' % selected) if self.selected_id != '': self.item_dict[self.selected_id].selected = False self.item_dict[self.selected_id].update() self.selected_id = selected self.item_dict[selected].selected = True self.item_dict[selected].update() self.status = '' self.updateScene([self.sceneRect()]) def mousePressEvent(self, event: QMouseEvent) -> None: pos = self.mapToScene(event.localPos().toPoint()) x = int(pos.x()) y = int(pos.y()) self.startX = x self.startY = y self.oldX = self.oldY = 0 if self.status == 'line': self.temp_item = MyItem(self.temp_id, self.status, [[x, y], [x + 1, y + 1]]) self.scene().addItem(self.temp_item) elif self.status == 'text': self.temp_item = MyItem(self.temp_id, self.status, [[x, y], [x + 1, y + 1]]) self.scene().addItem(self.temp_item) elif self.status == 'translate' or self.status == 'rotate' \ or self.status == 'scale': if self.selected_id != '': item = self.item_dict[self.selected_id] self.temp_p_list = item.p_list boundingRect = item.boundingRect() self.cx = boundingRect.center().x() self.cy = boundingRect.center().y() self.updateScene([self.sceneRect()]) super().mousePressEvent(event) def mouseMoveEvent(self, event: QMouseEvent) -> None: pos = self.mapToScene(event.localPos().toPoint()) x = int(pos.x()) y = int(pos.y()) if self.status == 'translate' and self.selected_id != '': item = self.item_dict[self.selected_id] if self.oldX and self.oldY: item.p_list = alg.translate(item.p_list, x - self.oldX, y - self.oldY) self.oldX = x self.oldY = y elif self.status == 'rotate' and self.selected_id != '': item = self.item_dict[self.selected_id] #if item.item_type != 'text': v0 = [self.startX - self.cx, self.startY - self.cy] v1 = [x - self.cx, y - self.cy] r = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1)) r = np.degrees(r) # print(r) if item.item_type == "line": item.p_list = alg.rotate(self.temp_p_list, self.cx, self.cy, r) else: item.rotation = r elif self.status == 'scale' and self.selected_id != '': item = self.item_dict[self.selected_id] x0 = abs(self.startX - self.cx) y0 = abs(self.startY - self.cy) dx = abs(x - self.cx) dy = abs(y - self.cy) if x0 != 0 and y0 != 0: f = (dy + dx) / (y0 + x0) # print(f) item.p_list = alg.scale(self.temp_p_list, self.cx, self.cy, f) elif self.status == 'line': self.temp_item.p_list[1] = [x, y] elif self.status == 'text': self.temp_item.p_list[1] = [x, y] self.updateScene([self.sceneRect()]) super().mouseMoveEvent(event) def mouseReleaseEvent(self, event: QMouseEvent) -> None: if self.status == 'line': self.item_dict[self.temp_id] = self.temp_item self.list_widget.addItem(self.temp_id) self.finish_draw() elif self.status == 'text': text, ok = QInputDialog.getText(self, '提示', '请输入文字:') if ok: self.temp_item.text = text [x0, y0] = self.temp_item.p_list[0] [x1, y1] = self.temp_item.p_list[1] min_font_size = 50 min_height = min_font_size min_width = min_font_size * len(text) if x1 - x0 < min_width: self.temp_item.p_list[1][0] = x0 + min_width if y1 - y0 < min_height: self.temp_item.p_list[1][1] = y0 + min_height self.item_dict[self.temp_id] = self.temp_item self.list_widget.addItem(self.temp_id) self.finish_draw() super().mouseReleaseEvent(event) class MyItem(QGraphicsItem): """ 自定义图元类,继承自QGraphicsItem """ def __init__(self, item_id: str, item_type: str, p_list: list, parent: QGraphicsItem = None): """ :param item_id: 图元ID :param item_type: 图元类型 :param p_list: 图元参数 :param parent: """ super().__init__(parent) self.id = item_id self.item_type = item_type self.p_list = p_list self.rotation = 0 self.selected = False self.text = "" rect = self.mapToScene(self.boundingRect()).boundingRect(); self.center = rect.center() self.pen = QPen(Qt.red, 5) def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: Optional[QWidget] = ...) -> None: painter.setPen(self.pen); x0, y0 = self.p_list[0] x1, y1 = self.p_list[1] if self.rotation != 0: center = QRectF(x0, y0, x1 - x0, y1 - y0).center() painter.translate(center) painter.rotate(self.rotation) painter.translate(-center) if self.item_type == 'line': painter.drawLine(x0, y0, x1, y1) elif self.item_type == 'text': x = min(x0, x1) y = min(y0, y1) w = max(x0, x1) - x h = max(y0, y1) - y font = painter.font() font.setPixelSize(min(w, h)) painter.setFont(font) painter.drawText(QRectF(x, y, w, h), Qt.AlignCenter, self.text) elif self.item_type == 'image': image = QImage(self.text) painter.drawImage(QRectF(x0, y0, x1 - x0, y1 - y0), image) if self.selected: painter.setPen(QColor(255, 0, 0)) painter.drawRect(self.boundingRect()) def boundingRect(self) -> QRectF: x0, y0 = self.p_list[0] x1, y1 = self.p_list[1] x = min(x0, x1) y = min(y0, y1) w = max(x0, x1) - x h = max(y0, y1) - y return QRectF(x - 1, y - 1, w + 2, h + 2) class MainWindow(QMainWindow): """ 主窗口类 """ def __init__(self): super().__init__() self.item_cnt = 0 # 使用QListWidget来记录已有的图元,并用于选择图元。注:这是图元选择的简单实现方法,更好的实现是在画布中直接用鼠标选择图元 self.list_widget = QListWidget(self) self.list_widget.setMinimumWidth(200) # 使用QGraphicsView作为画布 self.scene = QGraphicsScene(self) self.scene.setSceneRect(0, 0, 600, 600) self.canvas_widget = MyCanvas(self.scene, self) self.canvas_widget.setFixedSize(600, 600) self.canvas_widget.main_window = self self.canvas_widget.list_widget = self.list_widget # 设置菜单栏 menubar = self.menuBar() file_menu = menubar.addMenu('文件') export_image_act = file_menu.addAction('导出图片') exit_act = file_menu.addAction('退出') draw_menu = menubar.addMenu('插入') image_act = draw_menu.addAction('图片') text_act = draw_menu.addAction('文字') line_act = draw_menu.addAction('线段') edit_menu = menubar.addMenu('编辑') translate_act = edit_menu.addAction('平移') rotate_act = edit_menu.addAction('旋转') scale_act = edit_menu.addAction('缩放') # 连接信号和槽函数 exit_act.triggered.connect(qApp.quit) export_image_act.triggered.connect(self.export_image_action) image_act.triggered.connect(self.image_action) text_act.triggered.connect(self.text_action) line_act.triggered.connect(self.line_action) translate_act.triggered.connect(self.translate_action) rotate_act.triggered.connect(self.rotate_action) scale_act.triggered.connect(self.scale_action) self.list_widget.currentTextChanged.connect(self.canvas_widget.selection_changed) # 设置主窗口的布局 self.hbox_layout = QHBoxLayout() self.hbox_layout.addWidget(self.canvas_widget) self.hbox_layout.addWidget(self.list_widget, stretch=1) self.central_widget = QWidget() self.central_widget.setLayout(self.hbox_layout) self.setCentralWidget(self.central_widget) self.statusBar().showMessage('空闲') self.resize(600, 600) self.setWindowTitle('图片编辑器') def get_id(self): _id = str(self.item_cnt) self.item_cnt += 1 return _id def export_image_action(self): rect = QRectF(0, 0, 600, 600) image = QImage(600, 600, QImage.Format_RGB32) image.fill(Qt.GlobalColor.white) painter = QPainter(image) self.scene.render(painter, rect, rect) painter.end() image.save("export.jpg", format = 'jpeg') QMessageBox.information(self, "提示", "保存成功!", QMessageBox.Yes) def image_action(self): self.canvas_widget.insert_image(self.get_id()) self.statusBar().showMessage('插入图片') self.list_widget.clearSelection() self.canvas_widget.clear_selection() def text_action(self): self.canvas_widget.start_draw_text(self.get_id()) self.statusBar().showMessage('插入文字') self.list_widget.clearSelection() self.canvas_widget.clear_selection() def line_action(self): self.canvas_widget.start_draw_line(self.get_id()) self.statusBar().showMessage('插入线段') self.list_widget.clearSelection() self.canvas_widget.clear_selection() def check_selected(self): if self.canvas_widget.selected_id == '': QMessageBox.information(self, "错误", "请选择一个图元!", QMessageBox.Yes) return False else: return True def translate_action(self): if self.check_selected(): self.canvas_widget.start_translate() self.statusBar().showMessage('平移') def rotate_action(self): if self.check_selected(): self.canvas_widget.start_rotate() self.statusBar().showMessage('旋转') def scale_action(self): if self.check_selected(): self.canvas_widget.start_scale() self.statusBar().showMessage('缩放') def restart_program(): python=sys.executable os.execl(python, python, * sys.argv) if __name__ == '__main__': app = QApplication(sys.argv) mw = MainWindow() mw.show() e = app.exec_() if e == 773: restart_program() sys.exit(e)

filename:algorithms.py

#!/usr/bin/env python # -*- coding:utf-8 -*- import math def translate(p_list, dx, dy): """平移 :param p_list: (list of list of int: [[x0, y0], [x1, y1], [x2, y2], ...]) 图元参数 :param dx: (int) 水平方向平移量 :param dy: (int) 垂直方向平移量 :return: (list of list of int: [[x_0, y_0], [x_1, y_1], [x_2, y_2], ...]) 变换后的图元参数 """ result = [] for x,y in p_list: x = x + dx y = y + dy result.append([x, y]) return result def rotate(p_list, x, y, r): """旋转 :param p_list: (list of list of int: [[x0, y0], [x1, y1], [x2, y2], ...]) 图元参数 :param x: (int) 旋转中心x坐标 :param y: (int) 旋转中心y坐标 :param r: (int) 顺时针旋转角度(°) :return: (list of list of int: [[x_0, y_0], [x_1, y_1], [x_2, y_2], ...]) 变换后的图元参数 """ result = [] r = math.radians(r) sinr = math.sin(r) cosr = math.cos(r) for xi, yi in p_list: dx = xi - x dy = yi - y result.append([int(x + dx * cosr - dy * sinr), int(y + dx * sinr + dy * cosr)]) return result def scale(p_list, x, y, s): """缩放变换 :param p_list: (list of list of int: [[x0, y0], [x1, y1], [x2, y2], ...]) 图元参数 :param x: (int) 缩放中心x坐标 :param y: (int) 缩放中心y坐标 :param s: (float) 缩放倍数 :return: (list of list of int: [[x_0, y_0], [x_1, y_1], [x_2, y_2], ...]) 变换后的图元参数 """ result = [] for xi, yi in p_list: dx = xi - x dy = yi - y result.append([int(x + s * dx), int(y + s * dy)]) return result
最新回复(0)