Matejstein commited on
Commit
bd5ce6c
·
verified ·
1 Parent(s): 908a430

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -0
app.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tempfile
2
+ import os
3
+ import numpy as np
4
+ from PIL import Image
5
+ import gradio as gr
6
+
7
+ def make_seamless(pil_img, blend_factor=0.30):
8
+ """
9
+ Seamless tiling algorithm:
10
+ Shifts edges to the center and cross-fades original continuous regions over seams.
11
+ """
12
+ img = np.array(pil_img.convert("RGB")).astype(np.float32) / 255.0
13
+ h, w, c = img.shape
14
+
15
+ # Roll image 50% along both axes
16
+ shifted = np.roll(np.roll(img, h // 2, axis=0), w // 2, axis=1)
17
+
18
+ # Calculate distance from central cross seam
19
+ y = np.linspace(-1, 1, h)
20
+ x = np.linspace(-1, 1, w)
21
+ xx, yy = np.meshgrid(x, y)
22
+
23
+ dist_x = np.abs(xx)
24
+ dist_y = np.abs(yy)
25
+
26
+ # Smooth crossfade mask
27
+ mask_x = np.clip(dist_x / blend_factor, 0, 1)
28
+ mask_y = np.clip(dist_y / blend_factor, 0, 1)
29
+ mask = np.minimum(mask_x, mask_y)[:, :, np.newaxis]
30
+
31
+ # Blend original over the shifted seams
32
+ blended = shifted * mask + img * (1.0 - mask)
33
+ result = (np.clip(blended, 0, 1) * 255).astype(np.uint8)
34
+ return Image.fromarray(result)
35
+
36
+
37
+ def create_tiled_preview(pil_img, grid_size=(3, 3)):
38
+ """Creates a 3x3 tiled composite to check seamless borders."""
39
+ w, h = pil_img.size
40
+ cols, rows = grid_size
41
+ tiled = Image.new("RGB", (w * cols, h * rows))
42
+ for r in range(rows):
43
+ for c in range(cols):
44
+ tiled.paste(pil_img, (c * w, r * h))
45
+ return tiled
46
+
47
+
48
+ def process_texture(input_image, resolution_str, enable_seamless, blend_width, ut99_quantize):
49
+ if input_image is None:
50
+ return None, None, None, None
51
+
52
+ # Parse power-of-2 resolution target for UT99
53
+ res_val = int(resolution_str.split("x")[0])
54
+ img = input_image.resize((res_val, res_val), Image.Resampling.LANCZOS)
55
+
56
+ # Apply seamless algorithm
57
+ if enable_seamless:
58
+ img = make_seamless(img, blend_factor=blend_width / 100.0)
59
+
60
+ # Generate 3x3 Tiling Preview before palette reduction
61
+ tiled_preview = create_tiled_preview(img)
62
+
63
+ # Prepare export formats
64
+ temp_dir = tempfile.mkdtemp()
65
+ pcx_path = os.path.join(temp_dir, "ut99_texture.pcx")
66
+ bmp_path = os.path.join(temp_dir, "ut99_texture.bmp")
67
+
68
+ if ut99_quantize:
69
+ # UT99 requires 8-bit indexed 256 color palette
70
+ img_quant = img.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
71
+ img_quant.save(pcx_path, format="PCX")
72
+ img_quant.save(bmp_path, format="BMP")
73
+ final_preview = img_quant.convert("RGB")
74
+ else:
75
+ img.save(pcx_path, format="PCX")
76
+ img.save(bmp_path, format="BMP")
77
+ final_preview = img
78
+
79
+ return final_preview, tiled_preview, pcx_path, bmp_path
80
+
81
+
82
+ # Gradio Interface Setup
83
+ with gr.Blocks(title="UT99 Shameless Texture Forge") as demo:
84
+ gr.Markdown("# 🎮 UT99 Shameless Photo-to-Texture Converter")
85
+ gr.Markdown(
86
+ "Convert photos, street facades, and city blocks into seamless, "
87
+ "UT99-ready 8-bit indexed textures (.PCX / .BMP)."
88
+ )
89
+
90
+ with gr.Row():
91
+ with gr.Column():
92
+ input_img = gr.Image(type="pil", label="Upload Source Photo")
93
+
94
+ resolution = gr.Dropdown(
95
+ choices=["128x128", "256x256", "512x512"],
96
+ value="256x256",
97
+ label="Target Resolution (Power of 2 for UT99 Engine)"
98
+ )
99
+
100
+ enable_seamless = gr.Checkbox(value=True, label="Enable Shameless Seamless Tiling")
101
+
102
+ blend_width = gr.Slider(
103
+ minimum=10, maximum=50, value=30, step=1,
104
+ label="Seam Crossfade Width (%)"
105
+ )
106
+
107
+ ut99_quantize = gr.Checkbox(
108
+ value=True,
109
+ label="Convert to 8-Bit / 256 Colors (Required for UT99 .PCX)"
110
+ )
111
+
112
+ btn = gr.Button("Generate UT99 Texture", variant="primary")
113
+
114
+ with gr.Column():
115
+ output_texture = gr.Image(type="pil", label="Single Texture Output")
116
+ output_preview = gr.Image(type="pil", label="3x3 Seamless Tiling Test")
117
+
118
+ with gr.Row():
119
+ pcx_file = gr.File(label="Download .PCX (UnrealEd Standard)")
120
+ bmp_file = gr.File(label="Download .BMP (8-bit)")
121
+
122
+ btn.click(
123
+ fn=process_texture,
124
+ inputs=[input_img, resolution, enable_seamless, blend_width, ut99_quantize],
125
+ outputs=[output_texture, output_preview, pcx_file, bmp_file]
126
+ )
127
+
128
+ if __name__ == "__main__":
129
+ demo.launch()
130
+