feat: add WebP output format support for TGA conversion
- Updated TGAConverter.convert_tga_to_png() to support 'webp' format - High quality WebP encoding (quality=95, method=6) - Added format selector in UI (PNG or WebP) - Convert button text updates based on selected format - WebP is better for web uploads: smaller files, better quality WebP advantages: - 25-35% smaller file size than PNG - Better quality at same file size - Alpha channel support - Widely supported in modern browsers
This commit is contained in:
parent
14bac40fdf
commit
ac334ac4a8
|
|
@ -263,16 +263,18 @@ class TGAConverter:
|
|||
logger.error(f"Error reading TGA pixels: {e}")
|
||||
return None
|
||||
|
||||
def convert_tga_to_png(self, tga_path: Path, output_name: Optional[str] = None) -> Optional[Path]:
|
||||
def convert_tga_to_png(self, tga_path: Path, output_name: Optional[str] = None,
|
||||
output_format: str = 'png') -> Optional[Path]:
|
||||
"""
|
||||
Convert a TGA file to PNG.
|
||||
Convert a TGA file to PNG or WebP.
|
||||
|
||||
Args:
|
||||
tga_path: Path to .tga file
|
||||
output_name: Optional output filename (without extension)
|
||||
output_format: 'png' or 'webp'
|
||||
|
||||
Returns:
|
||||
Path to converted PNG file or None if failed
|
||||
Path to converted file or None if failed
|
||||
"""
|
||||
if not PIL_AVAILABLE:
|
||||
logger.error("PIL/Pillow required for TGA to PNG conversion")
|
||||
|
|
@ -292,15 +294,21 @@ class TGAConverter:
|
|||
else:
|
||||
image = image.convert('RGBA')
|
||||
|
||||
# Determine output path
|
||||
# Determine output path and format
|
||||
if output_name is None:
|
||||
output_name = tga_path.stem
|
||||
|
||||
png_path = self.output_dir / f"{output_name}.png"
|
||||
image.save(png_path, 'PNG')
|
||||
# Save as PNG or WebP
|
||||
if output_format.lower() == 'webp':
|
||||
output_path = self.output_dir / f"{output_name}.webp"
|
||||
# WebP with good quality and lossless option for sharp icons
|
||||
image.save(output_path, 'WEBP', quality=95, method=6)
|
||||
else:
|
||||
output_path = self.output_dir / f"{output_name}.png"
|
||||
image.save(output_path, 'PNG')
|
||||
|
||||
logger.info(f"Converted (PIL): {tga_path.name} -> {png_path.name}")
|
||||
return png_path
|
||||
logger.info(f"Converted (PIL): {tga_path.name} -> {output_path.name}")
|
||||
return output_path
|
||||
|
||||
except Exception as pil_error:
|
||||
logger.debug(f"PIL failed, trying manual: {pil_error}")
|
||||
|
|
@ -322,29 +330,36 @@ class TGAConverter:
|
|||
else:
|
||||
image = Image.fromarray(pixels, 'RGB')
|
||||
|
||||
# Determine output path
|
||||
# Determine output path and format
|
||||
if output_name is None:
|
||||
output_name = tga_path.stem
|
||||
|
||||
png_path = self.output_dir / f"{output_name}.png"
|
||||
image.save(png_path, 'PNG')
|
||||
# Save as PNG or WebP
|
||||
if output_format.lower() == 'webp':
|
||||
output_path = self.output_dir / f"{output_name}.webp"
|
||||
image.save(output_path, 'WEBP', quality=95, method=6)
|
||||
else:
|
||||
output_path = self.output_dir / f"{output_name}.png"
|
||||
image.save(output_path, 'PNG')
|
||||
|
||||
logger.info(f"Converted (manual): {tga_path.name} -> {png_path.name}")
|
||||
return png_path
|
||||
logger.info(f"Converted (manual): {tga_path.name} -> {output_path.name}")
|
||||
return output_path
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error converting {tga_path}: {e}")
|
||||
return None
|
||||
|
||||
def convert_cache_folder(self, cache_path: Optional[Path] = None) -> Dict[str, Path]:
|
||||
def convert_cache_folder(self, cache_path: Optional[Path] = None,
|
||||
output_format: str = 'png') -> Dict[str, Path]:
|
||||
"""
|
||||
Convert all TGA files in cache folder to PNG.
|
||||
Convert all TGA files in cache folder to PNG or WebP.
|
||||
|
||||
Args:
|
||||
cache_path: Path to cache folder (auto-detect if None)
|
||||
output_format: 'png' or 'webp'
|
||||
|
||||
Returns:
|
||||
Dictionary mapping TGA filenames to PNG paths
|
||||
Dictionary mapping TGA filenames to output paths
|
||||
"""
|
||||
results = {}
|
||||
|
||||
|
|
@ -362,14 +377,14 @@ class TGAConverter:
|
|||
logger.warning(f"No .tga files found in: {cache_path}")
|
||||
return results
|
||||
|
||||
logger.info(f"Found {len(tga_files)} TGA files to convert")
|
||||
logger.info(f"Found {len(tga_files)} TGA files to convert to {output_format.upper()}")
|
||||
|
||||
# Convert each file
|
||||
for i, tga_path in enumerate(tga_files, 1):
|
||||
logger.info(f"[{i}/{len(tga_files)}] Converting: {tga_path.name}")
|
||||
png_path = self.convert_tga_to_png(tga_path)
|
||||
if png_path:
|
||||
results[tga_path.name] = png_path
|
||||
output_path = self.convert_tga_to_png(tga_path, output_format=output_format)
|
||||
if output_path:
|
||||
results[tga_path.name] = output_path
|
||||
|
||||
logger.info(f"Converted {len(results)}/{len(tga_files)} files successfully")
|
||||
logger.info(f"Output directory: {self.output_dir}")
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from PyQt6.QtWidgets import (
|
|||
QDialog, QVBoxLayout, QHBoxLayout, QGridLayout,
|
||||
QLabel, QPushButton, QListWidget, QListWidgetItem,
|
||||
QGroupBox, QProgressBar, QTextEdit, QFileDialog,
|
||||
QMessageBox, QSpinBox, QCheckBox
|
||||
QMessageBox, QSpinBox, QCheckBox, QComboBox
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
||||
from PyQt6.QtGui import QPixmap
|
||||
|
|
@ -25,14 +25,16 @@ class TGAConvertWorker(QThread):
|
|||
"""Background worker for TGA conversion."""
|
||||
|
||||
progress_update = pyqtSignal(str)
|
||||
file_converted = pyqtSignal(str, str) # tga_name, png_path
|
||||
file_converted = pyqtSignal(str, str) # tga_name, output_path
|
||||
conversion_complete = pyqtSignal(int, int) # success_count, total_count
|
||||
conversion_error = pyqtSignal(str)
|
||||
|
||||
def __init__(self, converter: TGAConverter, cache_path: Optional[Path] = None):
|
||||
def __init__(self, converter: TGAConverter, cache_path: Optional[Path] = None,
|
||||
output_format: str = 'png'):
|
||||
super().__init__()
|
||||
self.converter = converter
|
||||
self.cache_path = cache_path
|
||||
self.output_format = output_format
|
||||
self._is_running = True
|
||||
|
||||
def run(self):
|
||||
|
|
@ -55,7 +57,7 @@ class TGAConvertWorker(QThread):
|
|||
total = len(tga_files)
|
||||
success = 0
|
||||
|
||||
self.progress_update.emit(f"Found {total} TGA files")
|
||||
self.progress_update.emit(f"Found {total} TGA files to convert to {self.output_format.upper()}")
|
||||
|
||||
for i, tga_path in enumerate(tga_files):
|
||||
if not self._is_running:
|
||||
|
|
@ -63,10 +65,10 @@ class TGAConvertWorker(QThread):
|
|||
|
||||
self.progress_update.emit(f"[{i+1}/{total}] Converting: {tga_path.name}")
|
||||
|
||||
png_path = self.converter.convert_tga_to_png(tga_path)
|
||||
if png_path:
|
||||
output_path = self.converter.convert_tga_to_png(tga_path, output_format=self.output_format)
|
||||
if output_path:
|
||||
success += 1
|
||||
self.file_converted.emit(tga_path.name, str(png_path))
|
||||
self.file_converted.emit(tga_path.name, str(output_path))
|
||||
|
||||
self.conversion_complete.emit(success, total)
|
||||
|
||||
|
|
@ -111,8 +113,9 @@ class TGAConverterDialog(QDialog):
|
|||
layout.addWidget(header)
|
||||
|
||||
desc = QLabel(
|
||||
"Convert Entropia Universe cached .tga icon files to PNG format.\n"
|
||||
"The game stores item icons in the cache folder as .tga files."
|
||||
"Convert Entropia Universe cached .tga icon files to PNG or WebP format.\n"
|
||||
"The game stores item icons in the cache folder as .tga files.\n"
|
||||
"WebP format is recommended for web uploads (smaller file size, better quality)."
|
||||
)
|
||||
desc.setStyleSheet("color: #888; padding: 5px;")
|
||||
desc.setWordWrap(True)
|
||||
|
|
@ -168,6 +171,16 @@ class TGAConverterDialog(QDialog):
|
|||
output_browse.clicked.connect(self._browse_output_folder)
|
||||
output_layout.addWidget(output_browse)
|
||||
|
||||
output_layout.addSpacing(20)
|
||||
|
||||
# Format selector
|
||||
output_layout.addWidget(QLabel("Format:"))
|
||||
self.format_combo = QComboBox()
|
||||
self.format_combo.addItem("PNG", "png")
|
||||
self.format_combo.addItem("WebP (better for web)", "webp")
|
||||
self.format_combo.currentIndexChanged.connect(self._on_format_changed)
|
||||
output_layout.addWidget(self.format_combo)
|
||||
|
||||
layout.addWidget(output_group)
|
||||
|
||||
# Convert button
|
||||
|
|
@ -275,6 +288,14 @@ class TGAConverterDialog(QDialog):
|
|||
self.converter.output_dir = Path(dir_path)
|
||||
self.output_path_label.setText(dir_path)
|
||||
|
||||
def _on_format_changed(self):
|
||||
"""Handle output format change."""
|
||||
output_format = self.format_combo.currentData()
|
||||
if output_format == 'webp':
|
||||
self.convert_btn.setText("🚀 Convert All to WebP")
|
||||
else:
|
||||
self.convert_btn.setText("🚀 Convert All to PNG")
|
||||
|
||||
def _start_conversion(self):
|
||||
"""Start TGA to PNG conversion."""
|
||||
if self.convert_worker and self.convert_worker.isRunning():
|
||||
|
|
@ -286,9 +307,10 @@ class TGAConverterDialog(QDialog):
|
|||
self.results_list.clear()
|
||||
self.converted_files = []
|
||||
|
||||
# Start worker with the selected cache path
|
||||
# Start worker with the selected cache path and format
|
||||
cache_path = self.converter._cache_path if self.converter._cache_path else None
|
||||
self.convert_worker = TGAConvertWorker(self.converter, cache_path)
|
||||
output_format = self.format_combo.currentData()
|
||||
self.convert_worker = TGAConvertWorker(self.converter, cache_path, output_format)
|
||||
self.convert_worker.progress_update.connect(self._on_progress)
|
||||
self.convert_worker.file_converted.connect(self._on_file_converted)
|
||||
self.convert_worker.conversion_complete.connect(self._on_conversion_complete)
|
||||
|
|
|
|||
Loading…
Reference in New Issue