32c7b837 by 柴进

修复JPG导出为PNG,PS无法打开的问题

1 parent 9fc22cc0
...@@ -271,6 +271,71 @@ def get_app_data_path() -> Path: ...@@ -271,6 +271,71 @@ def get_app_data_path() -> Path:
271 return Path.cwd() / "images" 271 return Path.cwd() / "images"
272 272
273 273
274 def save_png_with_validation(file_path: str, image_bytes: bytes) -> bool:
275 """使用Pillow验证并重写PNG文件,确保格式正确性
276
277 Args:
278 file_path: 保存文件路径
279 image_bytes: 图像字节数据
280
281 Returns:
282 bool: True表示使用Pillow成功处理,False表示回退到原始方法
283 """
284 try:
285 from PIL import Image
286 import io
287
288 # 使用Pillow打开图像数据
289 with Image.open(io.BytesIO(image_bytes)) as img:
290 # 检查是否实际是JPEG但伪装成PNG的情况
291 file_format = img.format
292 if file_format == 'JPEG':
293 logger = logging.getLogger(__name__)
294 logger.info(f"检测到伪装PNG的JPEG文件,实际格式: {file_format}")
295
296 # 根据文件路径的扩展名决定保存格式
297 save_format = 'PNG' if file_path.lower().endswith('.png') else 'JPEG'
298
299 # 如果检测到的格式与目标格式不符,进行真正的转换
300 if file_format and file_format != save_format:
301 logger = logging.getLogger(__name__)
302 logger.info(f"执行格式转换: {file_format} -> {save_format}")
303
304 # 如果目标格式是PNG,确保使用RGBA模式以支持透明度
305 if save_format == 'PNG':
306 if img.mode not in ['RGBA', 'RGB', 'L']:
307 if img.mode == 'P':
308 img = img.convert('RGBA')
309 elif img.mode == 'LA':
310 img = img.convert('RGBA')
311 else:
312 img = img.convert('RGBA')
313 elif save_format == 'JPEG':
314 if img.mode in ['RGBA', 'P']:
315 img = img.convert('RGB')
316 elif img.mode == 'L':
317 img = img.convert('RGB')
318
319 # 保存图片为指定格式
320 img.save(file_path, save_format, optimize=True)
321
322 logger = logging.getLogger(__name__)
323 logger.info(f"图片格式验证成功: {file_path}, 保存格式: {save_format}")
324 return True
325
326 except ImportError:
327 # Pillow不可用
328 logger = logging.getLogger(__name__)
329 logger.warning("Pillow库不可用,使用原始保存方法")
330 return False
331
332 except Exception as e:
333 # Pillow处理失败,回退到原始方法
334 logger = logging.getLogger(__name__)
335 logger.warning(f"Pillow处理失败,使用原始保存方法: {e}")
336 return False
337
338
274 class HistoryManager: 339 class HistoryManager:
275 """历史记录管理器""" 340 """历史记录管理器"""
276 341
...@@ -310,17 +375,23 @@ class HistoryManager: ...@@ -310,17 +375,23 @@ class HistoryManager:
310 record_dir = self.base_path / timestamp 375 record_dir = self.base_path / timestamp
311 record_dir.mkdir(exist_ok=True) 376 record_dir.mkdir(exist_ok=True)
312 377
313 # 保存生成的图片 378 # 保存生成的图片 - 使用PNG格式验证
314 generated_image_path = record_dir / "generated.png" 379 generated_image_path = record_dir / "generated.png"
315 with open(generated_image_path, 'wb') as f: 380 if not save_png_with_validation(str(generated_image_path), image_bytes):
316 f.write(image_bytes) 381 # Pillow处理失败,回退到原始方法
382 with open(generated_image_path, 'wb') as f:
383 f.write(image_bytes)
384 self.logger.warning(f"使用原始保存方法: {generated_image_path}")
317 385
318 # 保存参考图片 386 # 保存参考图片 - 使用PNG格式验证
319 reference_image_paths = [] 387 reference_image_paths = []
320 for i, ref_img_bytes in enumerate(reference_images): 388 for i, ref_img_bytes in enumerate(reference_images):
321 ref_path = record_dir / f"reference_{i + 1}.png" 389 ref_path = record_dir / f"reference_{i + 1}.png"
322 with open(ref_path, 'wb') as f: 390 if not save_png_with_validation(str(ref_path), ref_img_bytes):
323 f.write(ref_img_bytes) 391 # Pillow处理失败,回退到原始方法
392 with open(ref_path, 'wb') as f:
393 f.write(ref_img_bytes)
394 self.logger.warning(f"使用原始保存方法: {ref_path}")
324 reference_image_paths.append(ref_path) 395 reference_image_paths.append(ref_path)
325 396
326 # 保存元数据 397 # 保存元数据
...@@ -1929,20 +2000,63 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -1929,20 +2000,63 @@ class ImageGeneratorWindow(QMainWindow):
1929 return 2000 return
1930 2001
1931 try: 2002 try:
1932 # Create temporary file 2003 # 获取处理后的PNG图片数据
2004 processed_bytes = self.get_processed_image_bytes('.png')
2005
2006 # Create temporary file with processed data
1933 with tempfile.NamedTemporaryFile(delete=False, suffix='.png', mode='wb') as tmp_file: 2007 with tempfile.NamedTemporaryFile(delete=False, suffix='.png', mode='wb') as tmp_file:
1934 tmp_file.write(self.generated_image_bytes) 2008 tmp_file.write(processed_bytes)
1935 tmp_path = tmp_file.name 2009 tmp_path = tmp_file.name
1936 2010
1937 # Open with system default viewer 2011 # Open with system default viewer
1938 url = QUrl.fromLocalFile(tmp_path) 2012 url = QUrl.fromLocalFile(tmp_path)
1939 QDesktopServices.openUrl(url) 2013 QDesktopServices.openUrl(url)
1940 2014
1941 self.status_label.setText("● 已用系统查看器打开") 2015 processing_method = "格式转换后" if processed_bytes != self.generated_image_bytes else "原始数据"
2016 self.status_label.setText(f"● 已用系统查看器打开 ({processing_method})")
1942 self.status_label.setStyleSheet("QLabel { color: #007AFF; }") 2017 self.status_label.setStyleSheet("QLabel { color: #007AFF; }")
1943 except Exception as e: 2018 except Exception as e:
1944 QMessageBox.critical(self, "错误", f"无法打开系统图片查看器: {str(e)}") 2019 QMessageBox.critical(self, "错误", f"无法打开系统图片查看器: {str(e)}")
1945 2020
2021 def get_processed_image_bytes(self, file_extension='.png') -> bytes:
2022 """获取处理后的图片字节数据"""
2023 try:
2024 from PIL import Image
2025 import io
2026
2027 # 使用Pillow处理图像
2028 with Image.open(io.BytesIO(self.generated_image_bytes)) as img:
2029 file_format = img.format
2030 save_format = 'PNG' if file_extension.lower().endswith('.png') else 'JPEG'
2031
2032 # 检测并执行格式转换
2033 if file_format and file_format != save_format:
2034 self.logger.info(f"下载时格式转换: {file_format} -> {save_format}")
2035
2036 # 模式转换
2037 if save_format == 'PNG':
2038 if img.mode not in ['RGBA', 'RGB', 'L']:
2039 if img.mode == 'P':
2040 img = img.convert('RGBA')
2041 elif img.mode == 'LA':
2042 img = img.convert('RGBA')
2043 else:
2044 img = img.convert('RGBA')
2045 elif save_format == 'JPEG':
2046 if img.mode in ['RGBA', 'P']:
2047 img = img.convert('RGB')
2048 elif img.mode == 'L':
2049 img = img.convert('RGB')
2050
2051 # 转换为字节数据
2052 processed_bytes = io.BytesIO()
2053 img.save(processed_bytes, save_format, optimize=True)
2054 return processed_bytes.getvalue()
2055
2056 except Exception as e:
2057 self.logger.warning(f"图片处理失败,使用原始数据: {e}")
2058 return self.generated_image_bytes
2059
1946 def download_image(self): 2060 def download_image(self):
1947 """Download generated image""" 2061 """Download generated image"""
1948 if not self.generated_image_bytes: 2062 if not self.generated_image_bytes:
...@@ -1961,14 +2075,26 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -1961,14 +2075,26 @@ class ImageGeneratorWindow(QMainWindow):
1961 2075
1962 if file_path: 2076 if file_path:
1963 try: 2077 try:
2078 # 获取处理后的图片数据
2079 file_extension = Path(file_path).suffix
2080 processed_bytes = self.get_processed_image_bytes(file_extension)
2081
2082 # 保存处理后的图片
1964 with open(file_path, 'wb') as f: 2083 with open(file_path, 'wb') as f:
1965 f.write(self.generated_image_bytes) 2084 f.write(processed_bytes)
2085
2086 # 获取实际文件大小
2087 import os
2088 file_size = os.path.getsize(file_path)
2089
2090 processing_method = "格式转换" if processed_bytes != self.generated_image_bytes else "原始数据"
2091 self.logger.info(f"图片下载完成 - 方法: {processing_method}, 文件: {file_path}, 大小: {file_size} 字节")
1966 2092
1967 file_size = len(self.generated_image_bytes)
1968 QMessageBox.information(self, "成功", f"图片已保存到:\n{file_path}\n\n文件大小: {file_size:,} 字节") 2093 QMessageBox.information(self, "成功", f"图片已保存到:\n{file_path}\n\n文件大小: {file_size:,} 字节")
1969 self.status_label.setText("● 图片已保存") 2094 self.status_label.setText("● 图片已保存")
1970 self.status_label.setStyleSheet("QLabel { color: #34C759; }") 2095 self.status_label.setStyleSheet("QLabel { color: #34C759; }")
1971 except Exception as e: 2096 except Exception as e:
2097 self.logger.error(f"图片保存失败: {str(e)}")
1972 QMessageBox.critical(self, "错误", f"保存失败: {str(e)}") 2098 QMessageBox.critical(self, "错误", f"保存失败: {str(e)}")
1973 2099
1974 def refresh_history(self): 2100 def refresh_history(self):
......