# Grayish-white versions of the Sci-Fi Panels maps (asked for by the user on 2026-10-12: # "Do the textures grayish white"). # # _base.png + _orm.png -> _base_gw.png, _orm_gw.png (same folder, originals unchanged) # # Base colour: the pack has three flat tones, black paint (0.133), grey metal (~0.43) and white paint (0.965). # TONE maps them into one light range, so every panel reads grayish white and its design stays as a soft # two-tone. The pack's ambient-occlusion channel (ORM red) is multiplied in, so slots, vents, seams and holes # stay visible as grey lines. # ORM: only the metallic channel (blue) changes. The pack marks its paint as half metal (0.5) and its bare # metal as 0.8; a light colour with that much metal mirrors the surroundings and takes their colour instead # of looking white. Paint becomes 0, metal becomes METAL_OUT[1]. Occlusion and roughness are kept. # Normal maps are used as they are. # # Run: python make_graywhite.py [folder] (default: the folder this file is in) # The same recipe works on the 4K originals once they are named _base.png / _orm.png. import os, sys, glob import numpy as np from PIL import Image # sRGB-encoded input value -> sRGB-encoded output value (piecewise linear) TONE = [(0.000, 0.40), # open holes of the gratings: stay clearly darker (0.133, 0.68), # black paint -> light grey (0.430, 0.77), # grey metal -> pale grey (0.965, 0.88), # white paint -> off-white (1.000, 0.885)] AO_STRENGTH = 0.85 # how much of the occlusion map is multiplied into the colour (0 = none) METAL_IN = (0.5, 0.8) # the pack's metallic values for paint and for bare metal METAL_OUT = (0.0, 0.3) # what they become def convert(base_path, orm_path, out_base, out_orm): a = np.asarray(Image.open(base_path).convert('RGB'), dtype=np.float32) / 255.0 y = 0.2126 * a[..., 0] + 0.7152 * a[..., 1] + 0.0722 * a[..., 2] xs, ys = zip(*TONE) out = np.interp(y, xs, ys) if os.path.exists(orm_path): orm_im = Image.open(orm_path).convert('RGB') o = np.asarray(orm_im, dtype=np.float32) / 255.0 ao = orm_im.resize((a.shape[1], a.shape[0]), Image.LANCZOS) if orm_im.size != (a.shape[1], a.shape[0]) else orm_im ao = np.asarray(ao, dtype=np.float32)[..., 0] / 255.0 out = out * (1.0 - AO_STRENGTH * (1.0 - ao)) o = o.copy() o[..., 2] = np.interp(o[..., 2], METAL_IN, METAL_OUT) Image.fromarray(np.clip(o * 255.0 + 0.5, 0, 255).astype(np.uint8), 'RGB').save(out_orm, optimize=True) g = np.clip(out * 255.0 + 0.5, 0, 255).astype(np.uint8) Image.merge('RGB', (Image.fromarray(g),) * 3).save(out_base, optimize=True) return float(out.mean()) if __name__ == '__main__': folder = sys.argv[1] if len(sys.argv) > 1 else os.path.dirname(os.path.abspath(__file__)) for f in sorted(glob.glob(os.path.join(folder, '*_base.png'))): key = os.path.basename(f)[:-len('_base.png')] m = convert(f, os.path.join(folder, key + '_orm.png'), os.path.join(folder, key + '_base_gw.png'), os.path.join(folder, key + '_orm_gw.png')) print('%-10s mean %.3f' % (key, m))