Skip to content

Instantly share code, notes, and snippets.

@celsowm
Created September 28, 2025 03:29
Show Gist options
  • Select an option

  • Save celsowm/30afee4e38772aa2651b95ee5e0d2258 to your computer and use it in GitHub Desktop.

Select an option

Save celsowm/30afee4e38772aa2651b95ee5e0d2258 to your computer and use it in GitHub Desktop.
tga_fix.py for Saturn Jo Engine
#!/usr/bin/env python3
# tga_fix.py — Converte TGA para 8bpp (indexed) sem RLE e força origem TOP-LEFT.
# Requisitos: pip install pillow
# Uso:
# python tga_fix.py FLR.TGA
# python tga_fix.py FLR.TGA --out FLR_8.TGA
# python tga_fix.py FLR.TGA --no-flip
# python tga_fix.py FLR.TGA --rotate 90 --colors 128
import argparse
from pathlib import Path
from PIL import Image
def force_tga_origin_top_left(path: Path):
"""
Força o bit 5 (0x20) do Image Descriptor (byte 18 = offset 17) para TOP-LEFT.
Mantém os demais bits (incluindo alpha bits).
"""
with open(path, "r+b") as f:
header = bytearray(f.read(18))
if len(header) != 18:
raise ValueError("Arquivo muito curto para TGA.")
img_desc = header[17]
img_desc |= 0x20 # seta TOP (bit 5)
img_desc &= 0xEF # garante left-to-right (limpa bit 4) por segurança
header[17] = img_desc
f.seek(0)
f.write(header)
def main():
ap = argparse.ArgumentParser(description="Converte TGA para 8bpp sem RLE e origem TOP-LEFT.")
ap.add_argument("src", help="Arquivo de entrada .TGA")
ap.add_argument("--out", "-o", help="Arquivo de saída .TGA (default: <nome>_8.TGA)")
ap.add_argument("--colors", type=int, default=256, help="Número de cores (1..256). Default: 256")
ap.add_argument("--rotate", type=int, choices=[0, 90, 180, 270], default=0,
help="Rotação (graus, sentido horário). Default: 0")
ap.add_argument("--no-flip", dest="no_flip", action="store_true",
help="Não aplicar flip vertical (por padrão aplicamos FLIP_TOP_BOTTOM).")
ap.add_argument("--overwrite", action="store_true", help="Sobrescrever arquivo de saída se existir.")
args = ap.parse_args()
src = Path(args.src)
if not src.exists():
raise SystemExit(f"Arquivo não encontrado: {src}")
dst = Path(args.out) if args.out else src.with_name(src.stem + "_8.TGA")
if dst.exists() and not args.overwrite:
raise SystemExit(f"Arquivo de saída já existe: {dst} (use --overwrite)")
im = Image.open(src)
# Garante que os pixels fiquem "para cima"
if not args.no_flip:
im = im.transpose(Image.FLIP_TOP_BOTTOM)
# Rotação (horário)
if args.rotate == 90:
im = im.transpose(Image.ROTATE_270)
elif args.rotate == 180:
im = im.transpose(Image.ROTATE_180)
elif args.rotate == 270:
im = im.transpose(Image.ROTATE_90)
colors = max(1, min(256, args.colors))
im = im.convert("P", palette=Image.ADAPTIVE, colors=colors)
im.save(dst, format="TGA", compress=False)
# >>> Patch no header para TOP-LEFT <<<
force_tga_origin_top_left(dst)
print("OK!")
print(f" Entrada : {src}")
print(f" Saída : {dst}")
print(f" Cores : {colors}")
print(f" Flip Y : {'NÃO' if args.no_flip else 'SIM (forçar TOP-LEFT)'}")
print(f" Rotação : {args.rotate}°")
print(" Origem : TOP-LEFT (header ajustado)")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment