Matejstein commited on
Commit
f0a08b9
·
verified ·
1 Parent(s): e02602c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -58
app.py CHANGED
@@ -1,13 +1,17 @@
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
@@ -15,27 +19,21 @@ def make_seamless(pil_img, blend_factor=0.30):
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))
@@ -44,84 +42,113 @@ def create_tiled_preview(pil_img, grid_size=(3, 3)):
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
 
 
1
  import tempfile
2
  import os
3
+ import io
4
  import numpy as np
5
  from PIL import Image
6
  import gradio as gr
7
+ from huggingface_hub import InferenceClient
8
 
9
+ # Initialize Hugging Face Inference Client
10
+ client = InferenceClient()
11
+
12
+ def create_base_seamless_layout(pil_img, blend_factor=0.30):
13
  """
14
+ Creates a shifted structural baseline so the AI can repaint over the seams.
 
15
  """
16
  img = np.array(pil_img.convert("RGB")).astype(np.float32) / 255.0
17
  h, w, c = img.shape
 
19
  # Roll image 50% along both axes
20
  shifted = np.roll(np.roll(img, h // 2, axis=0), w // 2, axis=1)
21
 
22
+ # Smooth crossfade mask
23
  y = np.linspace(-1, 1, h)
24
  x = np.linspace(-1, 1, w)
25
  xx, yy = np.meshgrid(x, y)
26
 
27
+ mask_x = np.clip(np.abs(xx) / blend_factor, 0, 1)
28
+ mask_y = np.clip(np.abs(yy) / blend_factor, 0, 1)
 
 
 
 
29
  mask = np.minimum(mask_x, mask_y)[:, :, np.newaxis]
30
 
 
31
  blended = shifted * mask + img * (1.0 - mask)
32
  result = (np.clip(blended, 0, 1) * 255).astype(np.uint8)
33
  return Image.fromarray(result)
34
 
 
35
  def create_tiled_preview(pil_img, grid_size=(3, 3)):
36
+ """Generates a 3x3 tiled composite."""
37
  w, h = pil_img.size
38
  cols, rows = grid_size
39
  tiled = Image.new("RGB", (w * cols, h * rows))
 
42
  tiled.paste(pil_img, (c * w, r * h))
43
  return tiled
44
 
45
+ def generate_ai_texture(input_image, prompt, negative_prompt, strength, resolution_str, ut99_quantize):
 
46
  if input_image is None:
47
  return None, None, None, None
48
 
 
49
  res_val = int(resolution_str.split("x")[0])
 
 
 
 
 
50
 
51
+ # 1. Structural pre-processing for seamless geometry
52
+ base_seamless = create_base_seamless_layout(input_image)
53
+ resized_input = base_seamless.resize((1024, 1024), Image.Resampling.LANCZOS)
54
+
55
+ # 2. Convert PIL Image to Bytes for AI API call
56
+ img_byte_arr = io.BytesIO()
57
+ resized_input.save(img_byte_arr, format='JPEG')
58
+ img_bytes = img_byte_arr.getvalue()
59
+
60
+ # 3. Call AI Inference API (Image-to-Image Diffusion)
61
+ # The strength controls how heavily the AI repaints structural details
62
+ try:
63
+ ai_image = client.image_to_image(
64
+ image=img_bytes,
65
+ prompt=prompt + ", seamless texture, game asset, detailed, high resolution",
66
+ negative_prompt=negative_prompt + ", seams, borders, frames, text, watermark",
67
+ strength=strength,
68
+ model="stabilityai/stable-diffusion-xl-refiner-1.0"
69
+ )
70
+ except Exception:
71
+ # Fallback model if SDXL refiner queue is full
72
+ ai_image = client.image_to_image(
73
+ image=img_bytes,
74
+ prompt=prompt,
75
+ strength=strength,
76
+ model="runwayml/stable-diffusion-v1-5"
77
+ )
78
+
79
+ # 4. Resize AI output to UT99 Target Resolution
80
+ final_img = ai_image.resize((res_val, res_val), Image.Resampling.LANCZOS)
81
+
82
+ # 5. Create 3x3 Seamless Tiling Preview
83
+ tiled_preview = create_tiled_preview(final_img)
84
+
85
+ # 6. Save UT99 Formats (.PCX and .BMP with 8-bit quantization)
86
  temp_dir = tempfile.mkdtemp()
87
+ pcx_path = os.path.join(temp_dir, "ut99_ai_texture.pcx")
88
+ bmp_path = os.path.join(temp_dir, "ut99_ai_texture.bmp")
89
 
90
  if ut99_quantize:
91
+ quantized = final_img.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
92
+ quantized.save(pcx_path, format="PCX")
93
+ quantized.save(bmp_path, format="BMP")
94
+ display_img = quantized.convert("RGB")
 
95
  else:
96
+ final_img.save(pcx_path, format="PCX")
97
+ final_img.save(bmp_path, format="BMP")
98
+ display_img = final_img
 
 
99
 
100
+ return display_img, tiled_preview, pcx_path, bmp_path
101
 
102
+ # Gradio Interface
103
+ with gr.Blocks(title="UT99 AI Texture Generator") as demo:
104
+ gr.Markdown("# 🤖 UT99 AI Texture Synthesizer")
105
  gr.Markdown(
106
+ "Generates seamless textures from photos, city blocks, and landscapes using AI Image-to-Image diffusion."
 
107
  )
108
 
109
  with gr.Row():
110
  with gr.Column():
111
+ input_img = gr.Image(type="pil", label="Upload Source Photo (City Block / Landscape)")
112
 
113
+ prompt = gr.Textbox(
114
+ value="Weathered rocky canyon terrain, detailed stone texture, Unreal Engine style",
115
+ label="AI Prompt (Describe the style and details to generate)"
 
116
  )
117
 
118
+ negative_prompt = gr.Textbox(
119
+ value="blurry, ugly, distorted, frames, borders",
120
+ label="Negative Prompt"
121
+ )
122
+
123
+ strength = gr.Slider(
124
+ minimum=0.1, maximum=0.9, value=0.45, step=0.05,
125
+ label="AI Generation Strength (Higher = More new details, Lower = Keeps original photo closer)"
126
+ )
127
 
128
+ resolution = gr.Dropdown(
129
+ choices=["128x128", "256x256", "512x512"],
130
+ value="256x256",
131
+ label="Target Resolution (UT99 Engine Power of 2)"
132
  )
133
 
134
  ut99_quantize = gr.Checkbox(
135
  value=True,
136
+ label="Convert to 8-Bit / 256 Colors (.PCX / .BMP)"
137
  )
138
 
139
+ btn = gr.Button("Generate AI Texture", variant="primary")
140
 
141
  with gr.Column():
142
+ output_texture = gr.Image(type="pil", label="AI Generated Texture")
143
  output_preview = gr.Image(type="pil", label="3x3 Seamless Tiling Test")
144
 
145
  with gr.Row():
146
+ pcx_file = gr.File(label="Download .PCX")
147
+ bmp_file = gr.File(label="Download .BMP")
148
 
149
  btn.click(
150
+ fn=generate_ai_texture,
151
+ inputs=[input_img, prompt, negative_prompt, strength, resolution, ut99_quantize],
152
  outputs=[output_texture, output_preview, pcx_file, bmp_file]
153
  )
154