feat: add 3 upscale methods - Sharp Pixels, Smooth HQ4x-style, Photorealistic
- NEAREST: Sharp pixel edges (best for clean pixel art) - HQ4x-style: Integer scale first, then smooth to target with edge enhancement - LANCZOS: Very smooth (best for photorealistic textures) - Added upscale method selector to UI - Each method uses UnsharpMask for enhanced crispness
This commit is contained in:
parent
da52b99a36
commit
0034cc9453
|
|
@ -279,7 +279,7 @@ class TGAConverter:
|
|||
|
||||
def convert_tga_to_png(self, tga_path: Path, output_name: Optional[str] = None,
|
||||
canvas_size: Optional[Tuple[int, int]] = None,
|
||||
upscale: bool = False) -> Optional[Path]:
|
||||
upscale: bool = False, upscale_method: str = 'nearest') -> Optional[Path]:
|
||||
"""
|
||||
Convert a TGA file to PNG with optional canvas sizing.
|
||||
|
||||
|
|
@ -312,7 +312,7 @@ class TGAConverter:
|
|||
|
||||
# Apply canvas sizing if requested
|
||||
if canvas_size:
|
||||
image = self._apply_canvas(image, canvas_size, upscale)
|
||||
image = self._apply_canvas(image, canvas_size, upscale, upscale_method)
|
||||
|
||||
# Determine output path and format
|
||||
if output_name is None:
|
||||
|
|
@ -347,7 +347,7 @@ class TGAConverter:
|
|||
|
||||
# Apply canvas sizing if requested
|
||||
if canvas_size:
|
||||
image = self._apply_canvas(image, canvas_size, upscale)
|
||||
image = self._apply_canvas(image, canvas_size, upscale, upscale_method)
|
||||
|
||||
# Determine output path and format
|
||||
if output_name is None:
|
||||
|
|
@ -408,7 +408,7 @@ class TGAConverter:
|
|||
|
||||
return results
|
||||
|
||||
def _apply_canvas(self, image, canvas_size, upscale=False):
|
||||
def _apply_canvas(self, image, canvas_size, upscale=False, upscale_method='nearest'):
|
||||
"""
|
||||
Place image centered on a canvas of specified size.
|
||||
|
||||
|
|
@ -416,6 +416,7 @@ class TGAConverter:
|
|||
image: Source PIL Image
|
||||
canvas_size: (width, height) for output canvas
|
||||
upscale: Whether to upscale small images to fit canvas better
|
||||
upscale_method: 'nearest' (sharp pixels), 'hq4x' (smoothed), or 'lanczos' (smooth)
|
||||
|
||||
Returns:
|
||||
New image with canvas size, source image centered
|
||||
|
|
@ -435,17 +436,34 @@ class TGAConverter:
|
|||
scale = min(max_size / img_w, max_size / img_h)
|
||||
|
||||
if scale > 1: # Only upscale, never downscale
|
||||
# For pixel art/game icons, use integer scaling + NEAREST for sharpness
|
||||
# Round scale to nearest integer for clean pixel edges
|
||||
int_scale = max(1, round(scale))
|
||||
new_w = img_w * int_scale
|
||||
new_h = img_h * int_scale
|
||||
new_w = int(img_w * scale)
|
||||
new_h = int(img_h * scale)
|
||||
|
||||
# Use NEAREST neighbor for pixel art - keeps edges sharp
|
||||
image = image.resize((new_w, new_h), Image.Resampling.NEAREST)
|
||||
if upscale_method == 'nearest':
|
||||
# NEAREST: Sharp pixels, best for clean pixel art
|
||||
image = image.resize((new_w, new_h), Image.Resampling.NEAREST)
|
||||
|
||||
# Apply slight sharpening to reduce blur from the upscale
|
||||
image = image.filter(ImageFilter.SHARPEN)
|
||||
elif upscale_method == 'hq4x':
|
||||
# HQ4x-style: Integer scale first, then smooth to target
|
||||
int_scale = max(2, int(scale))
|
||||
temp_w = img_w * int_scale
|
||||
temp_h = img_h * int_scale
|
||||
|
||||
# Scale up with NEAREST (integer multiple)
|
||||
image = image.resize((temp_w, temp_h), Image.Resampling.NEAREST)
|
||||
|
||||
# Then downscale to target with LANCZOS for smoothing
|
||||
image = image.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
||||
|
||||
# Enhance edges
|
||||
image = image.filter(ImageFilter.EDGE_ENHANCE_MORE)
|
||||
|
||||
else: # lanczos
|
||||
# LANCZOS: Smooth, best for photorealistic textures
|
||||
image = image.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
||||
|
||||
# Apply subtle sharpening for crispness
|
||||
image = image.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3))
|
||||
|
||||
img_w, img_h = new_w, new_h
|
||||
|
||||
|
|
|
|||
|
|
@ -30,12 +30,13 @@ class TGAConvertWorker(QThread):
|
|||
conversion_error = pyqtSignal(str)
|
||||
|
||||
def __init__(self, converter: TGAConverter, cache_path: Optional[Path] = None,
|
||||
canvas_size=None, upscale: bool = False):
|
||||
canvas_size=None, upscale: bool = False, upscale_method: str = 'nearest'):
|
||||
super().__init__()
|
||||
self.converter = converter
|
||||
self.cache_path = cache_path
|
||||
self.canvas_size = canvas_size
|
||||
self.upscale = upscale
|
||||
self.upscale_method = upscale_method
|
||||
self._is_running = True
|
||||
|
||||
def run(self):
|
||||
|
|
@ -70,7 +71,8 @@ class TGAConvertWorker(QThread):
|
|||
output_path = self.converter.convert_tga_to_png(
|
||||
tga_path,
|
||||
canvas_size=self.canvas_size,
|
||||
upscale=self.upscale
|
||||
upscale=self.upscale,
|
||||
upscale_method=self.upscale_method
|
||||
)
|
||||
if output_path:
|
||||
success += 1
|
||||
|
|
@ -197,10 +199,17 @@ class TGAConverterDialog(QDialog):
|
|||
|
||||
canvas_layout.addSpacing(20)
|
||||
|
||||
self.upscale_check = QCheckBox("Upscale small icons")
|
||||
self.upscale_check.setChecked(True)
|
||||
self.upscale_check.setToolTip("Scale up small icons to better fill the canvas")
|
||||
canvas_layout.addWidget(self.upscale_check)
|
||||
canvas_layout.addWidget(QLabel("Upscale:"))
|
||||
self.upscale_method_combo = QComboBox()
|
||||
self.upscale_method_combo.addItem("Sharp Pixels (NEAREST)", "nearest")
|
||||
self.upscale_method_combo.addItem("Smooth (HQ4x-style)", "hq4x")
|
||||
self.upscale_method_combo.addItem("Photorealistic (LANCZOS)", "lanczos")
|
||||
self.upscale_method_combo.setToolTip(
|
||||
"NEAREST: Sharp pixel edges (best for pixel art)\n"
|
||||
"HQ4x: Smooth but keeps details (best for game icons)\n"
|
||||
"LANCZOS: Very smooth (best for photos)"
|
||||
)
|
||||
canvas_layout.addWidget(self.upscale_method_combo)
|
||||
|
||||
canvas_layout.addStretch()
|
||||
layout.addWidget(canvas_group)
|
||||
|
|
@ -323,11 +332,14 @@ class TGAConverterDialog(QDialog):
|
|||
|
||||
# Get canvas settings
|
||||
canvas_size = self.canvas_combo.currentData()
|
||||
upscale = self.upscale_check.isChecked()
|
||||
upscale_method = self.upscale_method_combo.currentData()
|
||||
|
||||
# Start worker with settings
|
||||
cache_path = self.converter._cache_path if self.converter._cache_path else None
|
||||
self.convert_worker = TGAConvertWorker(self.converter, cache_path, canvas_size, upscale)
|
||||
self.convert_worker = TGAConvertWorker(
|
||||
self.converter, cache_path, canvas_size,
|
||||
upscale=True, upscale_method=upscale_method
|
||||
)
|
||||
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