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:
|
try:
|
||||||
with open(tga_file.filepath, 'rb') as f:
|
with open(tga_file.filepath, 'rb') as f:
|
||||||
# Skip header
|
# Read header to get ID length
|
||||||
f.seek(18)
|
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
|
# Skip ID field if present
|
||||||
header = f.read(18)
|
|
||||||
id_length = header[0] if len(header) >= 18 else 0
|
|
||||||
if id_length > 0:
|
if id_length > 0:
|
||||||
f.seek(18 + id_length)
|
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
|
# Calculate pixel data size
|
||||||
bytes_per_pixel = tga_file.pixel_depth // 8
|
bytes_per_pixel = tga_file.pixel_depth // 8
|
||||||
pixel_data_size = tga_file.width * tga_file.height * bytes_per_pixel
|
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)
|
pixel_data = f.read(pixel_data_size)
|
||||||
|
|
||||||
if len(pixel_data) < 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
|
return None
|
||||||
|
# Pad with zeros
|
||||||
|
pixel_data = pixel_data + bytes(pixel_data_size - len(pixel_data))
|
||||||
|
|
||||||
# Convert to numpy array
|
# Convert to numpy array
|
||||||
pixels = np.frombuffer(pixel_data, dtype=np.uint8)
|
pixels = np.frombuffer(pixel_data, dtype=np.uint8)
|
||||||
|
|
@ -260,7 +279,33 @@ class TGAConverter:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
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)
|
tga_file = self.read_tga_header(tga_path)
|
||||||
if not tga_file:
|
if not tga_file:
|
||||||
return None
|
return None
|
||||||
|
|
@ -273,10 +318,8 @@ class TGAConverter:
|
||||||
|
|
||||||
# Create PIL Image
|
# Create PIL Image
|
||||||
if pixels.shape[2] == 4:
|
if pixels.shape[2] == 4:
|
||||||
# RGBA
|
|
||||||
image = Image.fromarray(pixels, 'RGBA')
|
image = Image.fromarray(pixels, 'RGBA')
|
||||||
else:
|
else:
|
||||||
# RGB
|
|
||||||
image = Image.fromarray(pixels, 'RGB')
|
image = Image.fromarray(pixels, 'RGB')
|
||||||
|
|
||||||
# Determine output path
|
# Determine output path
|
||||||
|
|
@ -284,11 +327,9 @@ class TGAConverter:
|
||||||
output_name = tga_path.stem
|
output_name = tga_path.stem
|
||||||
|
|
||||||
png_path = self.output_dir / f"{output_name}.png"
|
png_path = self.output_dir / f"{output_name}.png"
|
||||||
|
|
||||||
# Save as PNG
|
|
||||||
image.save(png_path, '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
|
return png_path
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue