Files
1960-utils/Texturing/ConvertOGLDX/convertogldx.py

166 lines
5.3 KiB
Python
Raw Permalink Normal View History

# normalmap_to_directx_filtered.py
# Drag & drop image files or folders onto the EXE.
# Default: FORCE convert to DirectX (Y) by flipping green.
# Suffix filter: only process files whose *basename* ends with one of: _n, _nrm, _normal (case-insensitive).
# Flags:
# --detect -> flip only if image looks OpenGL (+Y)
# --all -> ignore suffix filter; process all supported images
# --suffixes "a,b,c" -> comma-separated list of suffixes (without extensions)
import sys, os
from PIL import Image
import numpy as np
SUPPORTED_EXTS = {".png", ".tga", ".jpg", ".jpeg", ".tif", ".tiff", ".bmp"}
OUT_SUBFOLDER = "DirectX_Converted"
DEFAULT_SUFFIXES = ["_n", "_nrm", "_normal"] # case-insensitive
def is_image(p):
return os.path.splitext(p)[1].lower() in SUPPORTED_EXTS
def iter_inputs(paths):
for p in paths:
if os.path.isdir(p):
for root, _, files in os.walk(p):
for f in files:
fp = os.path.join(root, f)
if is_image(fp):
yield fp
else:
if is_image(p):
yield p
def has_normal_suffix(path, suffixes):
stem = os.path.splitext(os.path.basename(path))[0].lower()
return any(stem.endswith(suf.lower()) for suf in suffixes)
def flip_green(img_rgba):
r, g, b, a = img_rgba.split()
g = g.point(lambda i: 255 - i)
return Image.merge("RGBA", (r, g, b, a))
def analyze_mean_y(img_rgba):
_, g, b, _ = img_rgba.split()
g_np = np.array(g, dtype=np.float32)
b_np = np.array(b, dtype=np.float32)
y = (g_np / 255.0) * 2.0 - 1.0
flat_mask = b_np > 240
y_use = y[~flat_mask] if (~flat_mask).any() else y
return float(y_use.mean())
def ensure_directx(img_rgba, detect_mode: bool):
if not detect_mode:
return flip_green(img_rgba), True, None # forced
mean_y = analyze_mean_y(img_rgba)
if mean_y >= 0.0:
return flip_green(img_rgba), True, mean_y
else:
return img_rgba, False, mean_y
def output_path(src_path):
folder, base = os.path.split(src_path)
out_dir = os.path.join(folder, OUT_SUBFOLDER)
os.makedirs(out_dir, exist_ok=True)
return os.path.join(out_dir, base)
def save_preserving_format(out_img_rgba, src_path, had_alpha):
_, ext = os.path.splitext(src_path)
ext = ext.lower()
if not had_alpha or ext in {".jpg", ".jpeg", ".bmp"}:
out_img = out_img_rgba.convert("RGB")
else:
out_img = out_img_rgba
dst = output_path(src_path)
save_kwargs, fmt = {}, None
if ext in {".jpg", ".jpeg"}:
save_kwargs["quality"] = 95
fmt = "JPEG"
elif ext == ".png":
fmt = "PNG"
elif ext == ".tga":
fmt = "TGA"
elif ext in {".tif", ".tiff"}:
fmt = "TIFF"
elif ext == ".bmp":
fmt = "BMP"
else:
dst = os.path.splitext(dst)[0] + ".png"
fmt = "PNG"
out_img.save(dst, format=fmt, **save_kwargs)
return dst
def process_one(path, detect_mode: bool):
try:
src = Image.open(path)
had_alpha = src.mode in ("LA", "RGBA", "PA")
img = src.convert("RGBA")
out_img, flipped, mean_y = ensure_directx(img, detect_mode)
dst = save_preserving_format(out_img, path, had_alpha)
if detect_mode:
status = "flipped to DirectX (Y)" if flipped else "already DirectX (Y)"
extra = f" meanY={mean_y:+.4f}"
else:
status = "FORCED flip -> DirectX (Y)"
extra = ""
print(f"[OK] {path}\n {status}{extra}\n -> {dst}")
except Exception as e:
print(f"[ERR] {path} :: {e}")
def parse_args(argv):
detect_mode = False
process_all = False
suffixes = DEFAULT_SUFFIXES[:]
paths = []
it = iter(argv)
for a in it:
if a == "--detect":
detect_mode = True
elif a == "--all":
process_all = True
elif a == "--suffixes":
try:
raw = next(it)
suffixes = [s.strip() for s in raw.split(",") if s.strip()]
except StopIteration:
print("[WARN] --suffixes expects a quoted comma-separated list; using defaults.")
else:
paths.append(a)
return detect_mode, process_all, suffixes, paths
def main():
detect_mode, process_all, suffixes, args = parse_args(sys.argv[1:])
if not args:
print("Drag and drop image files or folders onto this EXE.")
print("Options: --detect --all --suffixes \"_n,_nrm,_normal\"")
return # auto-exit
files = list(iter_inputs(args))
if not files:
print("No supported images found.")
return
mode_desc = "DETECT mode (flip only if +Y detected)" if detect_mode else "FORCE mode (flip everything)"
filt_desc = "NO suffix filter (--all)" if process_all else f"Suffix filter: {', '.join(suffixes)}"
print(f"{mode_desc}\n{filt_desc}\nFound {len(files)} file(s) before filtering.\n")
count_total = 0
count_skipped = 0
for p in files:
if not process_all and not has_normal_suffix(p, suffixes):
print(f"[SKIP] {p} (name lacks normal-map suffix)")
count_skipped += 1
continue
count_total += 1
process_one(p, detect_mode)
print(f"\nProcessed: {count_total}, Skipped: {count_skipped}")
if __name__ == "__main__":
main()