yangyufeng commited on
Commit
b1b404c
·
1 Parent(s): 5d8652f

line down layout

Browse files
Files changed (1) hide show
  1. app.py +94 -80
app.py CHANGED
@@ -48,45 +48,51 @@ TASK_PRESETS = {
48
  }
49
  DEFAULT_PRESET = "Low-light Enhancement"
50
 
51
- # --- UI Header (大气版:字号放大,严格限制两行) ---
52
  TITLE_HTML = """
53
- <div style="text-align: center; margin-bottom: 30px;">
54
- <h1 style="font-size: 3.2rem; margin: 0 0 10px 0; background: linear-gradient(135deg, #4f46e5 0%, #ec4899 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px;">RealRestorer</h1>
55
- <p style="font-size: 1.15rem; color: var(--body-text-color-subdued); margin: 0; font-weight: 500;">A powerful image restoration model supporting deblurring, denoising, deflaring, low-light enhancement, and more.</p>
56
  </div>
57
  """
58
 
59
- # --- 宽屏大气 CSS ---
60
  CUSTOM_CSS = """
61
- /* 扩大最大宽度,增加顶部留白,营造呼吸感 */
62
- .gradio-container { max-width: 1600px !important; margin: auto !important; padding-top: 40px !important; }
63
-
64
- /* 调整面板圆角与阴影使其更大气 */
65
- .gr-box { border-radius: 16px !important; }
 
 
 
 
 
 
 
66
 
67
- /* 强化主按钮质感与视觉吸引力 */
68
  #run-btn {
69
  background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%) !important;
70
  color: white !important;
71
  border: none !important;
72
- font-size: 1.25rem !important;
73
  font-weight: 700 !important;
74
- padding: 16px !important;
75
  border-radius: 12px !important;
76
- box-shadow: 0 6px 15px -3px rgba(79, 70, 229, 0.4) !important;
77
  transition: all 0.2s ease-in-out !important;
 
78
  }
79
  #run-btn:hover {
80
  transform: translateY(-2px) !important;
81
- box-shadow: 0 10px 20px -3px rgba(79, 70, 229, 0.5) !important;
82
  }
83
 
84
- /* 优化状态栏的视觉 */
85
- .rr-status textarea {
86
- font-size: 1.05rem !important;
87
- color: #334155 !important;
88
- background: #f8fafc !important;
89
- font-family: ui-monospace, monospace !important;
90
  }
91
  """
92
 
@@ -125,11 +131,10 @@ def _pil_to_data_url(image: Image.Image) -> str:
125
  return f"data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode('utf-8')}"
126
 
127
  def _build_slider_html(before_image: Image.Image, after_image: Image.Image) -> str:
128
- # 状态:未上传图片。高度放大为 650px,与左侧对齐
129
  if before_image is None or after_image is None:
130
  return """
131
- <div style='height: 650px; display:flex; align-items:center; justify-content:center; color: var(--body-text-color-subdued); border: 2px dashed var(--border-color-primary); border-radius: 16px; font-size: 1.25rem; background: var(--background-fill-secondary);'>
132
- Upload an image and run to see the comparison here.
133
  </div>
134
  """
135
 
@@ -138,28 +143,31 @@ def _build_slider_html(before_image: Image.Image, after_image: Image.Image) -> s
138
 
139
  before_url = _pil_to_data_url(before_image)
140
  after_url = _pil_to_data_url(after_image)
 
141
  slider_id = f"slider_{int(time.time() * 1000)}"
142
 
143
  on_input = f"var p=this.value; document.getElementById('{slider_id}_top').style.clipPath='inset(0 '+(100-p)+'% 0 0)'; document.getElementById('{slider_id}_line').style.left=p+'%';"
144
 
