ge-utils/image_cutting/backup/main_완성.py
2025-07-18 18:25:05 +07:00

370 lines
15 KiB
Python

import sys
import os
import datetime
import subprocess
import re
import math
import time
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QLineEdit, QFileDialog,
QProgressBar, QTextEdit, QMessageBox, QCheckBox
)
from PyQt6.QtCore import Qt, QTimer
from PIL import Image
class ImageSlicer(QWidget):
def __init__(self):
super().__init__()
self.is_slicing = False
self.progress_target = 0
self.progress_timer = QTimer(self)
self.progress_timer.timeout.connect(self.update_progress_bar)
self.initUI()
def initUI(self):
self.setWindowTitle('Image Slicer')
# Layouts
main_layout = QVBoxLayout()
form_layout = QVBoxLayout()
button_layout = QHBoxLayout()
# Folder selection
folder_layout = QHBoxLayout()
self.folder_label = QLabel('Image Folder:')
self.folder_path = QLineEdit()
self.folder_button = QPushButton('Browse')
self.folder_button.clicked.connect(self.browse_folder)
folder_layout.addWidget(self.folder_label)
folder_layout.addWidget(self.folder_path)
folder_layout.addWidget(self.folder_button)
form_layout.addLayout(folder_layout)
# Dimensions
dimensions_layout = QHBoxLayout()
self.width_label = QLabel('Width (cm):')
self.width_input = QLineEdit('0')
self.height_label = QLabel('Height (cm):')
self.height_input = QLineEdit()
dimensions_layout.addWidget(self.width_label)
dimensions_layout.addWidget(self.width_input)
dimensions_layout.addWidget(self.height_label)
dimensions_layout.addWidget(self.height_input)
form_layout.addLayout(dimensions_layout)
# Checkboxes
checkbox_layout = QHBoxLayout()
self.save_combined_checkbox = QCheckBox("Save Combined Image")
self.save_combined_checkbox.setChecked(False)
self.skip_merge_checkbox = QCheckBox("Skip Image Merging")
checkbox_layout.addWidget(self.save_combined_checkbox)
checkbox_layout.addWidget(self.skip_merge_checkbox)
form_layout.addLayout(checkbox_layout)
# Progress bar
self.progress_bar = QProgressBar()
self.progress_bar.setStyleSheet("""
QProgressBar::chunk {
background-color: orange;
}
""")
form_layout.addWidget(self.progress_bar)
# Log screen
self.log_screen = QTextEdit()
self.log_screen.setReadOnly(True)
form_layout.addWidget(self.log_screen)
# Action buttons
self.start_button = QPushButton('Start')
self.start_button.clicked.connect(self.toggle_slicing)
self.save_log_button = QPushButton('Save Log')
self.save_log_button.clicked.connect(self.save_log)
button_layout.addWidget(self.start_button)
button_layout.addWidget(self.save_log_button)
main_layout.addLayout(form_layout)
main_layout.addLayout(button_layout)
self.setLayout(main_layout)
def browse_folder(self):
if self.is_slicing: return
folder = QFileDialog.getExistingDirectory(self, 'Select Folder')
if folder:
self.folder_path.setText(folder)
self.log(f"Selected folder: {folder}")
def log(self, message):
self.log_screen.append(message)
QApplication.processEvents()
def cm_to_pixels(self, cm):
return int(cm * 96 / 2.54)
def open_folder(self, path):
if sys.platform == "win32":
os.startfile(path)
elif sys.platform == "darwin":
subprocess.Popen(["open", path])
else:
subprocess.Popen(["xdg-open", path])
def toggle_slicing(self, checked=False):
if self.is_slicing:
self.stop_slicing()
else:
self.start_slicing()
def stop_slicing(self):
self.is_slicing = False
self.log("Stopping process...")
def update_progress_bar(self):
current_value = self.progress_bar.value()
if current_value < self.progress_target:
self.progress_bar.setValue(current_value + 1)
elif current_value > self.progress_target:
self.progress_bar.setValue(self.progress_target)
def save_folder_paths_to_info_txt(self, folder_path):
self.log("Finding all subdirectories with images...")
self.progress_target = 20
folders_with_images = []
for root, dirs, files in os.walk(folder_path):
QApplication.processEvents() # Keep UI responsive
# Exclude _combined and _log folders
if os.path.basename(root).endswith(('_combined', '_log')):
continue
if any(f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')) for f in files):
folders_with_images.append(root)
if not folders_with_images:
self.log("No folders with images found.")
self.progress_target = 100
return
self.log(f"Found {len(folders_with_images)} folders. Saving to info.txt...")
self.progress_target = 60
info_file_path = os.path.join(folder_path, "info.txt")
try:
with open(info_file_path, 'w', encoding='utf-8') as f:
for folder in sorted(folders_with_images):
f.write(f"{folder}\n")
self.log(f"Successfully saved paths to {info_file_path}")
except Exception as e:
self.log(f"Error saving info.txt: {e}")
self.progress_target = 100
def merge_and_slice_images(self, folder_path, slice_width_cm, slice_height_cm, save_combined):
self.log("Finding folders to process...")
self.progress_target = 5
QApplication.processEvents()
folders_to_process = []
processed_result_folders = [] # New list to store result folder paths
if any(f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')) for f in os.listdir(folder_path)):
folders_to_process.append(folder_path)
for item in os.listdir(folder_path):
item_path = os.path.join(folder_path, item)
if os.path.isdir(item_path):
if any(f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')) for f in os.listdir(item_path)):
folders_to_process.append(item_path)
self.progress_target = 10
if not folders_to_process:
self.log("No folders with images found to process.")
self.progress_target = 100
return
total_folders = len(folders_to_process)
progress_per_folder = 90 / total_folders
try:
for i, current_folder in enumerate(folders_to_process):
if not self.is_slicing: break
base_progress = 10 + (i * progress_per_folder)
self.log(f"--- Processing folder: {os.path.basename(current_folder)} ---")
self.progress_target = int(base_progress)
image_files = [os.path.join(current_folder, f) for f in os.listdir(current_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
def natural_sort_key(s): return [int(text) if text.isdigit() else text.lower() for text in re.split('([0-9]+)', s)]
image_files.sort(key=natural_sort_key)
if not image_files: continue
self.log(f"Merging {len(image_files)} images...")
self.progress_target = int(base_progress + (progress_per_folder * 0.2))
slice_height_px = self.cm_to_pixels(slice_height_cm)
target_width_px = self.cm_to_pixels(slice_width_cm) if slice_width_cm > 0 else 0
processed_images = []
first_image_width = 0
for image_path in image_files:
if not self.is_slicing: break
img = Image.open(image_path)
if target_width_px == 0:
if first_image_width == 0: first_image_width = img.width
current_target_width = first_image_width
else:
current_target_width = target_width_px
if img.width != current_target_width:
aspect_ratio = img.height / img.width
new_height = int(current_target_width * aspect_ratio)
img = img.resize((current_target_width, new_height), Image.Resampling.LANCZOS)
processed_images.append(img)
QApplication.processEvents()
if not self.is_slicing: break
self.progress_target = int(base_progress + (progress_per_folder * 0.6))
total_height = sum(p_img.height for p_img in processed_images)
combined_image = Image.new('RGB', (processed_images[0].width, total_height))
current_height = 0
for p_img in processed_images:
combined_image.paste(p_img, (0, current_height))
current_height += p_img.height
result_folder_name = f"{os.path.basename(current_folder)}_result"
output_dir = os.path.join(current_folder, result_folder_name)
os.makedirs(output_dir, exist_ok=True)
processed_result_folders.append(output_dir) # Add to list
if save_combined:
combined_folder_name = f"{os.path.basename(current_folder)}_combined"
combined_dir = os.path.join(os.path.dirname(output_dir), combined_folder_name)
os.makedirs(combined_dir, exist_ok=True)
combined_image_path = os.path.join(combined_dir, "combined_image.png")
combined_image.save(combined_image_path, 'PNG')
self.log(f"Saved combined image to: {combined_image_path}")
num_slices = math.ceil(combined_image.height / slice_height_px)
for j in range(num_slices):
if not self.is_slicing: break
top = j * slice_height_px
bottom = min((j + 1) * slice_height_px, combined_image.height)
box = (0, top, combined_image.width, bottom)
if top >= bottom: continue
cropped_img = combined_image.crop(box)
slice_filename = f"result_{j+1}.png"
slice_path = os.path.join(output_dir, slice_filename)
cropped_img.save(slice_path, 'PNG')
self.log(f"Saved {num_slices} slices in {result_folder_name}")
self.progress_target = int(base_progress + progress_per_folder)
# Save processed result folders to info.txt
if processed_result_folders:
info_file_path = os.path.join(folder_path, "info.txt")
try:
with open(info_file_path, 'w', encoding='utf-8') as f:
for result_folder in sorted(processed_result_folders):
f.write(f"{result_folder}\n")
self.log(f"Successfully saved processed folder paths to {info_file_path}")
except Exception as e:
self.log(f"Error saving info.txt for processed folders: {e}")
except Exception as e:
raise # Re-raise the exception to be caught by start_slicing
def start_slicing(self):
self.log_screen.clear()
self.progress_bar.setValue(0)
self.progress_target = 0
folder_path = self.folder_path.text()
slice_width_cm_str = self.width_input.text()
slice_height_cm_str = self.height_input.text()
if not os.path.isdir(folder_path):
QMessageBox.warning(self, "Warning", "Please select a valid folder.")
self.log("Error: Invalid folder path.")
return
try:
slice_width_cm = float(slice_width_cm_str) if slice_width_cm_str else 0
slice_height_cm = float(slice_height_cm_str) if slice_height_cm_str else 0
if slice_width_cm < 0 or (not self.skip_merge_checkbox.isChecked() and slice_height_cm <= 0):
raise ValueError
except (ValueError, TypeError):
QMessageBox.warning(self, "Warning", "Please enter valid positive numbers for height (width can be 0).")
self.log("Error: Invalid input.")
return
self.is_slicing = True
self.start_button.setText("Stop")
self.progress_timer.start(10)
save_combined = self.save_combined_checkbox.isChecked()
skip_merge = self.skip_merge_checkbox.isChecked()
try:
if skip_merge:
self.log("--- Skipping Merge: Saving folder paths to info.txt ---")
self.save_folder_paths_to_info_txt(folder_path)
else:
self.log("--- Starting Image Merge and Slice Process ---")
self.merge_and_slice_images(folder_path, slice_width_cm, slice_height_cm, save_combined)
if self.is_slicing:
self.progress_target = 100
while self.progress_bar.value() < 99:
time.sleep(0.01)
QApplication.processEvents()
self.log("--- All tasks completed ---")
QMessageBox.information(self, "Success", "Image processing completed for all tasks.")
except Exception as e:
self.log(f"An unexpected error occurred: {e}")
QMessageBox.critical(self, "Error", f"An unexpected error occurred: {e}")
finally:
self.stop_slicing_ui()
def stop_slicing_ui(self):
self.is_slicing = False
self.start_button.setText("Start")
self.progress_timer.stop()
self.progress_bar.setValue(0)
def save_log(self):
if self.is_slicing:
QMessageBox.warning(self, "Warning", "Cannot save log while process is running.")
return
folder_path = self.folder_path.text()
if not folder_path or not os.path.isdir(folder_path):
QMessageBox.warning(self, "Warning", "Please select a valid base folder first.")
return
log_content = self.log_screen.toPlainText()
if not log_content:
QMessageBox.warning(self, "Warning", "Log is empty.")
return
try:
log_dir_name = f"{os.path.basename(folder_path)}_log"
log_dir_path = os.path.join(folder_path, log_dir_name)
os.makedirs(log_dir_path, exist_ok=True)
now = datetime.datetime.now()
log_filename = f"log_{now.strftime('%H%M%S')}.log"
save_path = os.path.join(log_dir_path, log_filename)
with open(save_path, 'w', encoding='utf-8') as f:
f.write(log_content)
self.log(f"Log saved to {save_path}")
QMessageBox.information(self, "Success", f"Log successfully saved to\n{save_path}")
except Exception as e:
self.log(f"Error saving log: {e}")
QMessageBox.critical(self, "Error", f"Failed to save log file: {e}")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = ImageSlicer()
ex.show()
sys.exit(app.exec())