. main.py
Python
复制
# 导入必要的模块
import sys
from PyQt5.QtWidgets import QApplication
from image_editor import ImageEditor
def main():
"""主函数,创建并启动应用程序"""
app = QApplication(sys.argv) # 创建应用程序实例
editor = ImageEditor() # 创建图像编辑器实例
editor.show() # 显示主窗口
sys.exit(app.exec_()) # 运行应用程序
if __name__ == "__main__":
main() # 程序入口点
2. image_editor.py
Python
复制
# 导入必要的模块
import cv2
import numpy as np
import os
from PyQt5.QtWidgets import QMainWindow, QLabel, QFileDialog, QMessageBox
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import Qt
from tools.brightness_contrast import BrightnessContrastTool
from tools.blur import BlurTool
from tools.sharpen import SharpenTool
from tools.crop import CropTool
from tools.rotate import RotateTool
from tools.scale import ScaleTool
from tools.flip import FlipTool
from tools.text import TextTool
from tools.sticker import StickerTool
class ImageEditor(QMainWindow):
"""图像编辑器主类"""
def __init__(self):
super().__init__()
self.setWindowTitle("图像编辑器") # 设置窗口标题
self.setGeometry(100, 100, 800, 600) # 设置窗口位置和大小
self.image = None # 当前图像
self.history = [] # 操作历史栈
self.future = [] # 未来操作栈
self.tools = {} # 初始化工具字典
self.init_tools() # 初始化工具
self.init_ui() # 初始化用户界面
def init_ui(self):
"""初始化用户界面"""
# 创建显示图像的标签,居中对齐
self.canvas = QLabel(self)
self.canvas.setAlignment(Qt.AlignCenter)
# 设置背景颜色为透明
self.canvas.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
# 将标签设为中心部件
self.setCentralWidget(self.canvas)
# 创建菜单栏
self.create_menu()
def create_menu(self):
"""创建菜单栏"""
# 获取菜单栏
menu_bar = self.menuBar()
# 创建文件菜单
file_menu = menu_bar.addMenu("文件")
# 创建并添加打开菜单项
open_action = QAction("打开", self)
open_action.triggered.connect(self.open_image)
file_menu.addAction(open_action)
# 创建并添加保存菜单项
save_action = QAction("保存", self)
save_action.triggered.connect(self.save_image)
file_menu.addAction(save_action)
# 创建编辑菜单
edit_menu = menu_bar.addMenu("编辑")
# 创建并添加撤销菜单项
undo_action = QAction("撤销", self)
undo_action.triggered.connect(self.undo)
edit_menu.addAction(undo_action)
# 创建并添加重做菜单项
redo_action = QAction("重做", self)
redo_action.triggered.connect(self.redo)
edit_menu.addAction(redo_action)
# 创建并添加重置菜单项
reset_action = QAction("重置", self)
reset_action.triggered.connect(self.reset)
edit_menu.addAction(reset_action)
# 创建调整菜单
adjust_menu = menu_bar.addMenu("调整")
# 创建并添加亮度对比度菜单项
brightness_contrast_action = QAction("亮度对比度", self)
brightness_contrast_action.triggered.connect(self.show_brightness_contrast_dialog)
adjust_menu.addAction(brightness_contrast_action)
# 创建并添加模糊菜单项
blur_action = QAction("模糊", self)
blur_action.triggered.connect(self.show_blur_dialog)
adjust_menu.addAction(blur_action)
# 创建并添加锐化菜单项
sharpen_action = QAction("锐化", self)
sharpen_action.triggered.connect(self.show_sharpen_dialog)
adjust_menu.addAction(sharpen_action)
# 创建操作菜单
operation_menu = menu_bar.addMenu("操作")
# 创建并添加裁剪菜单项
crop_action = QAction("裁剪", self)
crop_action.triggered.connect(self.show_crop_dialog)
operation_menu.addAction(crop_action)
# 创建并添加旋转菜单项
rotate_action = QAction("旋转", self)
rotate_action.triggered.connect(self.show_rotate_dialog)
operation_menu.addAction(rotate_action)
# 创建并添加缩放菜单项
scale_action = QAction("缩放", self)
scale_action.triggered.connect(self.show_scale_dialog)
operation_menu.addAction(scale_action)
# 创建并添加翻转菜单项
flip_action = QAction("翻转", self)
flip_action.triggered.connect(self.show_flip_dialog)
operation_menu.addAction(flip_action)
# 创建添加菜单
add_menu = menu_bar.addMenu("添加")
# 创建并添加文字菜单项
text_action = QAction("文字", self)
text_action.triggered.connect(self.show_text_dialog)
add_menu.addAction(text_action)
# 创建并添加贴纸菜单项
sticker_action = QAction("贴纸", self)
sticker_action.triggered.connect(self.show_sticker_dialog)
add_menu.addAction(sticker_action)
def init_tools(self):
"""初始化工具"""
self.tools = {
"brightness_contrast": BrightnessContrastTool(self),
"blur": BlurTool(self),
"sharpen": SharpenTool(self),
"crop": CropTool(self),
"rotate": RotateTool(self),
"scale": ScaleTool(self),
"flip": FlipTool(self),
"text": TextTool(self),
"sticker": StickerTool(self)
}
def show_brightness_contrast_dialog(self):
"""显示亮度对比度对话框"""
self.tools["brightness_contrast"].show_dialog()
def show_blur_dialog(self):
"""显示模糊对话框"""
self.tools["blur"].show_dialog()
def show_sharpen_dialog(self):
"""显示锐化对话框"""
self.tools["sharpen"].show_dialog()
def show_crop_dialog(self):
"""显示裁剪对话框"""
self.tools["crop"].show_dialog()
def show_rotate_dialog(self):
"""显示旋转对话框"""
self.tools["rotate"].show_dialog()
def show_scale_dialog(self):
"""显示缩放对话框"""
self.tools["scale"].show_dialog()
def show_flip_dialog(self):
"""显示翻转对话框"""
self.tools["flip"].show_dialog()
def show_text_dialog(self):
"""显示文字对话框"""
self.tools["text"].show_dialog()
def show_sticker_dialog(self):
"""显示贴纸对话框"""
self.tools["sticker"].show_dialog()
def open_image(self):
"""打开图像文件"""
# 打开文件对话框,获取选定的文件路径
file_path, _ = QFileDialog.getOpenFileName(
self, "打开图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)"
)
if file_path:
# 检查文件是否存在
if not os.path.exists(file_path):
QMessageBox.warning(self, "错误", "文件不存在!")
return
# 如果图像未初始化,创建一个透明背景
if self.image is None:
self.image = np.ones((800, 600, 4), dtype=np.uint8) * 255
self.image[:, :, 3] = 0 # 设置透明度通道为 0(完全透明)
# 读取图像
img = cv2.imread(file_path)
if img is None:
QMessageBox.warning(self, "错误", "无法读取图像文件!")
return
# 如果是灰度图像,转换为彩色
if len(img.shape) == 2:
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
# 如果图像有 alpha 通道,合并到当前图像
if img.shape[2] == 4:
# 将新图像融合到透明背景
self.image = cv2.add(self.image, img)
else:
# 否则,添加 alpha 通道并融合
img_with_alpha = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
self.image = cv2.add(self.image, img_with_alpha)
# 将当前图像压入历史栈
self.push_history()
# 更新显示
self.update_canvas()
def save_image(self):
"""保存图像"""
if self.image is None:
return # 如果没有图像,则返回
# 打开保存文件对话框,获取保存的文件路径
file_path, _ = QFileDialog.getSaveFileName(
self, "保存图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)"
)
if file_path:
# 保存图像
cv2.imwrite(file_path, self.image)
def push_history(self):
"""将当前图像状态压入历史栈"""
if self.image is not None:
# 复制当前图像并压入历史栈
self.history.append(self.image.copy())
# 清空未来栈
self.future = []
def undo(self):
"""撤销操作"""
if len(self.history) > 1: # 如果历史栈中有多个状态
# 将当前图像压入未来栈
self.future.append(self.image.copy())
# 弹出历史栈的顶部
self.image = self.history.pop()
# 更新显示
self.update_canvas()
def redo(self):
"""重做操作"""
if len(self.future) > 0: # 如果未来栈中有状态
# 将当前图像压入历史栈
self.history.append(self.image.copy())
# 弹出未来栈的顶部
self.image = self.future.pop()
# 更新显示
self.update_canvas()
def reset(self):
"""重置图像"""
if len(self.history) > 0: # 如果历史栈中有初始状态
# 将当前图像压入未来栈
if len(self.history) > 1:
self.future.append(self.image.copy())
# 重置历史栈为初始状态
self.history = self.history[:1]
# 重置当前图像为初始状态
self.image = self.history[0].copy()
# 更新显示
self.update_canvas()
def update_canvas(self):
"""更新画布显示"""
if self.image is not None:
# 将图像从 BGRA 转换为 ARGB(Qt 使用 ARGB 格式)
argb_image = cv2.cvtColor(self.image, cv2.COLOR_BGRA2RGBA)
# 获取图像高度和宽度
height, width, channel = argb_image.shape
# 计算每行的字节数
bytes_per_line = channel * width
# 创建 QImage 对象
q_image = QImage(
argb_image.data, width, height, bytes_per_line, QImage.Format_ARGB32
)
# 创建 QPixmap 对象
pixmap = QPixmap.fromImage(q_image)
# 设置标签的图片
self.canvas.setPixmap(pixmap)
# 调整标签大小以适应图片
self.canvas.setFixedSize(width, height)
3. tools/__init__.py
Python
复制
# 这个文件用于将 tools 包标记为 Python 包
4. tools/brightness_contrast.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QSlider, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit
from PyQt5.QtCore import Qt
class BrightnessContrastDialog(QDialog):
"""亮度对比度对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("亮度对比度调节")
self.brightness_value = 0
self.contrast_value = 0
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 创建亮度滑动条,水平方向
self.slider_brightness = QSlider(Qt.Horizontal)
self.slider_brightness.setRange(-100, 100)
self.slider_brightness.setValue(0)
self.label_brightness = QLabel("亮度: 0")
# 创建对比度滑动条,水平方向
self.slider_contrast = QSlider(Qt.Horizontal)
self.slider_contrast.setRange(-100, 100)
self.slider_contrast.setValue(0)
self.label_contrast = QLabel("对比度: 0")
# 创建用于输入亮度值的文本框
self.edit_brightness = QLineEdit("0")
self.edit_brightness.setFixedWidth(50)
# 创建用于输入对比度值的文本框
self.edit_contrast = QLineEdit("0")
self.edit_contrast.setFixedWidth(50)
# 将滑动条的值改变信号连接到处理函数
self.slider_brightness.valueChanged.connect(self.on_slider_brightness)
# 将文本框的文本改变信号连接到处理函数
self.edit_brightness.textChanged.connect(self.on_edit_brightness)
# 将滑动条的值改变信号连接到处理函数
self.slider_contrast.valueChanged.connect(self.on_slider_contrast)
# 将文本框的文本改变信号连接到处理函数
self.edit_contrast.textChanged.connect(self.on_edit_contrast)
# 创建垂直布局管理器
layout = QVBoxLayout()
# 添加亮度标签
layout.addWidget(QLabel("亮度:"))
# 创建水平布局管理器
h_layout = QHBoxLayout()
h_layout.addWidget(self.slider_brightness)
h_layout.addWidget(self.label_brightness)
h_layout.addWidget(self.edit_brightness)
layout.addLayout(h_layout)
# 添加对比度标签
layout.addWidget(QLabel("对比度:"))
# 创建水平布局管理器
h_layout = QHBoxLayout()
h_layout.addWidget(self.slider_contrast)
h_layout.addWidget(self.label_contrast)
h_layout.addWidget(self.edit_contrast)
layout.addLayout(h_layout)
# 创建确定按钮
btn_ok = QPushButton("确定")
btn_ok.clicked.connect(self.accept)
# 创建取消按钮
btn_cancel = QPushButton("取消")
btn_cancel.clicked.connect(self.reject)
# 创建按钮布局管理器
btn_layout = QHBoxLayout()
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
# 将按钮布局添加到主布局
layout.addLayout(btn_layout)
# 设置对话框的布局
self.setLayout(layout)
def on_slider_brightness(self, value):
"""处理亮度滑动条值改变事件"""
self.brightness_value = value
self.label_brightness.setText(f"亮度: {value}")
self.edit_brightness.setText(str(value))
def on_slider_contrast(self, value):
"""处理对比度滑动条值改变事件"""
self.contrast_value = value
self.label_contrast.setText(f"对比度: {value}")
self.edit_contrast.setText(str(value))
def on_edit_brightness(self, text):
"""处理亮度文本框文本改变事件"""
try:
value = int(text)
if value < -100:
value = -100
elif value > 100:
value = 100
self.brightness_value = value
self.slider_brightness.setValue(value)
self.label_brightness.setText(f"亮度: {value}")
except ValueError:
pass
def on_edit_contrast(self, text):
"""处理对比度文本框文本改变事件"""
try:
value = int(text)
if value < -100:
value = -100
elif value > 100:
value = 100
self.contrast_value = value
self.slider_contrast.setValue(value)
self.label_contrast.setText(f"对比度: {value}")
except ValueError:
pass
def get_values(self):
"""获取当前的亮度和对比度值"""
return self.brightness_value, self.contrast_value
def accept(self):
"""处理确定按钮点击事件"""
# 获取亮度和对比度值
brightness, contrast = self.get_values()
# 调整图像的亮度和对比度
self.editor.image = cv2.convertScaleAbs(self.editor.image, alpha=1 + contrast/100.0, beta=brightness)
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
super().accept()
class BrightnessContrastTool:
"""亮度对比度工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = BrightnessContrastDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.show()
5. tools/blur.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QSlider, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit
from PyQt5.QtCore import Qt
class BlurDialog(QDialog):
"""模糊对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("模糊滤镜")
self.blur_value = 1
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 创建模糊滑动条,水平方向
self.slider_blur = QSlider(Qt.Horizontal)
self.slider_blur.setRange(1, 25)
self.slider_blur.setValue(1)
self.label_blur = QLabel("模糊半径: 1")
# 创建用于输入模糊值的文本框
self.edit_blur = QLineEdit("1")
self.edit_blur.setFixedWidth(50)
# 将滑动条的值改变信号连接到处理函数
self.slider_blur.valueChanged.connect(self.on_slider_blur)
# 将文本框的文本改变信号连接到处理函数
self.edit_blur.textChanged.connect(self.on_edit_blur)
# 创建垂直布局管理器
layout = QVBoxLayout()
# 添加模糊标签
layout.addWidget(QLabel("模糊半径:"))
# 创建水平布局管理器
h_layout = QHBoxLayout()
h_layout.addWidget(self.slider_blur)
h_layout.addWidget(self.label_blur)
h_layout.addWidget(self.edit_blur)
layout.addLayout(h_layout)
# 创建确定按钮
btn_ok = QPushButton("确定")
btn_ok.clicked.connect(self.accept)
# 创建取消按钮
btn_cancel = QPushButton("取消")
btn_cancel.clicked.connect(self.reject)
# 创建按钮布局管理器
btn_layout = QHBoxLayout()
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
# 将按钮布局添加到主布局
layout.addLayout(btn_layout)
# 设置对话框的布局
self.setLayout(layout)
def on_slider_blur(self, value):
"""处理模糊滑动条值改变事件"""
self.blur_value = value
self.label_blur.setText(f"模糊半径: {value}")
self.edit_blur.setText(str(value))
def on_edit_blur(self, text):
"""处理模糊文本框文本改变事件"""
try:
value = int(text)
if value < 1:
value = 1
elif value > 25:
value = 25
self.blur_value = value
self.slider_blur.setValue(value)
self.label_blur.setText(f"模糊半径: {value}")
except ValueError:
pass
def get_value(self):
"""获取当前的模糊值"""
return self.blur_value
def accept(self):
"""处理确定按钮点击事件"""
# 获取模糊值
blur_value = self.get_value()
# 创建模糊核
kernel_size = (blur_value * 2 + 1, blur_value * 2 + 1)
# 应用模糊滤镜
self.editor.image = cv2.GaussianBlur(self.editor.image, kernel_size, 0)
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
super().accept()
class BlurTool:
"""模糊工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = BlurDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.show()
6. tools/sharpen.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QSlider, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit
from PyQt5.QtCore import Qt
class SharpenDialog(QDialog):
"""锐化对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("锐化滤镜")
self.sharpen_value = 1
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 创建锐化滑动条,水平方向
self.slider_sharpen = QSlider(Qt.Horizontal)
self.slider_sharpen.setRange(1, 10)
self.slider_sharpen.setValue(1)
self.label_sharpen = QLabel("锐化强度: 1")
# 创建用于输入锐化值的文本框
self.edit_sharpen = QLineEdit("1")
self.edit_sharpen.setFixedWidth(50)
# 将滑动条的值改变信号连接到处理函数
self.slider_sharpen.valueChanged.connect(self.on_slider_sharpen)
# 将文本框的文本改变信号连接到处理函数
self.edit_sharpen.textChanged.connect(self.on_edit_sharpen)
# 创建垂直布局管理器
layout = QVBoxLayout()
# 添加锐化标签
layout.addWidget(QLabel("锐化强度:"))
# 创建水平布局管理器
h_layout = QHBoxLayout()
h_layout.addWidget(self.slider_sharpen)
h_layout.addWidget(self.label_sharpen)
h_layout.addWidget(self.edit_sharpen)
layout.addLayout(h_layout)
# 创建确定按钮
btn_ok = QPushButton("确定")
btn_ok.clicked.connect(self.accept)
# 创建取消按钮
btn_cancel = QPushButton("取消")
btn_cancel.clicked.connect(self.reject)
# 创建按钮布局管理器
btn_layout = QHBoxLayout()
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
# 将按钮布局添加到主布局
layout.addLayout(btn_layout)
# 设置对话框的布局
self.setLayout(layout)
def on_slider_sharpen(self, value):
"""处理锐化滑动条值改变事件"""
self.sharpen_value = value
self.label_sharpen.setText(f"锐化强度: {value}")
self.edit_sharpen.setText(str(value))
def on_edit_sharpen(self, text):
"""处理锐化文本框文本改变事件"""
try:
value = int(text)
if value < 1:
value = 1
elif value > 10:
value = 10
self.sharpen_value = value
self.slider_sharpen.setValue(value)
self.label_sharpen.setText(f"锐化强度: {value}")
except ValueError:
pass
def get_value(self):
"""获取当前的锐化值"""
return self.sharpen_value
def accept(self):
"""处理确定按钮点击事件"""
# 获取锐化值
sharpen_value = self.get_value()
# 创建锐化核
kernel = np.array([[-1, -1, -1],
[-1, 5 + sharpen_value, -1],
[-1, -1, -1]], dtype=np.float32)
# 应用锐化滤镜
self.editor.image = cv2.filter2D(self.editor.image, -1, kernel)
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
super().accept()
class SharpenTool:
"""锐化工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = SharpenDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.show()
7. tools/crop.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit
from PyQt5.QtCore import Qt
class CropDialog(QDialog):
"""裁剪对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("裁剪图像")
self.x = 0
self.y = 0
self.width = 100
self.height = 100
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 获取图像尺寸
if self.editor.image is not None:
height, width, _ = self.editor.image.shape
self.width = width
self.height = height
# 创建 X 坐标输入框和标签
self.label_x = QLabel("X:")
self.edit_x = QLineEdit("0")
self.edit_x.setFixedWidth(60)
# 创建 Y 坐标输入框和标签
self.label_y = QLabel("Y:")
self.edit_y = QLineEdit("0")
self.edit_y.setFixedWidth(60)
# 创建宽度输入框和标签
self.label_width = QLabel("宽度:")
self.edit_width = QLineEdit(str(self.width))
self.edit_width.setFixedWidth(60)
# 创建高度输入框和标签
self.label_height = QLabel("高度:")
self.edit_height = QLineEdit(str(self.height))
self.edit_height.setFixedWidth(60)
# 创建垂直布局管理器
layout = QVBoxLayout()
# 添加 X 坐标输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_x)
h_layout.addWidget(self.edit_x)
layout.addLayout(h_layout)
# 添加 Y 坐标输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_y)
h_layout.addWidget(self.edit_y)
layout.addLayout(h_layout)
# 添加宽度输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_width)
h_layout.addWidget(self.edit_width)
layout.addLayout(h_layout)
# 添加高度输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_height)
h_layout.addWidget(self.edit_height)
layout.addLayout(h_layout)
# 创建确定按钮
btn_ok = QPushButton("确定")
btn_ok.clicked.connect(self.accept)
# 创建取消按钮
btn_cancel = QPushButton("取消")
btn_cancel.clicked.connect(self.reject)
# 创建按钮布局管理器
btn_layout = QHBoxLayout()
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
# 将按钮布局添加到主布局
layout.addLayout(btn_layout)
# 设置对话框的布局
self.setLayout(layout)
def get_values(self):
"""获取当前的裁剪参数"""
try:
x = int(self.edit_x.text())
y = int(self.edit_y.text())
width = int(self.edit_width.text())
height = int(self.edit_height.text())
return x, y, width, height
except ValueError:
return 0, 0, 100, 100
def accept(self):
"""处理确定按钮点击事件"""
# 获取裁剪参数
x, y, width, height = self.get_values()
# 确保参数在有效范围内
if self.editor.image is not None:
img_height, img_width, _ = self.editor.image.shape
x = max(0, min(x, img_width - 1))
y = max(0, min(y, img_height - 1))
width = max(1, min(width, img_width - x))
height = max(1, min(height, img_height - y))
# 裁剪图像
self.editor.image = self.editor.image[y:y+height, x:x+width]
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
super().accept()
class CropTool:
"""裁剪工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = CropDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.init_ui() # 重新初始化 UI 以获取最新图像尺寸
self.dialog.show()
8. tools/rotate.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QSlider, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit
from PyQt5.QtCore import Qt
class RotateDialog(QDialog):
"""旋转对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("旋转图像")
self.rotate_value = 0
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 创建旋转滑动条,水平方向
self.slider_rotate = QSlider(Qt.Horizontal)
self.slider_rotate.setRange(-180, 180)
self.slider_rotate.setValue(0)
self.label_rotate = QLabel("旋转角度: 0")
# 创建用于输入旋转角度的文本框
self.edit_rotate = QLineEdit("0")
self.edit_rotate.setFixedWidth(50)
# 将滑动条的值改变信号连接到处理函数
self.slider_rotate.valueChanged.connect(self.on_slider_rotate)
# 将文本框的文本改变信号连接到处理函数
self.edit_rotate.textChanged.connect(self.on_edit_rotate)
# 创建垂直布局管理器
layout = QVBoxLayout()
# 添加旋转标签
layout.addWidget(QLabel("旋转角度:"))
# 创建水平布局管理器
h_layout = QHBoxLayout()
h_layout.addWidget(self.slider_rotate)
h_layout.addWidget(self.label_rotate)
h_layout.addWidget(self.edit_rotate)
layout.addLayout(h_layout)
# 创建确定按钮
btn_ok = QPushButton("确定")
btn_ok.clicked.connect(self.accept)
# 创建取消按钮
btn_cancel = QPushButton("取消")
btn_cancel.clicked.connect(self.reject)
# 创建按钮布局管理器
btn_layout = QHBoxLayout()
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
# 将按钮布局添加到主布局
layout.addLayout(btn_layout)
# 设置对话框的布局
self.setLayout(layout)
def on_slider_rotate(self, value):
"""处理旋转滑动条值改变事件"""
self.rotate_value = value
self.label_rotate.setText(f"旋转角度: {value}")
self.edit_rotate.setText(str(value))
def on_edit_rotate(self, text):
"""处理旋转文本框文本改变事件"""
try:
value = int(text)
if value < -180:
value = -180
elif value > 180:
value = 180
self.rotate_value = value
self.slider_rotate.setValue(value)
self.label_rotate.setText(f"旋转角度: {value}")
except ValueError:
pass
def get_value(self):
"""获取当前的旋转角度"""
return self.rotate_value
def accept(self):
"""处理确定按钮点击事件"""
# 获取旋转角度
angle = self.get_value()
# 获取图像中心
if self.editor.image is not None:
(h, w) = self.editor.image.shape[:2]
(cX, cY) = (w // 2, h // 2)
# 创建旋转矩阵
M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0)
# 应用旋转
self.editor.image = cv2.warpAffine(self.editor.image, M, (w, h))
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
super().accept()
class RotateTool:
"""旋转工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = RotateDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.show()
9. tools/scale.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QSlider, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit
from PyQt5.QtCore import Qt
class ScaleDialog(QDialog):
"""缩放对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("缩放图像")
self.scale_value = 100
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 创建缩放滑动条,水平方向
self.slider_scale = QSlider(Qt.Horizontal)
self.slider_scale.setRange(10, 300)
self.slider_scale.setValue(100)
self.label_scale = QLabel("缩放比例: 100%")
# 创建用于输入缩放比例的文本框
self.edit_scale = QLineEdit("100")
self.edit_scale.setFixedWidth(50)
# 将滑动条的值改变信号连接到处理函数
self.slider_scale.valueChanged.connect(self.on_slider_scale)
# 将文本框的文本改变信号连接到处理函数
self.edit_scale.textChanged.connect(self.on_edit_scale)
# 创建垂直布局管理器
layout = QVBoxLayout()
# 添加缩放标签
layout.addWidget(QLabel("缩放比例:"))
# 创建水平布局管理器
h_layout = QHBoxLayout()
h_layout.addWidget(self.slider_scale)
h_layout.addWidget(self.label_scale)
h_layout.addWidget(self.edit_scale)
layout.addLayout(h_layout)
# 创建确定按钮
btn_ok = QPushButton("确定")
btn_ok.clicked.connect(self.accept)
# 创建取消按钮
btn_cancel = QPushButton("取消")
btn_cancel.clicked.connect(self.reject)
# 创建按钮布局管理器
btn_layout = QHBoxLayout()
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
# 将按钮布局添加到主布局
layout.addLayout(btn_layout)
# 设置对话框的布局
self.setLayout(layout)
def on_slider_scale(self, value):
"""处理缩放滑动条值改变事件"""
self.scale_value = value
self.label_scale.setText(f"缩放比例: {value}%")
self.edit_scale.setText(str(value))
def on_edit_scale(self, text):
"""处理缩放文本框文本改变事件"""
try:
value = int(text)
if value < 10:
value = 10
elif value > 300:
value = 300
self.scale_value = value
self.slider_scale.setValue(value)
self.label_scale.setText(f"缩放比例: {value}%")
except ValueError:
pass
def get_value(self):
"""获取当前的缩放比例"""
return self.scale_value
def accept(self):
"""处理确定按钮点击事件"""
# 获取缩放比例
scale = self.get_value() / 100.0
# 缩放图像
if self.editor.image is not None:
new_width = int(self.editor.image.shape[1] * scale)
new_height = int(self.editor.image.shape[0] * scale)
self.editor.image = cv2.resize(self.editor.image, (new_width, new_height))
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
super().accept()
class ScaleTool:
"""缩放工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = ScaleDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.show()
10. tools/flip.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QPushButton
class FlipDialog(QDialog):
"""翻转对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("翻转图像")
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 创建按钮布局
btn_layout = QVBoxLayout()
# 创建水平翻转按钮
btn_horizontal = QPushButton("水平翻转")
btn_horizontal.clicked.connect(lambda: self.flip(1))
btn_layout.addWidget(btn_horizontal)
# 创建垂直翻转按钮
btn_vertical = QPushButton("垂直翻转")
btn_vertical.clicked.connect(lambda: self.flip(0))
btn_layout.addWidget(btn_vertical)
# 创建水平+垂直翻转按钮
btn_both = QPushButton("水平+垂直翻转")
btn_both.clicked.connect(lambda: self.flip(-1))
btn_layout.addWidget(btn_both)
# 创建取消按钮
btn_cancel = QPushButton("取消")
btn_cancel.clicked.connect(self.reject)
btn_layout.addWidget(btn_cancel)
# 设置对话框的布局
self.setLayout(btn_layout)
def flip(self, code):
"""执行翻转操作"""
# 翻转图像
if self.editor.image is not None:
self.editor.image = cv2.flip(self.editor.image, code)
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
self.accept()
class FlipTool:
"""翻转工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = FlipDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.show()
11. tools/text.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QFontDialog
from PyQt5.QtCore import Qt
class TextDialog(QDialog):
"""文字对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("添加文字")
self.text = ""
self.x = 50
self.y = 50
self.font = ""
self.color = (255, 255, 255)
self.size = 1
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 创建文字输入框和标签
self.label_text = QLabel("文字:")
self.edit_text = QLineEdit()
# 创建 X 坐标输入框和标签
self.label_x = QLabel("X:")
self.edit_x = QLineEdit("50")
self.edit_x.setFixedWidth(60)
# 创建 Y 坐标输入框和标签
self.label_y = QLabel("Y:")
self.edit_y = QLineEdit("50")
self.edit_y.setFixedWidth(60)
# 创建字体按钮
self.btn_font = QPushButton("选择字体")
self.btn_font.clicked.connect(self.choose_font)
# 创建颜色按钮
self.btn_color = QPushButton("选择颜色")
self.btn_color.clicked.connect(self.choose_color)
# 创建确定和取消按钮
self.btn_ok = QPushButton("确定")
self.btn_ok.clicked.connect(self.accept)
self.btn_cancel = QPushButton("取消")
self.btn_cancel.clicked.connect(self.reject)
# 创建布局
layout = QVBoxLayout()
# 添加文字输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_text)
h_layout.addWidget(self.edit_text)
layout.addLayout(h_layout)
# 添加 X 坐标输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_x)
h_layout.addWidget(self.edit_x)
layout.addLayout(h_layout)
# 添加 Y 坐标输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_y)
h_layout.addWidget(self.edit_y)
layout.addLayout(h_layout)
# 添加字体和颜色选择按钮
h_layout = QHBoxLayout()
h_layout.addWidget(self.btn_font)
h_layout.addWidget(self.btn_color)
layout.addLayout(h_layout)
# 添加确定和取消按钮
h_layout = QHBoxLayout()
h_layout.addWidget(self.btn_ok)
h_layout.addWidget(self.btn_cancel)
layout.addLayout(h_layout)
# 设置对话框的布局
self.setLayout(layout)
def choose_font(self):
"""选择字体"""
font, ok = QFontDialog.getFont()
if ok:
self.font = font.family()
self.size = font.pointSizeF()
def choose_color(self):
"""选择颜色"""
# 这里简化处理,实际应用中应使用 QColorDialog
self.color = (255, 0, 0) # 默认红色
def get_values(self):
"""获取当前的文字参数"""
self.text = self.edit_text.text()
try:
self.x = int(self.edit_x.text())
self.y = int(self.edit_y.text())
except ValueError:
self.x = 50
self.y = 50
return self.text, self.x, self.y, self.font, self.color, self.size
def accept(self):
"""处理确定按钮点击事件"""
# 获取文字参数
text, x, y, font, color, size = self.get_values()
# 添加文字
if self.editor.image is not None and text:
# 设置默认字体
if not font:
font = cv2.FONT_HERSHEY_SIMPLEX
else:
# 尝试匹配字体,如果失败则使用默认字体
try:
font = eval(f"cv2.FONT_{font.upper()}")
except AttributeError:
font = cv2.FONT_HERSHEY_SIMPLEX
# 将颜色从 RGB 转换为 BGR
bgr_color = (color[2], color[1], color[0])
# 添加文字
cv2.putText(self.editor.image, text, (x, y), font, size, bgr_color, 2)
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
super().accept()
class TextTool:
"""文字工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = TextDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.show()
12. tools/sticker.py
Python
复制
# 导入必要的模块
from PyQt5.QtWidgets import QDialog, QFileDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit
from PyQt5.QtCore import Qt
class StickerDialog(QDialog):
"""贴纸对话框类"""
def __init__(self, editor):
super().__init__()
self.editor = editor
self.setWindowTitle("添加贴纸")
self.x = 50
self.y = 50
self.sticker_path = ""
self.init_ui()
def init_ui(self):
"""初始化用户界面"""
# 创建 X 坐标输入框和标签
self.label_x = QLabel("X:")
self.edit_x = QLineEdit("50")
self.edit_x.setFixedWidth(60)
# 创建 Y 坐标输入框和标签
self.label_y = QLabel("Y:")
self.edit_y = QLineEdit("50")
self.edit_y.setFixedWidth(60)
# 创建选择贴纸按钮
self.btn_select = QPushButton("选择贴纸")
self.btn_select.clicked.connect(self.select_sticker)
# 创建确定和取消按钮
self.btn_ok = QPushButton("确定")
self.btn_ok.clicked.connect(self.accept)
self.btn_cancel = QPushButton("取消")
self.btn_cancel.clicked.connect(self.reject)
# 创建布局
layout = QVBoxLayout()
# 添加 X 坐标输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_x)
h_layout.addWidget(self.edit_x)
layout.addLayout(h_layout)
# 添加 Y 坐标输入
h_layout = QHBoxLayout()
h_layout.addWidget(self.label_y)
h_layout.addWidget(self.edit_y)
layout.addLayout(h_layout)
# 添加选择贴纸按钮
layout.addWidget(self.btn_select)
# 添加确定和取消按钮
h_layout = QHBoxLayout()
h_layout.addWidget(self.btn_ok)
h_layout.addWidget(self.btn_cancel)
layout.addLayout(h_layout)
# 设置对话框的布局
self.setLayout(layout)
def select_sticker(self):
"""选择贴纸文件"""
file_path, _ = QFileDialog.getOpenFileName(
self, "选择贴纸", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)"
)
if file_path:
self.sticker_path = file_path
def get_values(self):
"""获取当前的贴纸参数"""
try:
x = int(self.edit_x.text())
y = int(self.edit_y.text())
except ValueError:
x = 50
y = 50
return x, y, self.sticker_path
def accept(self):
"""处理确定按钮点击事件"""
# 获取贴纸参数
x, y, sticker_path = self.get_values()
# 添加贴纸
if self.editor.image is not None and sticker_path:
# 读取贴纸图像
sticker = cv2.imread(sticker_path, cv2.IMREAD_UNCHANGED)
if sticker is not None:
# 获取贴纸尺寸
sticker_height, sticker_width = sticker.shape[:2]
# 获取图像尺寸
img_height, img_width, _ = self.editor.image.shape
# 确保贴纸在图像范围内
if x + sticker_width > img_width:
x = img_width - sticker_width
if y + sticker_height > img_height:
y = img_height - sticker_height
# 添加贴纸
for i in range(sticker_height):
for j in range(sticker_width):
if sticker[i, j, 3] != 0: # 检查 alpha 通道
self.editor.image[y + i, x + j] = sticker[i, j]
# 将当前图像压入历史栈
self.editor.push_history()
# 更新显示
self.editor.update_canvas()
super().accept()
class StickerTool:
"""贴纸工具类"""
def __init__(self, editor):
self.editor = editor
self.dialog = StickerDialog(editor)
def show_dialog(self):
"""显示对话框"""
self.dialog.show()
13. ui/mainwindow.ui
这个文件定义了图形用户界面的布局,您可以使用 Qt Designer 创建它。以下是基本的 UI 文件内容:
xml
复制
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>图像编辑器</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="canvas">
<property name="text">
<string>请打开一张图片</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>文件</string>
</property>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
</widget>
<widget class="QMenu" name="menuEdit">
<property name="title">
<string>编辑</string>
</property>
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
<addaction name="actionReset"/>
</widget>
<widget class="QMenu" name="menuAdjust">
<property name="title">
<string>调整</string>
</property>
<addaction name="actionBrightnessContrast"/>
<addaction name="actionBlur"/>
<addaction name="actionSharpen"/>
</widget>
<widget class="QMenu" name="menuOperation">
<property name="title">
<string>操作</string>
</property>
<addaction name="actionCrop"/>
<addaction name="actionRotate"/>
<addaction name="actionScale"/>
<addaction name="actionFlip"/>
</widget>
<widget class="QMenu" name="menuAdd">
<property name="title">
<string>添加</string>
</property>
<addaction name="actionText"/>
<addaction name="actionSticker"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuAdjust"/>
<addaction name="menuOperation"/>
<addaction name="menuAdd"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionOpen">
<property name="text">
<string>打开</string>
</property>
</action>
<action name="actionSave">
<property name="text">
<string>保存</string>
</property>
</action>
<action name="actionUndo">
<property name="text">
<string>撤销</string>
</property>
</action>
<action name="actionRedo">
<property name="text">
<string>重做</string>
</property>
</action>
<action name="actionReset">
<property name="text">
<string>重置</string>
</property>
</action>
<action name="actionBrightnessContrast">
<property name="text">
<string>亮度对比度</string>
</property>
</action>
<action name="actionBlur">
<property name="text">
<string>模糊</string>
</property>
</action>
<action name="actionSharpen">
<property name="text">
<string>锐化</string>
</property>
</action>
<action name="actionCrop">
<property name="text">
<string>裁剪</string>
</property>
</action>
<action name="actionRotate">
<property name="text">
<string>旋转</string>
</property>
</action>
<action name="actionScale">
<property name="text">
<string>缩放</string>
</property>
</action>
<action name="actionFlip">
<property name="text">
<string>翻转</string>
</property>
</action>
<action name="actionText">
<property name="text">
<string>文字</string>
</property>
</action>
<action name="actionSticker">
<property name="text">
<string>贴纸</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>代码这这,这是项目结构:ImageEditor/
├── main.py # 主程序入口
├── image_editor.py # 图像编辑器主类
├── tools/ # 图像处理工具
│ ├── __init__.py # 工具包初始化
│ ├── brightness_contrast.py # 亮度对比度工具
│ ├── blur.py # 模糊工具
│ ├── sharpen.py # 锐化工具
│ ├── crop.py # 裁剪工具
│ ├── rotate.py # 旋转工具
│ ├── scale.py # 缩放工具
│ ├── flip.py # 翻转工具
│ ├── text.py # 文字工具
│ └── sticker.py # 贴纸工具
├── ui/ # 用户界面文件
│ ├── main_window.ui # 主窗口界面
│ └── resource.qrc # 资源文件
└── resources/ # 资源文件夹
├── stickers/ # 贴纸资源
└── fonts/ # 字体资源
最新发布