145
- # 态:渲染完成。高度同样放大650px,手柄也相应变大
146
  return f"""
147
- <div style="position:relative; width:100%; height:650px; overflow:hidden; border-radius:16px; background:var(--background-fill-secondary); border: 1px solid var(--border-color-primary); margin: 0 auto;">
148
- <!-- 底图: 修复后 -->
149
- <img src="{after_url}" style="position:absolute; top:0; left:0; width:100%; height:100%; object-fit:contain;" draggable="false" />
150
- <!-- 顶图: 原图 -->
151
- <img id="{slider_id}_top" src="{before_url}" style="position:absolute; top:0; left:0; width:100%; height:100%; object-fit:contain; clip-path:inset(0 50% 0 0);" draggable="false" />
152
-
153
- <!-- 拖拽线与把手 -->
154
- <div id="{slider_id}_line" style="position:absolute; top:0; left:50%; width:4px; height:100%; background:white; box-shadow:0 0 15px rgba(0,0,0,0.4); transform:translateX(-50%); pointer-events:none; z-index:10;">
155
- <div style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); width:48px; height:48px; border-radius:50%; background:white; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 15px rgba(0,0,0,0.4); font-weight:bold; color:#1e293b; font-size:1.3rem;">↔</div>
156
- </div>
157
-
158
- <!-- 透明原生滑动条 -->
159
- <input type="range" min="0" max="100" value="50" oninput="{on_input}" style="position:absolute; top:0; left:0; width:100%; height:100%; opacity:0; cursor:ew-resize; z-index:20; margin:0; appearance:auto;" />
 
 
160
  </div>
161
 
162
- <div style="display:flex; justify-content:space-between; color:var(--body-text-color-subdued); font-size:1.05rem; padding-top:16px; font-weight:600;">
163
  <span>⬅️ Original</span>
164
  <span>Restored ➡️</span>
165
  </div>
@@ -201,9 +209,8 @@ def run_inference(image: Optional[Image.Image], task_name: str, prompt: str, ste
201
  traceback.print_exc()
202
  return None, f"❌ Failed: {exc}", _build_slider_html(None, None)
203
 
204
-
205
  def build_demo():
206
- # 启用 text_lg 和 spacing_lg,让界面自带气的留白和清晰的大字体
207
  atmospheric_theme = gr.themes.Soft(
208
  text_size=gr.themes.sizes.text_lg,
209
  spacing_size=gr.themes.sizes.spacing_lg
@@ -212,55 +219,61 @@ def build_demo():
212
  with gr.Blocks(css=CUSTOM_CSS, title="RealRestorer", theme=atmospheric_theme) as demo:
213
  gr.HTML(TITLE_HTML)
214
 
215
- # 完美 1:1 对齐布局
216
- with gr.Row(equal_height=False):
 
 
 
217
 
218
- # ===== 左侧:控制面板 =====
219
- with gr.Column(scale=1):
220
- gr.Markdown("### ⚙️ 1. Setup & Control")
221
-
222
- # 上传区域放大至 420px,提供更宽裕的拖拽空间
223
- input_image = gr.Image(label="Upload Image", type="pil", height=420)
224
-
225
- task_dropdown = gr.Dropdown(choices=list(TASK_PRESETS.keys()), value=DEFAULT_PRESET, label="Task Preset")
226
- prompt_box = gr.Textbox(label="Instruction", value=TASK_PRESETS[DEFAULT_PRESET], lines=2)
227
-
228
- with gr.Row():
 
229
  guidance_slider = gr.Slider(minimum=1.0, maximum=6.0, value=DEFAULT_CFG, step=0.1, label="CFG / Guidance Scale")
 
230
  steps_slider = gr.Slider(minimum=12, maximum=40, value=DEFAULT_STEPS, step=1, label="Inference Steps")
231
-
232
- with gr.Accordion("Advanced Settings", open=False):
233
  seed_box = gr.Number(label="Seed (-1 for random)", value=DEFAULT_SEED, precision=0)
234
 
235
- run_button = gr.Button("🚀 Run Restoration", elem_id="run-btn")
236
-
237
 
238
- # ===== 右侧:展示面板 =====
239
- with gr.Column(scale=1):
240
- gr.Markdown("### 🖼️ 2. Results Preview")
241
-
242
- status_box = gr.Textbox(
243
- label="Status",
244
- value="💡 Ready. Upload an image and click Run.",
245
- interactive=False,
246
- lines=1,
247
- elem_classes=["rr-status"]
248
- )
 
 
 
 
 
249
 
250
- with gr.Tabs():
251
- with gr.Tab("Compare View"):
252
- # 高度在 HTML 中已锁定为巨大的 650px
253
- slider_view = gr.HTML(_build_slider_html(None, None))
254
-
255
- with gr.Tab("Output Image"):
256
- # 高度同步设定为 650px,与 Compare View 严格一致
257
- output_image = gr.Image(label="Restored Result", type="pil", interactive=False, height=650, show_label=False)
258
 
259
- # ===== 事件绑定 =====
 
 
260
  task_dropdown.change(fn=_on_preset_change, inputs=[task_dropdown], outputs=[prompt_box])
261
 
262
  run_button.click(
263
- fn=lambda: ("⏳ Running restoration, please wait...", _build_slider_html(None, None)),
264
  outputs=[status_box, slider_view]
265
  ).then(
266
  fn=run_inference,
@@ -272,4 +285,5 @@ def build_demo():
272
 
273
  if __name__ == "__main__":
274
  demo = build_demo()
275
- demo.queue(max_size=8).launch(server_name="0.0.0.0", show_error=True)
 
 
48
  }
49
  DEFAULT_PRESET = "Low-light Enhancement"
50
 
51
+ # --- UI Header (Strictly 2 lines, centered) ---
52
  TITLE_HTML = """
