Surae007 commited on
Commit
8a34363
·
verified ·
1 Parent(s): 711ee8a
Files changed (1) hide show
  1. app.py +116 -96
app.py CHANGED
@@ -1,9 +1,9 @@
1
- import os, io, time, json, math
2
  from typing import List, Dict, Optional, Tuple
3
 
4
  import gradio as gr
5
  import numpy as np
6
- from PIL import Image, ImageOps
7
 
8
  import torch
9
  from diffusers import (
@@ -17,52 +17,56 @@ from diffusers import (
17
  EulerAncestralDiscreteScheduler, HeunDiscreteScheduler,
18
  )
19
 
20
- # ---------- Optional deps ----------
 
21
  try:
22
  from rembg import remove as rembg_remove
23
  except Exception:
24
  rembg_remove = None
25
 
26
- # face restore
27
- # ---- Optional face restore (safe import) ----
28
  _HAS_GFP = False
 
 
29
  try:
30
- import gfpgan
31
  if hasattr(gfpgan, "GFPGANer"):
32
  GFPGANer = gfpgan.GFPGANer # type: ignore
33
  _HAS_GFP = True
34
- except Exception:
35
- GFPGANer = None # type: ignore
36
- GFP = None
37
 
38
- # realesrgan (fallback upscaler)
 
 
 
39
  try:
40
- from realesrgan import RealESRGAN
41
  _HAS_REALESRGAN = True
42
- except Exception:
43
- _HAS_REALESRGAN = False
 
 
44
 
45
  device = "cuda" if torch.cuda.is_available() else "cpu"
46
  dtype = torch.float16 if device == "cuda" else torch.float32
47
 
48
- # ---------------- Registry (เปลี่ยน/เพิ่มได้เอง) ----------------
49
  MODELS: List[Tuple[str,str,str]] = [
50
- # (id, label, note)
51
  ("stabilityai/stable-diffusion-xl-base-1.0", "SDXL Base 1.0", "เอนกประสงค์ สมดุล"),
52
  ("stabilityai/stable-diffusion-xl-refiner-1.0","SDXL Refiner", "เก็บรายละเอียด (pass 2)"),
53
- ("SG161222/RealVisXL_V4.0", "RealVis XL v4", "โฟโต้เรียล คน/สินค้าเนียน"),
54
- ("Lykon/dreamshaper-xl-v2", "DreamShaper XL","แฟนตาซี–เรียลลิสติกหลากสไตล์"),
55
  ("RunDiffusion/Juggernaut-XL", "Juggernaut XL", "คอนทราสต์แรง รายละเอียดหนัก"),
56
- ("emilianJR/epiCRealismXL", "EpicRealism XL","แฟชั่น/พอร์เทรตคอนทราสต์ดี"),
57
  ("black-forest-labs/FLUX.1-dev", "FLUX.1-dev", "สมัยใหม่/คุมสไตล์ดี (ไม่ใช่ SDXL)"),
58
  ("stabilityai/sd-turbo", "SD-Turbo", "ไวมาก เหมาะกับร่างไอเดีย"),
59
  ("stabilityai/stable-diffusion-2-1", "SD 2.1", "แลนด์สเคป/องค์ประกอบกว้าง"),
60
- ("runwayml/stable-diffusion-v1-5", "SD 1.5", "คลาสสิก/ทรัพยากรเยอะ"),
61
- ("timbrooks/instruct-pix2pix", "Instruct-Pix2Pix","แก้ภาพตามคำสั่ง (Img2Img)"),
62
  ]
63
 
64
  LORAS: List[Tuple[str,str,str]] = [
65
- # หมายเหตุ: รายชื่อ LoRA แพร่หลายและเปลี่ยนเร็ว; ถ้าโหลดไม่ได้ โปรแกรมจะไม่ล้ม
66
  ("ByteDance/SDXL-Lightning", "SDXL-Lightning", "สปีดเร็ว (LoRA)"),
67
  ("ostris/epicrealism-xl-lora", "EpicrealismXL-LoRA","โทนเรียลลิสติก"),
68
  ("XLabs-AI/flux-prompt-lora", "FLUX Prompt LoRA", "ปรับ prompt style (FLUX)"),
@@ -77,7 +81,6 @@ LORAS: List[Tuple[str,str,str]] = [
77
  ]
78
 
79
  CONTROLNETS: List[Tuple[str,str,str,str]] = [
80
- # (id, label, note, key)
81
  ("diffusers/controlnet-canny-sdxl-1.0", "Canny", "คุมเส้นขอบ", "canny"),
82
  ("diffusers/controlnet-openpose-sdxl-1.0", "OpenPose", "คุมท่าทางคน", "pose"),
83
  ("diffusers/controlnet-depth-sdxl-1.0", "Depth", "คุมมุมมอง/ระยะลึก", "depth"),
@@ -106,12 +109,12 @@ SCHEDULERS = {
106
  "Heun": HeunDiscreteScheduler,
107
  }
108
 
109
- # ---------------- Cache & helpers ----------------
110
  PIPE_CACHE: Dict[str, object] = {}
111
  CONTROL_CACHE: Dict[str, ControlNetModel] = {}
112
  UPSCALE_PIPE: Optional[StableDiffusionUpscalePipeline] = None
113
- REALSR: Optional[RealESRGAN] = None
114
 
 
115
  def set_sched(pipe, name: str):
116
  cls = SCHEDULERS.get(name, DPMSolverMultistepScheduler)
117
  pipe.scheduler = cls.from_config(pipe.scheduler.config)
@@ -119,8 +122,7 @@ def set_sched(pipe, name: str):
119
  def seed_gen(sd: int):
120
  if sd is None or sd < 0: return None
121
  g = torch.Generator(device=device if device=="cuda" else "cpu")
122
- g.manual_seed(int(sd))
123
- return g
124
 
125
  def prep_pipe(model_id: str, control_ids: List[str]):
126
  key = f"{model_id}|{'-'.join(control_ids) if control_ids else 'none'}"
@@ -137,8 +139,7 @@ def prep_pipe(model_id: str, control_ids: List[str]):
137
  pipe = StableDiffusionXLPipeline.from_pretrained(model_id, torch_dtype=dtype, use_safetensors=True)
138
 
139
  if device == "cuda":
140
- pipe.to("cuda")
141
- pipe.enable_vae_tiling(); pipe.enable_vae_slicing()
142
  try: pipe.enable_xformers_memory_efficient_attention()
143
  except Exception: pass
144
  else:
@@ -154,44 +155,46 @@ def apply_loras(pipe, ids: List[str], scales: List[float]):
154
  try:
155
  sc = scales[i] if i < len(scales) else 0.7
156
  pipe.fuse_lora(lora_scale=float(sc))
157
- except Exception: pass
 
158
  except Exception as e:
159
  print(f"[LoRA] load failed {rid}: {e}")
160
 
161
  def to_png_info(meta: dict) -> str:
162
  return json.dumps(meta, ensure_ascii=False, indent=2)
163
 
164
- # ---------------- Optional Post-process ----------------
165
  def ensure_upscalers():
166
  global UPSCALE_PIPE, GFP, REALSR
167
  if UPSCALE_PIPE is None:
168
  try:
169
  UPSCALE_PIPE = StableDiffusionUpscalePipeline.from_pretrained(
170
  "stabilityai/stable-diffusion-x4-upscaler",
171
- torch_dtype=torch.float16 if device=="cuda" else torch.float32,
172
- use_safetensors=True
173
  ).to(device)
174
  except Exception as e:
175
  print("[Upscaler] SD x4 not available:", e)
176
 
177
- if _HAS_GFP and GFP is None:
 
178
  try:
179
  GFP = GFPGANer(model_path=None, upscale=1, arch="clean", channel_multiplier=2)
180
  except Exception as e:
181
  print("[GFPGAN] init failed:", e)
182
 
 
183
  if _HAS_REALESRGAN and REALSR is None and device == "cuda":
184
  try:
185
- REALSR = RealESRGAN(torch.device("cuda"), scale=4)
186
- REALSR.load_weights("weights/RealESRGAN_x4plus.pth") # ใช้ได้เมื่อมีไฟล์ (ถ้าไม่มีจะข้าม)
187
  except Exception as e:
 
188
  print("[RealESRGAN] init failed:", e)
189
 
190
  def post_process(img: Image.Image, do_upscale: bool, do_face: bool, do_rembg: bool):
191
  ensure_upscalers()
192
  out = img
193
 
194
- # Upscale priority: RealESRGAN > SD x4 > none
195
  if do_upscale:
196
  try:
197
  if REALSR is not None:
@@ -207,8 +210,8 @@ def post_process(img: Image.Image, do_upscale: bool, do_face: bool, do_rembg: bo
207
 
208
  if do_face and _HAS_GFP and GFP is not None:
209
  try:
210
- _, _, out = GFP.enhance(np.array(out), has_aligned=False, only_center_face=False, paste_back=True)
211
- out = Image.fromarray(out)
212
  except Exception as e:
213
  print("[GFPGAN] skipped:", e)
214
 
@@ -220,18 +223,19 @@ def post_process(img: Image.Image, do_upscale: bool, do_face: bool, do_rembg: bo
220
 
221
  return out
222
 
223
- # ---------------- Core generate ----------------
224
  def run_txt2img(model_id, custom_model, prompt, preset, negative,
225
  steps, cfg, width, height, scheduler_name, seed,
226
  lora_list, lora_custom_csv, lora_s1, lora_s2, lora_s3,
227
  ctrl_selected, ctrl_images, use_refiner, refine_strength,
228
  do_upscale, do_face, do_rembg):
 
229
  if not prompt.strip(): raise gr.Error("กรุณากรอก prompt")
230
  model = (custom_model.strip() or model_id).strip()
231
  if preset in PRESETS: prompt = prompt + PRESETS[preset]
232
- if not negative.strip(): negative = NEG_DEFAULT
233
 
234
- # Collect control images
235
  cond_imgs, ctrl_ids = [], []
236
  for (cid, label, note, key) in CONTROLNETS:
237
  if label in ctrl_selected and key in ctrl_images and ctrl_images[key] is not None:
@@ -276,7 +280,7 @@ def run_txt2img(model_id, custom_model, prompt, preset, negative,
276
  num_inference_steps=int(steps), guidance_scale=float(cfg),
277
  generator=gen).images[0]
278
 
279
- # Refiner (GPU)
280
  if use_refiner and device == "cuda":
281
  try:
282
  ref = StableDiffusionXLImg2ImgPipeline.from_pretrained(
@@ -285,69 +289,87 @@ def run_txt2img(model_id, custom_model, prompt, preset, negative,
285
  ).to("cuda")
286
  set_sched(ref, scheduler_name)
287
  with torch.autocast("cuda"):
288
- image = ref(prompt=prompt, negative_prompt=negative,
289
- image=image, strength=float(refine_strength),
290
  num_inference_steps=max(10, int(steps)//2),
291
  guidance_scale=float(cfg), generator=gen).images[0]
292
  except Exception as e:
293
  print("[Refiner] skipped:", e)
294
 
295
- # Post-process
296
  image = post_process(image, do_upscale, do_face, do_rembg)
297
-
298
  meta = {
299
- "model": model, "loras": loras, "controlnets": ctrl_selected,
300
- "prompt": prompt, "negative": negative, "size": f"{width}x{height}",
301
- "steps": steps, "cfg": cfg, "scheduler": scheduler_name, "seed": seed,
302
- "post": {"upscale": do_upscale, "face_restore": do_face, "remove_bg": do_rembg}
303
  }
304
  return image, to_png_info(meta)
305
 
306
- def run_img2img(model_id, custom_model, init_image, strength, **kw):
307
- if init_image is None: raise gr.Error("โปรดอัปโหลดภาพเริ่มต้น (init image)")
 
 
308
  model = (custom_model.strip() or model_id).strip()
 
 
 
309
  pipe = StableDiffusionXLImg2ImgPipeline.from_pretrained(model, torch_dtype=dtype, use_safetensors=True)
310
- pipe = pipe.to(device);
311
  try:
312
  if device=="cuda": pipe.enable_xformers_memory_efficient_attention()
313
- except: pass
314
- set_sched(pipe, kw["scheduler_name"]); gen = seed_gen(kw["seed"])
315
- prompt = kw["prompt"] + (PRESETS.get(kw["preset"], "") if kw["preset"] else "")
316
- negative = kw["negative"] or NEG_DEFAULT
317
 
318
  if device=="cuda":
319
  with torch.autocast("cuda"):
320
  img = pipe(prompt=prompt, negative_prompt=negative, image=init_image,
321
- strength=float(strength), num_inference_steps=int(kw["steps"]),
322
- guidance_scale=float(kw["cfg"]), generator=gen).images[0]
323
  else:
324
  img = pipe(prompt=prompt, negative_prompt=negative, image=init_image,
325
- strength=float(strength), num_inference_steps=int(kw["steps"]),
326
- guidance_scale=float(kw["cfg"]), generator=gen).images[0]
327
 
328
- img = post_process(img, kw["do_upscale"], kw["do_face"], kw["do_rembg"])
329
- meta = {"mode":"img2img","model":model,"prompt":prompt,"neg":negative,"steps":kw["steps"],"cfg":kw["cfg"],"seed":kw["seed"],"strength":strength}
 
330
  return img, to_png_info(meta)
331
 
332
  def expand_canvas_for_outpaint(img: Image.Image, expand_px: int, direction: str) -> Tuple[Image.Image, Image.Image]:
333
  w, h = img.size
334
- if direction == "left": new = Image.new("RGBA", (w+expand_px, h), (0,0,0,0)); new.paste(img, (expand_px,0)); mask = Image.new("L", (w+expand_px,h), 0); ImageDraw = ImageDraw if 'ImageDraw' in globals() else __import__('PIL.ImageDraw').ImageDraw; d=ImageDraw.Draw(mask); d.rectangle([0,0,expand_px,h], fill=255)
335
- elif direction == "right": new = Image.new("RGBA",(w+expand_px,h),(0,0,0,0)); new.paste(img,(0,0)); mask=Image.new("L",(w+expand_px,h),0); ImageDraw=ImageDraw if 'ImageDraw' in globals() else __import__('PIL.ImageDraw').ImageDraw; d=ImageDraw.Draw(mask); d.rectangle([w,0,w+expand_px,h], fill=255)
336
- elif direction == "top": new = Image.new("RGBA",(w,h+expand_px),(0,0,0,0)); new.paste(img,(0,expand_px)); mask=Image.new("L",(w,h+expand_px),0); ImageDraw=ImageDraw if 'ImageDraw' in globals() else __import__('PIL.ImageDraw').ImageDraw; d=ImageDraw.Draw(mask); d.rectangle([0,0,w,expand_px], fill=255)
337
- else: new = Image.new("RGBA",(w,h+expand_px),(0,0,0,0)); new.paste(img,(0,0)); mask=Image.new("L",(w,h+expand_px),0); ImageDraw=ImageDraw if 'ImageDraw' in globals() else __import__('PIL.ImageDraw').ImageDraw; d=ImageDraw.Draw(mask); d.rectangle([0,h,w,h+expand_px], fill=255)
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  return new.convert("RGB"), mask
339
 
340
- def run_inpaint_outpaint(model_id, custom_model, base_image, mask_image, mode, expand_px, expand_dir, **kw):
 
 
341
  if base_image is None: raise gr.Error("โปรดอัปโหลดภาพฐาน")
342
  model = (custom_model.strip() or model_id).strip()
 
 
 
343
  pipe = StableDiffusionXLInpaintPipeline.from_pretrained(model, torch_dtype=dtype, use_safetensors=True)
344
- pipe = pipe.to(device);
345
  try:
346
  if device=="cuda": pipe.enable_xformers_memory_efficient_attention()
347
- except: pass
348
- set_sched(pipe, kw["scheduler_name"]); gen = seed_gen(kw["seed"])
349
- prompt = kw["prompt"] + (PRESETS.get(kw["preset"], "") if kw["preset"] else "")
350
- negative = kw["negative"] or NEG_DEFAULT
351
 
352
  if mode == "Outpaint":
353
  base_image, mask_image = expand_canvas_for_outpaint(base_image, int(expand_px), expand_dir)
@@ -356,34 +378,30 @@ def run_inpaint_outpaint(model_id, custom_model, base_image, mask_image, mode, e
356
  with torch.autocast("cuda"):
357
  img = pipe(prompt=prompt, negative_prompt=negative,
358
  image=base_image, mask_image=mask_image,
359
- strength=kw.get("strength", 0.7),
360
- num_inference_steps=int(kw["steps"]),
361
- guidance_scale=float(kw["cfg"]),
362
- generator=gen).images[0]
363
  else:
364
  img = pipe(prompt=prompt, negative_prompt=negative,
365
  image=base_image, mask_image=mask_image,
366
- strength=kw.get("strength", 0.7),
367
- num_inference_steps=int(kw["steps"]),
368
- guidance_scale=float(kw["cfg"]),
369
- generator=gen).images[0]
370
 
371
- img = post_process(img, kw["do_upscale"], kw["do_face"], kw["do_rembg"])
372
- meta = {"mode":mode,"model":model,"prompt":prompt,"steps":kw["steps"],"cfg":kw["cfg"],"seed":kw["seed"]}
373
  return img, to_png_info(meta)
374
 
375
  # ---------------- UI ----------------
376
  def build_ui():
377
  with gr.Blocks(theme=gr.themes.Soft(), title="Masterpiece SDXL Studio Pro") as demo:
378
  gr.Markdown("# 🖼️ Masterpiece SDXL Studio Pro")
379
- gr.Markdown("เลือก **Models/LoRA/ControlNet** ได้หลายรายการ + **Img2Img / Inpaint / Outpaint** + **Upscale/FaceRestore/RemoveBG**")
380
 
381
- # Common widgets
382
  model_dd = gr.Dropdown(choices=[m[0] for m in MODELS], value=MODELS[0][0], label="Model (เลือก)")
383
  model_custom = gr.Textbox(label="Custom Model ID (เช่น username/my-model)", placeholder="(ไม่จำเป็น)")
384
 
385
  preset = gr.Dropdown(choices=list(PRESETS.keys()), value=None, label="Style Preset (optional)")
386
  negative = gr.Textbox(value=NEG_DEFAULT, label="Negative Prompt")
 
387
  steps = gr.Slider(10, 60, 30, step=1, label="Steps")
388
  cfg = gr.Slider(1.0, 12.0, 7.0, step=0.1, label="CFG")
389
  width = gr.Slider(512, 1024, 832, step=64, label="Width")
@@ -392,14 +410,20 @@ def build_ui():
392
  seed = gr.Number(value=-1, precision=0, label="Seed (-1=random)")
393
 
394
  # LoRA
395
- lora_group = gr.CheckboxGroup(choices=[f"{rid} — {lbl} ({note})" for rid,lbl,note in LORAS], label="LoRA (เลือกหลายตัวได้)")
 
 
 
396
  lora_custom = gr.Textbox(label="Custom LoRA IDs (คั่นด้วย comma)")
397
  lora_s1 = gr.Slider(0.0, 1.2, 0.7, 0.05, label="LoRA scale #1")
398
  lora_s2 = gr.Slider(0.0, 1.2, 0.5, 0.05, label="LoRA scale #2")
399
  lora_s3 = gr.Slider(0.0, 1.2, 0.5, 0.05, label="LoRA scale #3")
400
 
401
  # ControlNet
402
- ctrl_group = gr.CheckboxGroup(choices=[c[1]+" ("+c[2]+")" for c in CONTROLNETS], label="ControlNet (เลือกชนิด)")
 
 
 
403
  imgs = {
404
  "canny": gr.Image(type="pil", label="Canny"),
405
  "pose": gr.Image(type="pil", label="OpenPose"),
@@ -447,11 +471,7 @@ def build_ui():
447
 
448
  def parse_lora_list(selected: List[str]) -> List[str]:
449
  if not selected: return []
450
- out = []
451
- for s in selected:
452
- rid = s.split(" — ")[0].strip()
453
- out.append(rid)
454
- return out
455
 
456
  btn_txt.click(
457
  fn=run_txt2img,
@@ -461,7 +481,7 @@ def build_ui():
461
  gr.Variable(parse_lora_list), lora_custom, lora_s1, lora_s2, lora_s3,
462
  ctrl_group,
463
  {k:v for k,v in imgs.items()}, # dict of images
464
- gr.Checkbox(False), gr.Slider(0.05,0.5,0.2,0.05),
465
  do_upscale, do_face, do_rembg
466
  ],
467
  outputs=[out_img_txt, out_meta_txt],
@@ -471,7 +491,7 @@ def build_ui():
471
  btn_i2i.click(
472
  fn=run_img2img,
473
  inputs=[model_dd, model_custom, init_img, strength,
474
- ("prompt",), preset, negative, steps, cfg, width, height, scheduler, seed,
475
  do_upscale, do_face, do_rembg],
476
  outputs=[out_img_i2i, out_meta_i2i],
477
  api_name="img2img"
@@ -480,13 +500,13 @@ def build_ui():
480
  btn_io.click(
481
  fn=run_inpaint_outpaint,
482
  inputs=[model_dd, model_custom, base_img, mask_img, mode_io, expand_px, expand_dir,
483
- ("prompt",), preset, negative, steps, cfg, width, height, scheduler, seed,
484
  strength, do_upscale, do_face, do_rembg],
485
  outputs=[out_img_io, out_meta_io],
486
  api_name="inpaint_outpaint"
487
  )
488
 
489
- gr.Markdown("ℹ️ **หมายเหตุ**: ถ้า LoRA/ControlNet/โพสต์โปรเซสบางตัวไม่มีในสภาพแวดล้อม โปรแกรมจะข้ามให้อัตโนมัติและแจ้งใน Console")
490
 
491
  return demo
492
 
 
1
+ import os, io, time, json
2
  from typing import List, Dict, Optional, Tuple
3
 
4
  import gradio as gr
5
  import numpy as np
6
+ from PIL import Image, ImageDraw
7
 
8
  import torch
9
  from diffusers import (
 
17
  EulerAncestralDiscreteScheduler, HeunDiscreteScheduler,
18
  )
19
 
20
+ # -------- Optional deps (SAFE IMPORTS: ไม่มีโมดูลก็ไม่พัง) --------
21
+ # Remove background
22
  try:
23
  from rembg import remove as rembg_remove
24
  except Exception:
25
  rembg_remove = None
26
 
27
+ # Face restore (GFPGAN)
 
28
  _HAS_GFP = False
29
+ GFPGANer = None
30
+ GFP = None
31
  try:
32
+ import gfpgan # type: ignore
33
  if hasattr(gfpgan, "GFPGANer"):
34
  GFPGANer = gfpgan.GFPGANer # type: ignore
35
  _HAS_GFP = True
36
+ except Exception as e:
37
+ print("[WARN] GFPGAN not available:", e)
 
38
 
39
+ # RealESRGAN (upscale; ใช้ได้เมื่อมี)
40
+ _HAS_REALESRGAN = False
41
+ RealESRGAN = None
42
+ REALSR = None
43
  try:
44
+ from realesrgan import RealESRGAN # type: ignore
45
  _HAS_REALESRGAN = True
46
+ except Exception as e:
47
+ print("[WARN] RealESRGAN not available:", e)
48
+
49
+ # ---------------------------------------------------------------
50
 
51
  device = "cuda" if torch.cuda.is_available() else "cpu"
52
  dtype = torch.float16 if device == "cuda" else torch.float32
53
 
54
+ # ---------------- Registry (ตัวเลือกยอดนิยม + เพิ่มเองได้) ----------------
55
  MODELS: List[Tuple[str,str,str]] = [
 
56
  ("stabilityai/stable-diffusion-xl-base-1.0", "SDXL Base 1.0", "เอนกประสงค์ สมดุล"),
57
  ("stabilityai/stable-diffusion-xl-refiner-1.0","SDXL Refiner", "เก็บรายละเอียด (pass 2)"),
58
+ ("SG161222/RealVisXL_V4.0", "RealVis XL v4", "โฟโต้เรียล คน/สินค้า"),
59
+ ("Lykon/dreamshaper-xl-v2", "DreamShaper XL","แฟนตาซี–เรียลลิสติก"),
60
  ("RunDiffusion/Juggernaut-XL", "Juggernaut XL", "คอนทราสต์แรง รายละเอียดหนัก"),
61
+ ("emilianJR/epiCRealismXL", "EpicRealism XL","แฟชั่น/พอร์เทรต"),
62
  ("black-forest-labs/FLUX.1-dev", "FLUX.1-dev", "สมัยใหม่/คุมสไตล์ดี (ไม่ใช่ SDXL)"),
63
  ("stabilityai/sd-turbo", "SD-Turbo", "ไวมาก เหมาะกับร่างไอเดีย"),
64
  ("stabilityai/stable-diffusion-2-1", "SD 2.1", "แลนด์สเคป/องค์ประกอบกว้าง"),
65
+ ("runwayml/stable-diffusion-v1-5", "SD 1.5", "สไตล์คลาสสิก"),
66
+ ("timbrooks/instruct-pix2pix", "Instruct-Pix2Pix","แก้ภาพตามคำสั่ง (img2img)"),
67
  ]
68
 
69
  LORAS: List[Tuple[str,str,str]] = [
 
70
  ("ByteDance/SDXL-Lightning", "SDXL-Lightning", "สปีดเร็ว (LoRA)"),
71
  ("ostris/epicrealism-xl-lora", "EpicrealismXL-LoRA","โทนเรียลลิสติก"),
72
  ("XLabs-AI/flux-prompt-lora", "FLUX Prompt LoRA", "ปรับ prompt style (FLUX)"),
 
81
  ]
82
 
83
  CONTROLNETS: List[Tuple[str,str,str,str]] = [
 
84
  ("diffusers/controlnet-canny-sdxl-1.0", "Canny", "คุมเส้นขอบ", "canny"),
85
  ("diffusers/controlnet-openpose-sdxl-1.0", "OpenPose", "คุมท่าทางคน", "pose"),
86
  ("diffusers/controlnet-depth-sdxl-1.0", "Depth", "คุมมุมมอง/ระยะลึก", "depth"),
 
109
  "Heun": HeunDiscreteScheduler,
110
  }
111
 
112
+ # ---------------- Cache ----------------
113
  PIPE_CACHE: Dict[str, object] = {}
114
  CONTROL_CACHE: Dict[str, ControlNetModel] = {}
115
  UPSCALE_PIPE: Optional[StableDiffusionUpscalePipeline] = None
 
116
 
117
+ # ---------------- Helpers ----------------
118
  def set_sched(pipe, name: str):
119
  cls = SCHEDULERS.get(name, DPMSolverMultistepScheduler)
120
  pipe.scheduler = cls.from_config(pipe.scheduler.config)
 
122
  def seed_gen(sd: int):
123
  if sd is None or sd < 0: return None
124
  g = torch.Generator(device=device if device=="cuda" else "cpu")
125
+ g.manual_seed(int(sd)); return g
 
126
 
127
  def prep_pipe(model_id: str, control_ids: List[str]):
128
  key = f"{model_id}|{'-'.join(control_ids) if control_ids else 'none'}"
 
139
  pipe = StableDiffusionXLPipeline.from_pretrained(model_id, torch_dtype=dtype, use_safetensors=True)
140
 
141
  if device == "cuda":
142
+ pipe.to("cuda"); pipe.enable_vae_tiling(); pipe.enable_vae_slicing()
 
143
  try: pipe.enable_xformers_memory_efficient_attention()
144
  except Exception: pass
145
  else:
 
155
  try:
156
  sc = scales[i] if i < len(scales) else 0.7
157
  pipe.fuse_lora(lora_scale=float(sc))
158
+ except Exception:
159
+ pass
160
  except Exception as e:
161
  print(f"[LoRA] load failed {rid}: {e}")
162
 
163
  def to_png_info(meta: dict) -> str:
164
  return json.dumps(meta, ensure_ascii=False, indent=2)
165
 
166
+ # --------------- Optional post-process ---------------
167
  def ensure_upscalers():
168
  global UPSCALE_PIPE, GFP, REALSR
169
  if UPSCALE_PIPE is None:
170
  try:
171
  UPSCALE_PIPE = StableDiffusionUpscalePipeline.from_pretrained(
172
  "stabilityai/stable-diffusion-x4-upscaler",
173
+ torch_dtype=dtype, use_safetensors=True
 
174
  ).to(device)
175
  except Exception as e:
176
  print("[Upscaler] SD x4 not available:", e)
177
 
178
+ # GFPGAN
179
+ if _HAS_GFP and GFP is None and GFPGANer is not None:
180
  try:
181
  GFP = GFPGANer(model_path=None, upscale=1, arch="clean", channel_multiplier=2)
182
  except Exception as e:
183
  print("[GFPGAN] init failed:", e)
184
 
185
+ # RealESRGAN (ต้องมี weights เองถึงใช้ได้)
186
  if _HAS_REALESRGAN and REALSR is None and device == "cuda":
187
  try:
188
+ REALSR = RealESRGAN(torch.device("cuda"), scale=4) # ต้องมีไฟล์ weights ถึงจะทำงานจริง
189
+ # REALSR.load_weights("weights/RealESRGAN_x4plus.pth")
190
  except Exception as e:
191
+ REALSR = None
192
  print("[RealESRGAN] init failed:", e)
193
 
194
  def post_process(img: Image.Image, do_upscale: bool, do_face: bool, do_rembg: bool):
195
  ensure_upscalers()
196
  out = img
197
 
 
198
  if do_upscale:
199
  try:
200
  if REALSR is not None:
 
210
 
211
  if do_face and _HAS_GFP and GFP is not None:
212
  try:
213
+ _, _, restored = GFP.enhance(np.array(out), has_aligned=False, only_center_face=False, paste_back=True)
214
+ out = Image.fromarray(restored)
215
  except Exception as e:
216
  print("[GFPGAN] skipped:", e)
217
 
 
223
 
224
  return out
225
 
226
+ # ---------------- Core generators ----------------
227
  def run_txt2img(model_id, custom_model, prompt, preset, negative,
228
  steps, cfg, width, height, scheduler_name, seed,
229
  lora_list, lora_custom_csv, lora_s1, lora_s2, lora_s3,
230
  ctrl_selected, ctrl_images, use_refiner, refine_strength,
231
  do_upscale, do_face, do_rembg):
232
+
233
  if not prompt.strip(): raise gr.Error("กรุณากรอก prompt")
234
  model = (custom_model.strip() or model_id).strip()
235
  if preset in PRESETS: prompt = prompt + PRESETS[preset]
236
+ if not (negative or "").strip(): negative = NEG_DEFAULT
237
 
238
+ # ControlNet images
239
  cond_imgs, ctrl_ids = [], []
240
  for (cid, label, note, key) in CONTROLNETS:
241
  if label in ctrl_selected and key in ctrl_images and ctrl_images[key] is not None:
 
280
  num_inference_steps=int(steps), guidance_scale=float(cfg),
281
  generator=gen).images[0]
282
 
283
+ # Refiner (GPU only)
284
  if use_refiner and device == "cuda":
285
  try:
286
  ref = StableDiffusionXLImg2ImgPipeline.from_pretrained(
 
289
  ).to("cuda")
290
  set_sched(ref, scheduler_name)
291
  with torch.autocast("cuda"):
292
+ image = ref(prompt=prompt, negative_prompt=negative, image=image,
293
+ strength=float(refine_strength),
294
  num_inference_steps=max(10, int(steps)//2),
295
  guidance_scale=float(cfg), generator=gen).images[0]
296
  except Exception as e:
297
  print("[Refiner] skipped:", e)
298
 
 
299
  image = post_process(image, do_upscale, do_face, do_rembg)
 
300
  meta = {
301
+ "mode":"txt2img","model":model,"loras":loras,"controlnets":ctrl_selected,
302
+ "prompt":prompt,"negative":negative,"size":f"{width}x{height}",
303
+ "steps":steps,"cfg":cfg,"scheduler":scheduler_name,"seed":seed,
304
+ "post":{"upscale":do_upscale,"face_restore":do_face,"remove_bg":do_rembg}
305
  }
306
  return image, to_png_info(meta)
307
 
308
+ def run_img2img(model_id, custom_model, init_image, strength,
309
+ prompt, preset, negative, steps, cfg, width, height, scheduler_name, seed,
310
+ do_upscale, do_face, do_rembg):
311
+ if init_image is None: raise gr.Error("โปรดอัปโหลดภาพเริ่มต้น")
312
  model = (custom_model.strip() or model_id).strip()
313
+ if preset in PRESETS: prompt = prompt + PRESETS[preset]
314
+ if not (negative or "").strip(): negative = NEG_DEFAULT
315
+
316
  pipe = StableDiffusionXLImg2ImgPipeline.from_pretrained(model, torch_dtype=dtype, use_safetensors=True)
317
+ pipe = pipe.to(device)
318
  try:
319
  if device=="cuda": pipe.enable_xformers_memory_efficient_attention()
320
+ except Exception: pass
321
+ set_sched(pipe, scheduler_name); gen = seed_gen(seed)
 
 
322
 
323
  if device=="cuda":
324
  with torch.autocast("cuda"):
325
  img = pipe(prompt=prompt, negative_prompt=negative, image=init_image,
326
+ strength=float(strength), num_inference_steps=int(steps),
327
+ guidance_scale=float(cfg), generator=gen).images[0]
328
  else:
329
  img = pipe(prompt=prompt, negative_prompt=negative, image=init_image,
330
+ strength=float(strength), num_inference_steps=int(steps),
331
+ guidance_scale=float(cfg), generator=gen).images[0]
332
 
333
+ img = post_process(img, do_upscale, do_face, do_rembg)
334
+ meta = {"mode":"img2img","model":model,"prompt":prompt,"neg":negative,
335
+ "steps":steps,"cfg":cfg,"seed":seed,"strength":strength}
336
  return img, to_png_info(meta)
337
 
338
  def expand_canvas_for_outpaint(img: Image.Image, expand_px: int, direction: str) -> Tuple[Image.Image, Image.Image]:
339
  w, h = img.size
340
+ new = Image.new("RGBA", (w, h), (0,0,0,0))
341
+ mask = Image.new("L", (w, h), 0)
342
+ draw = ImageDraw.Draw(mask)
343
+
344
+ if direction == "left":
345
+ new = Image.new("RGBA", (w+expand_px, h), (0,0,0,0)); new.paste(img, (expand_px,0))
346
+ mask = Image.new("L", (w+expand_px, h), 0); draw = ImageDraw.Draw(mask); draw.rectangle([0,0,expand_px,h], fill=255)
347
+ elif direction == "right":
348
+ new = Image.new("RGBA", (w+expand_px, h), (0,0,0,0)); new.paste(img, (0,0))
349
+ mask = Image.new("L", (w+expand_px, h), 0); draw = ImageDraw.Draw(mask); draw.rectangle([w,0,w+expand_px,h], fill=255)
350
+ elif direction == "top":
351
+ new = Image.new("RGBA", (w, h+expand_px), (0,0,0,0)); new.paste(img, (0,expand_px))
352
+ mask = Image.new("L", (w, h+expand_px), 0); draw = ImageDraw.Draw(mask); draw.rectangle([0,0,w,expand_px], fill=255)
353
+ else: # bottom
354
+ new = Image.new("RGBA", (w, h+expand_px), (0,0,0,0)); new.paste(img, (0,0))
355
+ mask = Image.new("L", (w, h+expand_px), 0); draw = ImageDraw.Draw(mask); draw.rectangle([0,h,w,h+expand_px], fill=255)
356
+
357
  return new.convert("RGB"), mask
358
 
359
+ def run_inpaint_outpaint(model_id, custom_model, base_image, mask_image, mode, expand_px, expand_dir,
360
+ prompt, preset, negative, steps, cfg, width, height, scheduler_name, seed,
361
+ strength, do_upscale, do_face, do_rembg):
362
  if base_image is None: raise gr.Error("โปรดอัปโหลดภาพฐาน")
363
  model = (custom_model.strip() or model_id).strip()
364
+ if preset in PRESETS: prompt = prompt + PRESETS[preset]
365
+ if not (negative or "").strip(): negative = NEG_DEFAULT
366
+
367
  pipe = StableDiffusionXLInpaintPipeline.from_pretrained(model, torch_dtype=dtype, use_safetensors=True)
368
+ pipe = pipe.to(device)
369
  try:
370
  if device=="cuda": pipe.enable_xformers_memory_efficient_attention()
371
+ except Exception: pass
372
+ set_sched(pipe, scheduler_name); gen = seed_gen(seed)
 
 
373
 
374
  if mode == "Outpaint":
375
  base_image, mask_image = expand_canvas_for_outpaint(base_image, int(expand_px), expand_dir)
 
378
  with torch.autocast("cuda"):
379
  img = pipe(prompt=prompt, negative_prompt=negative,
380
  image=base_image, mask_image=mask_image,
381
+ strength=float(strength), num_inference_steps=int(steps),
382
+ guidance_scale=float(cfg), generator=gen).images[0]
 
 
383
  else:
384
  img = pipe(prompt=prompt, negative_prompt=negative,
385
  image=base_image, mask_image=mask_image,
386
+ strength=float(strength), num_inference_steps=int(steps),
387
+ guidance_scale=float(cfg), generator=gen).images[0]
 
 
388
 
389
+ img = post_process(img, do_upscale, do_face, do_rembg)
390
+ meta = {"mode":mode,"model":model,"prompt":prompt,"steps":steps,"cfg":cfg,"seed":seed}
391
  return img, to_png_info(meta)
392
 
393
  # ---------------- UI ----------------
394
  def build_ui():
395
  with gr.Blocks(theme=gr.themes.Soft(), title="Masterpiece SDXL Studio Pro") as demo:
396
  gr.Markdown("# 🖼️ Masterpiece SDXL Studio Pro")
397
+ gr.Markdown("Text2Img Img2Img Inpaint/Outpaint Multi-LoRA • Multi-ControlNet • Upscale/FaceRestore/RemoveBG")
398
 
 
399
  model_dd = gr.Dropdown(choices=[m[0] for m in MODELS], value=MODELS[0][0], label="Model (เลือก)")
400
  model_custom = gr.Textbox(label="Custom Model ID (เช่น username/my-model)", placeholder="(ไม่จำเป็น)")
401
 
402
  preset = gr.Dropdown(choices=list(PRESETS.keys()), value=None, label="Style Preset (optional)")
403
  negative = gr.Textbox(value=NEG_DEFAULT, label="Negative Prompt")
404
+
405
  steps = gr.Slider(10, 60, 30, step=1, label="Steps")
406
  cfg = gr.Slider(1.0, 12.0, 7.0, step=0.1, label="CFG")
407
  width = gr.Slider(512, 1024, 832, step=64, label="Width")
 
410
  seed = gr.Number(value=-1, precision=0, label="Seed (-1=random)")
411
 
412
  # LoRA
413
+ lora_group = gr.CheckboxGroup(
414
+ choices=[f"{rid} — {lbl} ({note})" for rid,lbl,note in LORAS],
415
+ label="LoRA (เลือกหลายตัวได้)"
416
+ )
417
  lora_custom = gr.Textbox(label="Custom LoRA IDs (คั่นด้วย comma)")
418
  lora_s1 = gr.Slider(0.0, 1.2, 0.7, 0.05, label="LoRA scale #1")
419
  lora_s2 = gr.Slider(0.0, 1.2, 0.5, 0.05, label="LoRA scale #2")
420
  lora_s3 = gr.Slider(0.0, 1.2, 0.5, 0.05, label="LoRA scale #3")
421
 
422
  # ControlNet
423
+ ctrl_group = gr.CheckboxGroup(
424
+ choices=[c[1]+" ("+c[2]+")" for c in CONTROLNETS],
425
+ label="ControlNet (เลือกชนิด)"
426
+ )
427
  imgs = {
428
  "canny": gr.Image(type="pil", label="Canny"),
429
  "pose": gr.Image(type="pil", label="OpenPose"),
 
471
 
472
  def parse_lora_list(selected: List[str]) -> List[str]:
473
  if not selected: return []
474
+ return [s.split(" — ")[0].strip() for s in selected]
 
 
 
 
475
 
476
  btn_txt.click(
477
  fn=run_txt2img,
 
481
  gr.Variable(parse_lora_list), lora_custom, lora_s1, lora_s2, lora_s3,
482
  ctrl_group,
483
  {k:v for k,v in imgs.items()}, # dict of images
484
+ gr.Checkbox(False), gr.Slider(0.05,0.5,0.2,0.05), # use_refiner, refine_strength (placeholder; UI ไม่โชว์)
485
  do_upscale, do_face, do_rembg
486
  ],
487
  outputs=[out_img_txt, out_meta_txt],
 
491
  btn_i2i.click(
492
  fn=run_img2img,
493
  inputs=[model_dd, model_custom, init_img, strength,
494
+ prompt_i2i, preset, negative, steps, cfg, width, height, scheduler, seed,
495
  do_upscale, do_face, do_rembg],
496
  outputs=[out_img_i2i, out_meta_i2i],
497
  api_name="img2img"
 
500
  btn_io.click(
501
  fn=run_inpaint_outpaint,
502
  inputs=[model_dd, model_custom, base_img, mask_img, mode_io, expand_px, expand_dir,
503
+ prompt_io, preset, negative, steps, cfg, width, height, scheduler, seed,
504
  strength, do_upscale, do_face, do_rembg],
505
  outputs=[out_img_io, out_meta_io],
506
  api_name="inpaint_outpaint"
507
  )
508
 
509
+ gr.Markdown("ℹ️ **หมายเหตุ**: ถ้า LoRA/ControlNet/โพสต์โปรเซสบางตัวไม่มีในสภาพแวดล้อม โปรแกรมจะข้ามอย่างปลอดภัย พร้อมพิมพ์คำเตือนใน Console")
510
 
511
  return demo
512