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:
LemonNexus 2026-02-11 16:17:31 +00:00
parent 14bac40fdf
commit ac334ac4a8
2 changed files with 68 additions and 31 deletions

View File

@ -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}")

View File

@ -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)