Guest User

Fix Paladins Normals

a guest
Dec 18th, 2019
435
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/bin/python3
  2. import os
  3. import sys
  4. import math
  5. import re
  6.  
  7. DDS_HEADER_SIZE = 0x80
  8.  
  9. # Convert [-7F,+7F] to [0,FF]
  10. def signed_to_colour(signed_byte):
  11.     signed_int = int.from_bytes(signed_byte, "little", signed=True)
  12.     unsigned_int = signed_int + 128
  13.    
  14.     return signed_int, unsigned_int.to_bytes(1, "little")
  15.  
  16. # Infer correct size from the file size
  17. def get_image_dimensions(path):
  18.     dds_file_size = os.path.getsize(path)
  19.     size = (dds_file_size - DDS_HEADER_SIZE) / 2
  20.     size = math.floor(math.sqrt(size))
  21.     return size
  22.  
  23. def get_output_filename(filepath_in):
  24.     match = re.fullmatch(r"(.*)\.dds", filepath_in)
  25.    
  26.     if match is None:
  27.         # Failed to parse filename, return a default filename
  28.         sys.stderr.write("Warning: failed to generate output filename, writing to out.tga\n")
  29.         return "out.tga"
  30.    
  31.     return match[1] + ".tga"
  32.  
  33. def convert_normal_map(filepath_in, filepath_out=None):
  34.     if filepath_out is None:
  35.         filepath_out = get_output_filename(filepath_in)
  36.    
  37.     size = get_image_dimensions(filepath_in)
  38.  
  39.     file_in = open(filepath_in, "rb")
  40.     file_out = open(filepath_out, "wb")
  41.  
  42.     # Bytes in a row in the dds file
  43.     row_size = size * 2
  44.  
  45.     # skip over current header
  46.     file_in.seek(DDS_HEADER_SIZE)
  47.  
  48.     # TGA header
  49.     file_out.write(bytes([
  50.             0, # id
  51.             0, # colour map (none)
  52.             2, # uncompressed true colour
  53.             0, 0, 0, 0, 0, # colour map metadata
  54.            
  55.             # Image spec
  56.             0, 0, 0, 0, # image origin
  57.         ])
  58.             + size.to_bytes(2, "little") # width
  59.             + size.to_bytes(2, "little") # height
  60.             + bytes([24, # bits/pixel
  61.             0, # image descriptor (idk either, but zero seems to work)
  62.         ]))
  63.  
  64.     for row_id in range(size):
  65.         # DDS starts at the top left, but TGA starts at the bottom left,
  66.         # so read from the dds file starting at the last row
  67.         dds_row_id = size - 1 - row_id
  68.         file_in.seek(DDS_HEADER_SIZE + dds_row_id * row_size)
  69.        
  70.         for pixel_id in range(size):
  71.             r_in = file_in.read(1)
  72.             g_in = file_in.read(1)
  73.            
  74.             r_raw, r_byte = signed_to_colour(r_in)
  75.             g_raw, g_byte = signed_to_colour(g_in)
  76.            
  77.             # calculate blue/z value to normalise vector size
  78.             b_raw = 128 * 128 - r_raw * r_raw - g_raw * g_raw
  79.             if b_raw > 0:
  80.                 b_raw **= 0.5
  81.                 b_raw = math.floor(b_raw)
  82.                 b_raw = min(b_raw, 127)
  83.             else:
  84.                 b_raw = 0
  85.            
  86.             b_byte = (b_raw + 128).to_bytes(1, "little")
  87.            
  88.             file_out.write(b_byte + g_byte + r_byte)
  89.  
  90.     file_in.close()
  91.     file_out.close()
  92.  
  93. if __name__ == "__main__":
  94.     args = sys.argv[1:]
  95.    
  96.     if len(args) <= 0:
  97.         sys.stderr.write("No filenames given.\n"
  98.             f"\tUsage: {sys.argv[0]} file1.dds [file2.dds ...]\n")
  99.         sys.exit(1)
  100.    
  101.     for path in args:
  102.         if not os.path.exists(path):
  103.             sys.stderr.write(f"{path} does not exist. skipping...\n")
  104.             continue
  105.        
  106.         convert_normal_map(path)
RAW Paste Data