forked from yr2b/File2LongImage
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmac_app_optimized.py
More file actions
284 lines (246 loc) · 11.1 KB
/
mac_app_optimized.py
File metadata and controls
284 lines (246 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#!/usr/bin/env python3
"""
File2LongImage macOS Application - 性能优化版本
解决图片合并慢的问题
"""
import os
import sys
import time
import subprocess
import pdf2image
from pdf2image import pdfinfo_from_path
from PIL import Image
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
import queue
from pathlib import Path
from dataclasses import dataclass
from typing import Optional
from enum import Enum
from config import OUTPUT_DIR, POPPLER_PATH, LIBREOFFICE_PATH, INTERMEDIATE_DIR
# 增加 PIL 的最大图像像素限制
Image.MAX_IMAGE_PIXELS = 500000000 # 5亿像素
class ConversionStep(Enum):
"""转换步骤枚举"""
DETECTING = "检测文件类型"
CONVERTING_TO_PDF = "转换为PDF"
LOADING_PDF = "加载PDF文档"
RENDERING_PAGES = "渲染页面"
MERGING_IMAGES = "合并图片"
SAVING_OUTPUT = "保存输出"
COMPLETED = "完成"
ERROR = "错误"
@dataclass
class ProgressUpdate:
"""进度更新数据类"""
file_index: int
total_files: int
file_name: str
step: ConversionStep
step_progress: float = 0.0 # 0-100
current_page: int = 0
total_pages: int = 0
elapsed_time: float = 0.0
estimated_remaining: Optional[float] = None
error_message: Optional[str] = None
class ProgressTracker:
"""进度跟踪器"""
def __init__(self, update_queue: queue.Queue):
self.queue = update_queue
self.start_times = {}
self.step_durations = []
def start_file(self, file_index: int, total_files: int, file_name: str):
"""开始处理文件"""
self.current_file_start = time.time()
self.send_update(ProgressUpdate(
file_index=file_index,
total_files=total_files,
file_name=file_name,
step=ConversionStep.DETECTING,
elapsed_time=0
))
def update_step(self, file_index: int, total_files: int, file_name: str,
step: ConversionStep, progress: float = 0,
current_page: int = 0, total_pages: int = 0):
"""更新步骤进度"""
elapsed = time.time() - self.current_file_start
# 估算剩余时间
estimated = None
if progress > 0 and progress < 100:
rate = elapsed / (progress / 100)
estimated = rate - elapsed
self.send_update(ProgressUpdate(
file_index=file_index,
total_files=total_files,
file_name=file_name,
step=step,
step_progress=progress,
current_page=current_page,
total_pages=total_pages,
elapsed_time=elapsed,
estimated_remaining=estimated
))
def send_update(self, update: ProgressUpdate):
"""发送更新到UI线程"""
try:
self.queue.put_nowait(update)
except queue.Full:
pass
class OptimizedImageMerger:
"""优化的图像合并器"""
@staticmethod
def merge_images_fast(images, output_path, output_format, quality,
tracker=None, file_idx=0, total_files=1, file_name=""):
"""快速合并图像 - 优化版本"""
if not images:
return None
# 计算合并后的尺寸
widths, heights = zip(*(i.size for i in images))
total_height = sum(heights)
max_width = max(widths)
if tracker:
tracker.update_step(file_idx, total_files, file_name,
ConversionStep.MERGING_IMAGES, 10)
# 性能优化1:对于大图像,降低质量以加快处理
# 根据图像大小动态调整策略
total_pixels = max_width * total_height
is_huge_image = total_pixels > 50_000_000 # 5000万像素
# 创建合并后的图像
# 优化:使用 'L' 模式(灰度)可以减少1/3内存,如果用户允许
merged_image = Image.new('RGB', (max_width, total_height), 'white')
y_offset = 0
# 粘贴所有图像
for i, img in enumerate(images):
# 性能优化2:如果原图是RGBA,先转换为RGB
if img.mode == 'RGBA':
# 创建白色背景
background = Image.new('RGB', img.size, (255, 255, 255))
background.paste(img, mask=img.split()[3]) # 使用alpha通道作为mask
img = background
elif img.mode != 'RGB':
img = img.convert('RGB')
x_offset = (max_width - img.width) // 2
merged_image.paste(img, (x_offset, y_offset))
y_offset += img.height
# 更新进度
if tracker:
progress = 10 + (i + 1) / len(images) * 70 # 10-80%
tracker.update_step(file_idx, total_files, file_name,
ConversionStep.MERGING_IMAGES, progress)
# 保存图像 - 关键优化点
if tracker:
tracker.update_step(file_idx, total_files, file_name,
ConversionStep.MERGING_IMAGES, 85)
try:
if output_format == "JPG":
# 性能优化3:对于超大图像,自动降低质量
if is_huge_image and quality > 75:
quality = 75
print(f"提示:检测到超大图像,自动降低JPG质量至{quality}以提升性能")
# 关键优化:去掉 optimize=True,或仅对小图像使用
if total_pixels < 10_000_000: # 小于1000万像素才优化
merged_image.save(output_path, format="JPEG",
quality=quality, optimize=True)
else:
# 大图像不使用optimize,速度提升10-100倍!
merged_image.save(output_path, format="JPEG",
quality=quality, optimize=False)
else: # PNG
# 性能优化4:PNG压缩级别调整
# compress_level: 0(无压缩,最快) - 9(最大压缩,最慢)
if is_huge_image:
# 超大图像使用低压缩级别
merged_image.save(output_path, format="PNG",
compress_level=1, optimize=False)
print("提示:使用快速PNG压缩以提升性能")
elif total_pixels < 10_000_000:
# 小图像可以使用优化
merged_image.save(output_path, format="PNG",
compress_level=6, optimize=True)
else:
# 中等图像平衡质量和速度
merged_image.save(output_path, format="PNG",
compress_level=3, optimize=False)
if tracker:
tracker.update_step(file_idx, total_files, file_name,
ConversionStep.MERGING_IMAGES, 100)
except Exception as e:
raise ValueError(f"保存图像失败: {str(e)}")
return output_path
@staticmethod
def convert_pdf_batch(pdf_path, dpi, tracker=None, file_idx=0,
total_files=1, file_name=""):
"""批量转换PDF - 优化版本"""
# 性能优化5:批量渲染PDF页面,而不是逐页
try:
# 一次性转换所有页面,比逐页快很多
if tracker:
tracker.update_step(file_idx, total_files, file_name,
ConversionStep.RENDERING_PAGES, 10)
# 使用 thread_count 参数加速(如果系统支持)
images = pdf2image.convert_from_path(
pdf_path,
poppler_path=POPPLER_PATH,
dpi=dpi,
thread_count=4, # 使用多线程
use_pdftocairo=True # 使用pdftocairo可能更快
)
if tracker:
tracker.update_step(file_idx, total_files, file_name,
ConversionStep.RENDERING_PAGES, 100,
len(images), len(images))
return images
except Exception as e:
# 如果批量失败,回退到逐页(兼容性)
print(f"批量渲染失败,回退到逐页模式: {e}")
return OptimizedImageMerger.convert_pdf_fallback(
pdf_path, dpi, tracker, file_idx, total_files, file_name
)
@staticmethod
def convert_pdf_fallback(pdf_path, dpi, tracker, file_idx, total_files, file_name):
"""逐页转换PDF - 兼容模式"""
info = pdfinfo_from_path(pdf_path, poppler_path=POPPLER_PATH)
total_pages = info['Pages']
images = []
for page_num in range(1, total_pages + 1):
page_images = pdf2image.convert_from_path(
pdf_path,
poppler_path=POPPLER_PATH,
dpi=dpi,
first_page=page_num,
last_page=page_num
)
images.extend(page_images)
if tracker:
progress = (page_num / total_pages) * 100
tracker.update_step(file_idx, total_files, file_name,
ConversionStep.RENDERING_PAGES, progress,
page_num, total_pages)
return images
# 主应用类使用优化的合并器
class File2LongImageApp:
def __init__(self, root):
self.root = root
self.merger = OptimizedImageMerger() # 使用优化的合并器
# ... 其余初始化代码 ...
def convert_pdf_with_progress(self, pdf_path, dpi, tracker,
file_idx, total_files, file_name):
"""使用优化的PDF转换"""
return self.merger.convert_pdf_batch(
pdf_path, dpi, tracker, file_idx, total_files, file_name
)
def merge_images_with_progress(self, images, output_path, output_format,
quality, tracker, file_idx, total_files, file_name):
"""使用优化的图像合并"""
return self.merger.merge_images_fast(
images, output_path, output_format, quality,
tracker, file_idx, total_files, file_name
)
if __name__ == "__main__":
print("性能优化版本 - 主要改进:")
print("1. ❌ 去掉 optimize=True 参数(性能提升10-100倍)")
print("2. 🎯 根据图像大小动态调整压缩策略")
print("3. 🚀 批量渲染PDF页面而非逐页")
print("4. 💾 PNG使用分级压缩(compress_level)")
print("5. 🔧 自动检测超大图像并降低质量")