53
+ <div style="text-align: center; margin-bottom: 20px;">
54
+ <h1 style="font-size: 3.5rem; margin: 0 0 10px 0; background: linear-gradient(135deg, #4f46e5 0%, #ec4899 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px;">RealRestorer</h1>
55
+ <p style="font-size: 1.2rem; color: var(--body-text-color-subdued); margin: 0; font-weight: 500;">A powerful image restoration model supporting deblurring, denoising, deflaring, low-light enhancement, and more.</p>
56
  </div>
57
  """
58
 
59
+ # --- Full-Width Vertical CSS ---
60
  CUSTOM_CSS = """
61
+ /* 放宽最大宽度,让网页横向占满 */
62
+ .gradio-container { max-width: 1800px !important; margin: auto !important; padding: 40px 20px !important; }
63
+
64
+ /* 定义区块的视觉卡片风格 */
65
+ .rr-section {
66
+ background: #ffffff !important;
67
+ border: 1px solid #e2e8f0 !important;
68
+ border-radius: 16px !important;
69
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04) !important;
70
+ padding: 30px !important;
71
+ margin-bottom: 30px !important;
72
+ }
73
 
74
+ /* 巨型行动按钮 */
75
  #run-btn {
76
  background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%) !important;
77
  color: white !important;
78
  border: none !important;
79
+ font-size: 1.3rem !important;
80
  font-weight: 700 !important;
81
+ padding: 18px !important;
82
  border-radius: 12px !important;
83
+ box-shadow: 0 6px 20px -4px rgba(79, 70, 229, 0.4) !important;
84
  transition: all 0.2s ease-in-out !important;
85
+ margin-top: 10px !important;
86
  }
87
  #run-btn:hover {
88
  transform: translateY(-2px) !important;
89
+ box-shadow: 0 10px 25px -4px rgba(79, 70, 229, 0.5) !important;
90
  }
91
 
92
+ /* 标题样式 */
93
+ .section-title {
94
+ font-size: 1.4rem; font-weight: 700; color: #1e293b;
95
+ margin-bottom: 20px; display: flex; align-items: center; gap: 8px;
 
 
96
  }
97
  """
98
 
 
131
  return f"data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode('utf-8')}"
132
 
133
  def _build_slider_html(before_image: Image.Image, after_image: Image.Image) -> str:
 
134
  if before_image is None or after_image is None:
135
  return """
136
+ <div style='height: 500px; width: 100%; display:flex; align-items:center; justify-content:center; color: var(--body-text-color-subdued); border: 2px dashed var(--border-color-primary); border-radius: 16px; font-size: 1.3rem; background: var(--background-fill-secondary);'>
137
+ Upload an image and run to see the full-width comparison here.
138
  </div>
139
  """
140
 
 
143
 
144
  before_url = _pil_to_data_url(before_image)
145
  after_url = _pil_to_data_url(after_image)
146
+ width, height = after_image.size
147
  slider_id = f"slider_{int(time.time() * 1000)}"
148
 
149
  on_input = f"var p=this.value; document.getElementById('{slider_id}_top').style.clipPath='inset(0 '+(100-p)+'% 0 0)'; document.getElementById('{slider_id}_line').style.left=p+'%';"
150
 
151
+ # 自适应外层包裹居中,内层按原图比例撑开,最大高度限制屏幕高度的 80% 防止竖图过长
152
  return f"""
153
+ <div style="display: flex; justify-content: center; width: 100%; background: var(--background-fill-secondary); border-radius: 16px; padding: 20px; border: 1px solid var(--border-color-primary); box-sizing: border-box;">
154
+ <div style="position:relative; width:100%; max-height:80vh; aspect-ratio:{width}/{height}; overflow:hidden; border-radius:12px; box-shadow: 0 10px 30px rgba(0,0,0,0.15);">
155
+ <!-- 底图: 修复后 -->
156
+ <img src="{after_url}" style="position:absolute; top:0; left:0; width:100%; height:100%; object-fit:contain;" draggable="false" />
157
+ <!-- 顶图: 原图 -->
158
+ <img id="{slider_id}_top" src="{before_url}" style="position:absolute; top:0; left:0; width:100%; height:100%; object-fit:contain; clip-path:inset(0 50% 0 0);" draggable="false" />
159
+
160
+ <!-- 拖拽线与把手 -->
161
+ <div id="{slider_id}_line" style="position:absolute; top:0; left:50%; width:4px; height:100%; background:white; box-shadow:0 0 20px rgba(0,0,0,0.5); transform:translateX(-50%); pointer-events:none; z-index:10;">
162
+ <div style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); width:56px; height:56px; border-radius:50%; background:white; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 20px rgba(0,0,0,0.4); font-weight:bold; color:#1e293b; font-size:1.5rem;">↔</div>
163
+ </div>
164
+
165
+ <!-- 透明原生滑动条 -->
166
+ <input type="range" min="0" max="100" value="50" oninput="{on_input}" style="position:absolute; top:0; left:0; width:100%; height:100%; opacity:0; cursor:ew-resize; z-index:20; margin:0; appearance:auto;" />
167
+ </div>
168
  </div>
