import sysimport osfrom pathlib import Pathfrom PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog, QProgressBar, QMessageBox, QScrollArea, QSplitter, QApplication, QGridLayout)from PyQt6.QtCore import (Qt, pyqtSignal, QSize, QRect)from PyQt6.QtGui import (QPainter, QColor, QPen, QImage, QPixmap, QFont)class ThumbnailWidget(QWidget): """缩略图小部件 - 修复点击事件""" clicked = pyqtSignal(str) def __init__(self, image_path, parent=None): super().__init__(parent) self.image_path = image_path self.is_selected = False self.setFixedSize(100, 120) # 缩小尺寸 # 设置鼠标悬停和点击效果 self.setMouseTracking(True) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 绘制背景 if self.is_selected: painter.setBrush(QColor(100, 149, 237, 80)) # 选中时更明显的背景色 painter.setPen(QPen(QColor(70, 130, 180), 2)) else: painter.setBrush(QColor(245, 245, 245)) painter.setPen(QPen(QColor(200, 200, 200), 1)) painter.drawRoundedRect(1, 1, self.width()-2, self.height()-2, 6, 6) # 绘制缩略图 try: # 检查文件是否存在 if os.path.exists(self.image_path): # 使用QImage加载图片 img = QImage(self.image_path) if not img.isNull(): # 缩放图片以适合缩略图区域 scaled_img = img.scaled(80, 80, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) # 居中显示缩略图 x = (self.width() - scaled_img.width()) // 2 y = 8 painter.drawImage(x, y, scaled_img) # 绘制文件名 painter.setPen(QColor(60, 60, 60)) font = painter.font() font.setPointSize(7) font.setFamily("Microsoft YaHei") painter.setFont(font) file_name = os.path.basename(self.image_path) if len(file_name) > 12: file_name = file_name[:10] + "..." # 使用drawText绘制文本,确保在区域内 painter.drawText(QRect(0, 90, self.width(), 20), Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextWordWrap, file_name) else: # 文件不存在时显示提示 painter.setPen(QColor(200, 100, 100)) painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, "图片丢失") except Exception as e: print(f"绘制缩略图时出错: {e}") painter.setPen(QColor(200, 100, 100)) painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, "加载失败") def mousePressEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: # 确保图片路径有效 if os.path.exists(self.image_path): self.clicked.emit(self.image_path) else: print(f"图片不存在: {self.image_path}") event.accept() def enterEvent(self, event): if not self.is_selected: self.setCursor(Qt.CursorShape.PointingHandCursor) super().enterEvent(event) def leaveEvent(self, event): self.unsetCursor() super().leaveEvent(event)class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("批量处理图片去黑边工具V1.0(欢迎关注微信公众号:码海听潮)") self.setGeometry(100, 100, 900, 650) # 缩小主窗口尺寸 self.folder_path = None self.image_files = [] self.actual_image_files = [] # 实际有效的图片文件列表 self.current_preview_image = None self.init_ui() self.apply_stylesheet() def init_ui(self): # 创建中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(8) main_layout.setContentsMargins(10, 10, 10, 10) # 顶部工具栏 toolbar = QWidget() toolbar.setMaximumHeight(60) toolbar_layout = QHBoxLayout(toolbar) toolbar_layout.setSpacing(10) # 选择文件夹按钮 self.select_folder_btn = QPushButton("📁 选择文件夹") self.select_folder_btn.clicked.connect(self.select_folder) self.select_folder_btn.setMinimumHeight(36) self.select_folder_btn.setIconSize(QSize(20, 20)) toolbar_layout.addWidget(self.select_folder_btn) # 文件夹路径显示 self.folder_label = QLabel("未选择文件夹") self.folder_label.setStyleSheet(""" padding: 8px 12px; background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; color: #495057; font-size: 13px; """) self.folder_label.setMinimumHeight(36) toolbar_layout.addWidget(self.folder_label, 1) # 处理按钮 self.process_btn = QPushButton("🚀 批量处理") self.process_btn.clicked.connect(self.process_images) self.process_btn.setMinimumHeight(36) self.process_btn.setEnabled(False) toolbar_layout.addWidget(self.process_btn) main_layout.addWidget(toolbar) # 创建分割器 splitter = QSplitter() splitter.setStyleSheet("QSplitter::handle { background-color: #dee2e6; }") # 左侧图片列表区域(缩小比例) left_widget = QWidget() left_layout = QVBoxLayout(left_widget) left_layout.setSpacing(5) left_layout.setContentsMargins(5, 5, 5, 5) # 图片列表标题 list_title = QLabel("📷 图片列表") list_title.setStyleSheet(""" font-size: 13px; font-weight: bold; padding: 8px; background-color: #e9ecef; border-radius: 4px; color: #495057; """) left_layout.addWidget(list_title) # 图片列表容器 self.list_container = QScrollArea() self.list_container.setWidgetResizable(True) self.list_container.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.list_container.setStyleSheet(""" QScrollArea { border: 1px solid #dee2e6; border-radius: 4px; background-color: white; } QScrollBar:vertical { width: 10px; background-color: #f8f9fa; } QScrollBar::handle:vertical { background-color: #adb5bd; border-radius: 4px; min-height: 20px; } """) self.list_widget = QWidget() self.list_layout = QGridLayout(self.list_widget) self.list_layout.setSpacing(8) self.list_layout.setAlignment(Qt.AlignmentFlag.AlignTop) self.list_layout.setContentsMargins(8, 8, 8, 8) self.list_container.setWidget(self.list_widget) left_layout.addWidget(self.list_container) # 右侧预览区域(增大比例) right_widget = QWidget() right_layout = QVBoxLayout(right_widget) right_layout.setSpacing(8) right_layout.setContentsMargins(5, 5, 5, 5) # 预览标题 preview_title = QLabel("👁️ 图片预览") preview_title.setStyleSheet(""" font-size: 13px; font-weight: bold; padding: 8px; background-color: #e9ecef; border-radius: 4px; color: #495057; """) right_layout.addWidget(preview_title) # 预览图片显示区域 preview_container = QWidget() preview_container_layout = QVBoxLayout(preview_container) preview_container_layout.setContentsMargins(0, 0, 0, 0) self.preview_label = QLabel() self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.preview_label.setMinimumSize(400, 400) self.preview_label.setStyleSheet(""" QLabel { border: 2px dashed #adb5bd; border-radius: 8px; background-color: #f8f9fa; color: #6c757d; font-size: 14px; padding: 20px; } """) self.preview_label.setText("请从左侧选择图片进行预览") self.preview_label.setWordWrap(True) # 图片信息标签 self.image_info_label = QLabel() self.image_info_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.image_info_label.setStyleSheet(""" color: #495057; font-size: 12px; padding: 5px; background-color: #e9ecef; border-radius: 4px; """) self.image_info_label.setMaximumHeight(40) preview_container_layout.addWidget(self.preview_label, 1) preview_container_layout.addWidget(self.image_info_label) right_layout.addWidget(preview_container) # 添加分割器部件 splitter.addWidget(left_widget) splitter.addWidget(right_widget) splitter.setSizes([250, 650]) # 调整左右比例 main_layout.addWidget(splitter, 1) # 底部状态栏 status_bar = QWidget() status_layout = QVBoxLayout(status_bar) status_layout.setSpacing(5) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) self.progress_bar.setTextVisible(True) status_layout.addWidget(self.progress_bar) # 状态标签 self.status_label = QLabel("就绪") self.status_label.setStyleSheet(""" padding: 6px 10px; background-color: #e8f4fc; border: 1px solid #c9e2f3; border-radius: 4px; color: #2c5aa0; font-size: 12px; """) status_layout.addWidget(self.status_label) main_layout.addWidget(status_bar) def apply_stylesheet(self): self.setStyleSheet(""" QMainWindow { background-color: #f5f7fa; } QPushButton { background-color: #4a86e8; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-size: 13px; font-weight: bold; } QPushButton:hover { background-color: #3a76d8; } QPushButton:pressed { background-color: #2a66c8; } QPushButton:disabled { background-color: #cccccc; color: #666666; } QLabel { color: #333333; } QProgressBar { border: 1px solid #cccccc; border-radius: 4px; text-align: center; height: 22px; font-size: 11px; } QProgressBar::chunk { background-color: #4a86e8; border-radius: 4px; } """) def select_folder(self): folder = QFileDialog.getExistingDirectory(self, "选择图片文件夹") if folder: self.folder_path = folder self.folder_label.setText(f"📂 {folder}") self.load_image_files() self.process_btn.setEnabled(True) self.status_label.setText(f"已选择文件夹: {folder}") def load_image_files(self): if not self.folder_path: return # 清除现有列表 self.clear_thumbnail_list() # 清空之前的数据 self.image_files = [] self.actual_image_files = [] # 获取图片文件 extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.webp', '.gif'} try: for root, dirs, files in os.walk(self.folder_path): for file in files: ext = os.path.splitext(file)[1].lower() if ext in extensions: full_path = os.path.join(root, file) self.image_files.append(full_path) # 更新状态 if self.image_files: self.status_label.setText(f"找到 {len(self.image_files)} 张图片") else: self.status_label.setText("文件夹中没有找到图片文件") return # 创建缩略图 self.create_thumbnails() except Exception as e: self.status_label.setText(f"读取文件夹时出错: {str(e)}") def clear_thumbnail_list(self): # 安全地清除现有缩略图 while self.list_layout.count(): item = self.list_layout.takeAt(0) if item.widget(): widget = item.widget() widget.deleteLater() def create_thumbnails(self): # 创建缩略图 for i, img_path in enumerate(self.image_files): row = i // 3 col = i % 3 thumbnail = ThumbnailWidget(img_path) thumbnail.clicked.connect(self.preview_image) self.list_layout.addWidget(thumbnail, row, col) # 调整列表widget的大小 self.list_widget.adjustSize() def get_actual_image_count(self): """获取实际有效的图片数量(根据列表中实际显示的缩略图)""" actual_count = 0 for i in range(self.list_layout.count()): widget = self.list_layout.itemAt(i).widget() if isinstance(widget, ThumbnailWidget): actual_count += 1 return actual_count def preview_image(self, img_path): """预览选中的图片 - 修复版本""" try: # 清除之前的选中状态 for i in range(self.list_layout.count()): widget = self.list_layout.itemAt(i).widget() if isinstance(widget, ThumbnailWidget): widget.is_selected = False widget.update() # 设置当前选中 for i in range(self.list_layout.count()): widget = self.list_layout.itemAt(i).widget() if isinstance(widget, ThumbnailWidget) and widget.image_path == img_path: widget.is_selected = True widget.update() break # 更新预览 self.update_preview(img_path) except Exception as e: print(f"预览图片时出错: {e}") self.preview_label.setText(f"预览失败: {str(e)}") self.image_info_label.clear() def update_preview(self, img_path): """更新预览区域""" try: # 检查文件是否存在 if not os.path.exists(img_path): self.preview_label.setText("图片文件不存在") self.image_info_label.clear() return # 使用QImage加载图片 img = QImage(img_path) if img.isNull(): self.preview_label.setText("无法加载图片格式") self.image_info_label.clear() return # 获取预览区域尺寸 preview_size = self.preview_label.size() max_width = preview_size.width() - 40 max_height = preview_size.height() - 40 # 缩放图片 scaled_img = img.scaled( max_width, max_height, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation ) # 显示图片 self.preview_label.setPixmap(QPixmap.fromImage(scaled_img)) # 显示图片信息 file_name = os.path.basename(img_path) file_size = os.path.getsize(img_path) file_size_str = self.format_file_size(file_size) info_text = f""" <div style='text-align: center;'> <b>📄 文件名:</b> {file_name}<br> <b>📏 尺寸:</b> {img.width()} × {img.height()} 像素<br> <b>💾 大小:</b> {file_size_str}<br> <b>📍 路径:</b> {img_path[:80]}{'...' if len(img_path) > 80 else ''} </div> """ self.image_info_label.setText(info_text) except Exception as e: print(f"更新预览时出错: {e}") self.preview_label.setText(f"加载错误: {str(e)}") self.image_info_label.clear() def format_file_size(self, size_bytes): """格式化文件大小""" for unit in ['B', 'KB', 'MB', 'GB']: if size_bytes < 1024.0: return f"{size_bytes:.1f} {unit}" size_bytes /= 1024.0 return f"{size_bytes:.1f} TB" def process_images(self): """批量处理图片 - 以图片列表中实际存在的图片数量为准""" # 获取图片列表中实际存在的图片 actual_images = [] for i in range(self.list_layout.count()): widget = self.list_layout.itemAt(i).widget() if isinstance(widget, ThumbnailWidget): img_path = widget.image_path # 检查图片文件是否存在 if os.path.exists(img_path): actual_images.append(img_path) # 使用实际存在的图片数量 actual_image_count = len(actual_images) if actual_image_count == 0: self.status_label.setText("没有图片需要处理") return # 确认对话框 - 使用实际图片数量 reply = QMessageBox.question( self, '确认处理', f'确定要处理 {actual_image_count} 张图片吗?', QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No ) if reply != QMessageBox.StandardButton.Yes: return self.progress_bar.setVisible(True) self.progress_bar.setMaximum(actual_image_count) self.progress_bar.setValue(0) success_count = 0 fail_count = 0 # 处理实际存在的图片 for i, img_path in enumerate(actual_images, 1): self.progress_bar.setValue(i) file_name = os.path.basename(img_path)[:20] + ("..." if len(os.path.basename(img_path)) > 20 else "") self.status_label.setText(f"正在处理: {file_name}") QApplication.processEvents() # 这里应该是实际的图片处理逻辑 # 暂时模拟处理结果 result = {'success': True} # 模拟成功 if result['success']: success_count += 1 else: fail_count += 1 self.progress_bar.setVisible(False) self.status_label.setText(f"处理完成!成功: {success_count} 张,失败: {fail_count} 张") # 显示完成对话框 QMessageBox.information( self, "处理完成", f"批量处理完成!\n\n成功: {success_count} 张\n失败: {fail_count} 张", QMessageBox.StandardButton.Ok )def main(): app = QApplication(sys.argv) app.setStyle('Fusion') app.setApplicationName("批量处理图片去黑边工具V1.0") # 设置字体 font = QFont("Microsoft YaHei", 9) app.setFont(font) window = MainWindow() window.show() sys.exit(app.exec())if __name__ == "__main__": main()