fix: use PIL as primary TGA reader with manual fallback
- PIL handles more TGA formats correctly - Manual parser fixed to properly skip ID and color map fields - Added better error messages with expected vs actual data size - Fallback chain: PIL first, then manual parser
This commit is contained in:
parent
46e84b39e3
commit
14bac40fdf
|
|
@ -188,15 +188,29 @@ class TGAConverter:
|
|||
"""
|
||||
try:
|
||||
with open(tga_file.filepath, 'rb') as f:
|
||||
# Skip header
|
||||
f.seek(18)
|
||||
# Read header to get ID length
|
||||
header = f.read(18)
|
||||
if len(header) < 18:
|
||||
logger.warning(f"Invalid TGA header: {tga_file.filepath}")
|
||||
return None
|
||||
|
||||
id_length = header[0]
|
||||
color_map_type = header[1]
|
||||
image_type = header[2]
|
||||
|
||||
# Skip ID field if present
|
||||
header = f.read(18)
|
||||
id_length = header[0] if len(header) >= 18 else 0
|
||||
if id_length > 0:
|
||||
f.seek(18 + id_length)
|
||||
|
||||
# Skip color map if present
|
||||
if color_map_type == 1:
|
||||
# Read color map spec
|
||||
color_map_offset = 3 # After id_length, color_map_type, image_type
|
||||
color_map_length = struct.unpack('<H', header[color_map_offset+2:color_map_offset+4])[0]
|
||||
color_map_entry_size = header[color_map_offset+4]
|
||||
color_map_size = color_map_length * (color_map_entry_size // 8)
|
||||
f.seek(f.tell() + color_map_size)
|
||||
|
||||
# Calculate pixel data size
|
||||
bytes_per_pixel = tga_file.pixel_depth // 8
|
||||
pixel_data_size = tga_file.width * tga_file.height * bytes_per_pixel
|
||||
|
|
@ -205,8 +219,13 @@ class TGAConverter:
|
|||
pixel_data = f.read(pixel_data_size)
|
||||
|
||||
if len(pixel_data) < pixel_data_size:
|
||||
logger.warning(f"Incomplete TGA pixel data: {tga_file.filepath}")
|
||||
logger.warning(f"Incomplete TGA pixel data: {tga_file.filepath} "
|
||||
f"(got {len(pixel_data)}, expected {pixel_data_size})")
|
||||
# Try to use what we have
|
||||
if len(pixel_data) == 0:
|
||||
return None
|
||||
# Pad with zeros
|
||||
pixel_data = pixel_data + bytes(pixel_data_size - len(pixel_data))
|
||||
|
||||
# Convert to numpy array
|
||||
pixels = np.frombuffer(pixel_data, dtype=np.uint8)
|
||||
|
|
@ -260,7 +279,33 @@ class TGAConverter:
|
|||
return None
|
||||
|
||||
try:
|
||||
# Read TGA
|
||||
# Try PIL first (handles more TGA formats)
|
||||
try:
|
||||
image = Image.open(tga_path)
|
||||
|
||||
# Convert to RGBA if necessary
|
||||
if image.mode in ('RGB', 'RGBA'):
|
||||
# Image is already in good format
|
||||
pass
|
||||
elif image.mode == 'P': # Palette
|
||||
image = image.convert('RGBA')
|
||||
else:
|
||||
image = image.convert('RGBA')
|
||||
|
||||
# Determine output path
|
||||
if output_name is None:
|
||||
output_name = tga_path.stem
|
||||
|
||||
png_path = self.output_dir / f"{output_name}.png"
|
||||
image.save(png_path, 'PNG')
|
||||
|
||||
logger.info(f"Converted (PIL): {tga_path.name} -> {png_path.name}")
|
||||
return png_path
|
||||
|
||||
except Exception as pil_error:
|
||||
logger.debug(f"PIL failed, trying manual: {pil_error}")
|
||||
|
||||
# Fallback to manual TGA reading
|
||||
tga_file = self.read_tga_header(tga_path)
|
||||
if not tga_file:
|
||||
return None
|
||||
|
|
@ -273,10 +318,8 @@ class TGAConverter:
|
|||
|
||||
# Create PIL Image
|
||||
if pixels.shape[2] == 4:
|
||||
# RGBA
|
||||
image = Image.fromarray(pixels, 'RGBA')
|
||||
else:
|
||||
# RGB
|
||||
image = Image.fromarray(pixels, 'RGB')
|
||||
|
||||
# Determine output path
|
||||
|
|
@ -284,11 +327,9 @@ class TGAConverter:
|
|||
output_name = tga_path.stem
|
||||
|
||||
png_path = self.output_dir / f"{output_name}.png"
|
||||
|
||||
# Save as PNG
|
||||
image.save(png_path, 'PNG')
|
||||
|
||||
logger.info(f"Saved: {png_path}")
|
||||
logger.info(f"Converted (manual): {tga_path.name} -> {png_path.name}")
|
||||
return png_path
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
Loading…
Reference in New Issue