169
 
170
+ <div style="display:flex; justify-content:space-between; color:var(--body-text-color-subdued); font-size:1.1rem; padding-top:16px; font-weight:600;">
171
  <span>⬅️ Original</span>
172
  <span>Restored ➡️</span>
173
  </div>
 
209
  traceback.print_exc()
210
  return None, f"❌ Failed: {exc}", _build_slider_html(None, None)
211
 
 
212
  def build_demo():
213
+ # 启用全局字体和间距
214
  atmospheric_theme = gr.themes.Soft(
215
  text_size=gr.themes.sizes.text_lg,
216
  spacing_size=gr.themes.sizes.spacing_lg
 
219
  with gr.Blocks(css=CUSTOM_CSS, title="RealRestorer", theme=atmospheric_theme) as demo:
220
  gr.HTML(TITLE_HTML)
221
 
222
+ # ==========================================
223
+ # Section 1 & 2: 图像上传与控制参数 (纵向排列)
224
+ # ==========================================
225
+ with gr.Column(elem_classes=["rr-section"]):
226
+ gr.HTML("<div class='section-title'>📥 1. Upload & Settings</div>")
227
 
228
+ # 1. 超宽的图片上传区域
229
+ input_image = gr.Image(label="Upload Image", type="pil", height=450)
230
+
231
+ # 2. 控制参数横向铺开,充分利用宽度
232
+ with gr.Row():
233
+ with gr.Column(scale=1):
234
+ task_dropdown = gr.Dropdown(choices=list(TASK_PRESETS.keys()), value=DEFAULT_PRESET, label="Task Preset")
235
+ with gr.Column(scale=2):
236
+ prompt_box = gr.Textbox(label="Instruction(输入指令)", value=TASK_PRESETS[DEFAULT_PRESET], lines=1)
237
+
238
+ with gr.Row():
239
+ with gr.Column(scale=1):
240
  guidance_slider = gr.Slider(minimum=1.0, maximum=6.0, value=DEFAULT_CFG, step=0.1, label="CFG / Guidance Scale")
241
+ with gr.Column(scale=1):
242
  steps_slider = gr.Slider(minimum=12, maximum=40, value=DEFAULT_STEPS, step=1, label="Inference Steps")
243
+ with gr.Column(scale=1):
 
244
  seed_box = gr.Number(label="Seed (-1 for random)", value=DEFAULT_SEED, precision=0)
245
 
246
+ # 3. 占据一整行的巨型运行按钮
247
+ run_button = gr.Button("🚀 Run Restoration", elem_id="run-btn")
248
 
249
+ # ==========================================
250
+ # Section 3: 结果展示 (横跨整个屏幕)
251
+ # ==========================================
252
+ with gr.Column(elem_classes=["rr-section"]):
253
+ gr.HTML("<div class='section-title'>🖼️ 2. Restoration Results</div>")
254
+
255
+ status_box = gr.Textbox(
256
+ label="Status",
257
+ value="💡 Ready. Upload an image, adjust settings, and click Run.",
258
+ interactive=False,
259
+ lines=1
260
+ )
261
+
262
+ with gr.Tabs():
263
+ with gr.Tab("Compare View"):
264
+ slider_view = gr.HTML(_build_slider_html(None, None))
265
 
266
+ with gr.Tab("Output Image"):
267
+ # 单纯展示的输出图也给足高度
268
+ output_image = gr.Image(label="Restored Result", type="pil", interactive=False, height=700, show_label=False)
 
 
 
 
 
269
 
270
+ # ==========================================
271
+ # Events
272
+ # ==========================================
273
  task_dropdown.change(fn=_on_preset_change, inputs=[task_dropdown], outputs=[prompt_box])
274
 
275
  run_button.click(
276
+ fn=lambda: ("⏳ Processing... Please wait.", _build_slider_html(None, None)),
277
  outputs=[status_box, slider_view]
278
  ).then(
279
  fn=run_inference,
 
285
 
286
  if __name__ == "__main__":
287
  demo = build_demo()
288
+ # default_concurrency_limit=1 prevents multiple heavy runs at once
289
+ demo.queue(max_size=8, default_concurrency_limit=1).launch(server_name="0.0.0.0", show_error=True)