修复JPG导出为PNG,PS无法打开的问题
Showing
1 changed file
with
137 additions
and
11 deletions
| ... | @@ -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): | ... | ... |
-
Please register or sign in to post a comment