yangyufeng commited on
Commit
dc8a892
·
1 Parent(s): e441495

update UI

Browse files
Files changed (1) hide show
  1. app.py +63 -167
app.py CHANGED
@@ -48,165 +48,49 @@ 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: 2.5rem; margin: 0 0 8px 0; background: linear-gradient(135deg, #4f46e5 0%, #ec4899 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">RealRestorer</h1>
55
- <p style="font-size: 1.05rem; color: #64748b; margin: 0 auto; font-weight: 500; max-width: 860px; line-height: 1.65;">
56
- A powerful open-source image restoration model for deblurring, denoising, deflaring,
57
- low-light enhancement, and more.<br/>
58
- Simply describe the restoration task you want in natural language and give it a try.
59
- </p>
60
  </div>
61
  """
62
 
63
- # --- Clean CSS (Expanded Width & Symmetrical Layout) ---
64
  CUSTOM_CSS = """
65
- .gradio-container {
66
- --body-background-fill: #f6f9fc !important;
67
- --body-background-fill-dark: #f6f9fc !important;
68
- --background-fill-primary: #ffffff !important;
69
- --background-fill-secondary: #f8fbff !important;
70
- --block-background-fill: #ffffff !important;
71
- --block-background-fill-dark: #ffffff !important;
72
- --block-border-color: #e2e8f0 !important;
73
- --block-border-color-dark: #e2e8f0 !important;
74
- --border-color-primary: #e2e8f0 !important;
75
- --border-color-accent: #c7d2fe !important;
76
- --input-background-fill: #ffffff !important;
77
- --input-background-fill-focus: #ffffff !important;
78
- --input-border-color: #d7dfeb !important;
79
- --input-border-color-focus: #a5b4fc !important;
80
- --body-text-color: #1e293b !important;
81
- --body-text-color-subdued: #64748b !important;
82
- --block-title-text-color: #0f172a !important;
83
- --block-label-text-color: #334155 !important;
84
- --block-info-text-color: #64748b !important;
85
- --button-secondary-background-fill: #ffffff !important;
86
- --button-secondary-background-fill-hover: #f8fbff !important;
87
- --button-secondary-text-color: #334155 !important;
88
- --button-secondary-border-color: #d7dfeb !important;
89
- }
90
- body { background-color: #f1f5f9 !important; color: #1e293b !important; }
91
- /* 扩大最大宽度,充分利用大屏幕 */
92
- .gradio-container { max-width: 1600px !important; margin: 0 auto !important; padding-top: 30px !important; }
93
- .gradio-container,
94
- .gradio-container .prose,
95
- .gradio-container .form,
96
- .gradio-container label,
97
- .gradio-container span,
98
- .gradio-container p,
99
- .gradio-container h1,
100
- .gradio-container h2,
101
- .gradio-container h3,
102
- .gradio-container h4 {
103
- color: #1e293b !important;
104
- }
105
- .gradio-container svg {
106
- color: inherit !important;
107
- }
108
  .rr-panel {
109
- background: white !important;
110
  border: 1px solid #e2e8f0 !important;
111
- border-radius: 12px !important;
112
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05) !important;
113
  padding: 24px !important;
114
  }
 
 
115
  .rr-panel-title {
116
- font-size: 1.2rem !important; font-weight: bold !important; color: #1e293b !important;
117
- margin-bottom: 20px !important; padding-bottom: 10px !important; border-bottom: 1px solid #e2e8f0 !important;
118
  }
 
 
119
  #run-btn {
120
- background: #4f46e5 !important; color: white !important; border: none !important;
121
- font-size: 1.15rem !important; font-weight: bold !important; border-radius: 8px !important; padding: 14px !important;
122
- margin-top: 20px !important; box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.2) !important;
 
 
123
  }
124
- #run-btn:hover { background: #4338ca !important; }
125
- .rr-accordion { border: none !important; box-shadow: none !important; background: transparent !important; padding: 0 !important; }
126
- .rr-accordion > button { padding: 15px 0 5px !important; border-top: 1px solid #e2e8f0 !important; font-size: 0.95rem !important;}
127
  .rr-status {
128
- background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px 16px;
129
- font-size: 0.95rem; color: #475569; margin-bottom: 20px; font-family: monospace; white-space: pre-wrap;
130
- }
131
- .rr-panel input,
132
- .rr-panel textarea,
133
- .rr-panel select {
134
- background: #ffffff !important;
135
- color: #1e293b !important;
136
- border-color: #d7dfeb !important;
137
- }
138
- .rr-panel textarea::placeholder,
139
- .rr-panel input::placeholder {
140
- color: #94a3b8 !important;
141
- }
142
- .rr-panel button:not(#run-btn) {
143
- background: #ffffff !important;
144
- color: #334155 !important;
145
- border-color: #d7dfeb !important;
146
- }
147
- .rr-panel [role="tab"],
148
- .rr-panel [role="tab"] * {
149
- color: #475569 !important;
150
- }
151
- .rr-panel [role="tab"][aria-selected="true"],
152
- .rr-panel [role="tab"][aria-selected="true"] * {
153
- color: #4f46e5 !important;
154
- }
155
- .rr-panel [role="tablist"],
156
- .rr-panel .tabs,
157
- .rr-panel .tabitem {
158
- background: #ffffff !important;
159
- }
160
- .rr-panel details,
161
- .rr-panel summary,
162
- .rr-panel .accordion,
163
- .rr-panel .gr-accordion {
164
- background: #ffffff !important;
165
- color: #1e293b !important;
166
- }
167
- .rr-panel .progress-text,
168
- .rr-panel .progress-text * {
169
- color: #334155 !important;
170
- }
171
- .rr-panel .loading,
172
- .rr-panel .pending,
173
- .rr-panel .spinner,
174
- .rr-panel .wrap,
175
- .rr-panel .empty,
176
- .rr-panel .placeholder,
177
- .rr-panel .toast-wrap,
178
- .rr-panel .message,
179
- .rr-panel .panel {
180
- background: #ffffff !important;
181
- color: #334155 !important;
182
- }
183
- .rr-panel [data-testid="image"] {
184
- background: white !important;
185
- border-radius: 10px !important;
186
- border: 1px solid #e2e8f0 !important;
187
- }
188
- .rr-panel [data-testid="image"] > div,
189
- .rr-panel [data-testid="image"] canvas,
190
- .rr-panel [data-testid="image"] img,
191
- .rr-panel [data-testid="image"] video {
192
- background: white !important;
193
- }
194
- .rr-panel [data-testid="image"] button,
195
- .rr-panel [data-testid="image"] label,
196
- .rr-panel [data-testid="image"] span,
197
- .rr-panel [data-testid="image"] div {
198
- color: #334155 !important;
199
- }
200
- .rr-panel .tabitem,
201
- .rr-panel .tabs,
202
- .rr-panel .tabs > .tab-nav {
203
- background: white !important;
204
- }
205
- .rr-panel .empty,
206
- .rr-panel .placeholder,
207
- .rr-panel .loading,
208
- .rr-panel .wrap {
209
- background: white !important;
210
  }
211
  """
212
 
@@ -245,13 +129,15 @@ def _pil_to_data_url(image: Image.Image) -> str:
245
  return f"data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode('utf-8')}"
246
 
247
  def _build_slider_html(before_image: Image.Image, after_image: Image.Image) -> str:
 
248
  if before_image is None or after_image is None:
249
  return """
250
- <div style='height: 450px; display:flex; align-items:center; justify-content:center; color:#94a3b8; border: 2px dashed #e2e8f0; border-radius: 10px; font-size: 1.1rem; background: #f8fafc;'>
251
- The comparison will appear here
252
  </div>
253
  """
254
 
 
255
  if before_image.size != after_image.size:
256
  before_image = before_image.resize(after_image.size, Image.LANCZOS)
257
 
@@ -260,25 +146,26 @@ def _build_slider_html(before_image: Image.Image, after_image: Image.Image) -> s
260
  width, height = after_image.size
261
  slider_id = f"slider_{int(time.time() * 1000)}"
262
 
 
263
  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+'%';"
264
 
265
  return f"""
266
- <div style="position:relative; width:100%; max-height:75vh; aspect-ratio:{width}/{height}; overflow:hidden; border-radius:10px; background:#ffffff; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); border: 1px solid #e2e8f0;">
267
  <!-- 底图: 修复后 -->
268
- <img src="{after_url}" style="position:absolute; inset:0; width:100%; height:100%; object-fit:contain;" draggable="false" />
269
  <!-- 顶图: 原图 -->
270
- <img id="{slider_id}_top" src="{before_url}" style="position:absolute; inset:0; width:100%; height:100%; object-fit:contain; clip-path:inset(0 50% 0 0);" draggable="false" />
271
 
272
  <!-- 拖拽线与把手 -->
273
- <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.5); transform:translateX(-50%); pointer-events:none; z-index:10;">
274
- <div style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); width:44px; height:44px; border-radius:50%; background:white; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 12px rgba(0,0,0,0.3); font-weight:bold; color:#333; font-size:1.1rem;">↔</div>
275
  </div>
276
 
277
  <!-- 透明原生滑动条 -->
278
- <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;" />
279
  </div>
280
 
281
- <div style="display:flex; justify-content:space-between; color:#64748b; font-size:0.95rem; padding-top:12px; font-weight:bold;">
282
  <span>⬅️ Original</span>
283
  <span>Restored ➡️</span>
284
  </div>
@@ -320,38 +207,47 @@ def run_inference(image: Optional[Image.Image], task_name: str, prompt: str, ste
320
  traceback.print_exc()
321
  return None, f"❌ Failed: {exc}", _build_slider_html(None, None)
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  def build_demo():
324
- with gr.Blocks(
325
- css=CUSTOM_CSS,
326
- title="RealRestorer",
327
- theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="pink", neutral_hue="slate"),
328
- ) as demo:
329
  gr.HTML(TITLE_HTML)
330
 
331
  # 1:1 Symmetrical Layout
332
- with gr.Row():
333
 
334
  # Left Column: Setup & Control (scale=1)
335
- with gr.Column(scale=1, min_width=500, elem_classes=["rr-panel"]):
336
  gr.HTML("<div class='rr-panel-title'>1. Setup & Control</div>")
337
 
338
- input_image = gr.Image(label="Upload Image", type="pil", height=320)
339
 
340
  task_dropdown = gr.Dropdown(choices=list(TASK_PRESETS.keys()), value=DEFAULT_PRESET, label="Task Preset")
341
  prompt_box = gr.Textbox(label="Instruction", value=TASK_PRESETS[DEFAULT_PRESET], lines=3)
342
 
343
- # 将两个滑动条放在一行节省空间
344
  with gr.Row():
345
  guidance_slider = gr.Slider(minimum=1.0, maximum=6.0, value=DEFAULT_CFG, step=0.1, label="CFG / Guidance Scale")
346
  steps_slider = gr.Slider(minimum=12, maximum=40, value=DEFAULT_STEPS, step=1, label="Inference Steps")
347
 
348
- with gr.Accordion("Advanced Settings", open=False, elem_classes=["rr-accordion"]):
349
  seed_box = gr.Number(label="Seed (-1 for random)", value=DEFAULT_SEED, precision=0)
350
 
351
  run_button = gr.Button("🚀 Run Restoration", elem_id="run-btn")
352
 
353
  # Right Column: Results Preview (scale=1)
354
- with gr.Column(scale=1, min_width=500, elem_classes=["rr-panel"]):
355
  gr.HTML("<div class='rr-panel-title'>2. Results Preview</div>")
356
 
357
  status_box = gr.HTML(
@@ -363,7 +259,7 @@ def build_demo():
363
  slider_view = gr.HTML(_build_slider_html(None, None))
364
 
365
  with gr.Tab("Output Image"):
366
- output_image = gr.Image(label="Restored Result", type="pil", interactive=False, height=650)
367
 
368
  # Event Bindings
369
  task_dropdown.change(fn=_on_preset_change, inputs=[task_dropdown], outputs=[prompt_box])
@@ -385,4 +281,4 @@ def build_demo():
385
 
386
  if __name__ == "__main__":
387
  demo = build_demo()
388
- 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) ---
52
  TITLE_HTML = """
53
+ <div style="text-align: center; margin-bottom: 24px;">
54
+ <h1 style="font-size: 2.6rem; margin: 0 0 8px 0; background: linear-gradient(135deg, #4f46e5 0%, #ec4899 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">RealRestorer</h1>
55
+ <p style="font-size: 1.05rem; color: #64748b; 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
+ # --- Clean CSS for custom components ---
60
  CUSTOM_CSS = """
61
+ /* 扩大容器,匹配大屏 1:1 体验 */
62
+ .gradio-container { max-width: 1500px !important; margin: auto !important; padding-top: 30px !important; }
63
+
64
+ /* 自定义白底卡片面板 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  .rr-panel {
66
+ background: #ffffff !important;
67
  border: 1px solid #e2e8f0 !important;
68
+ border-radius: 16px !important;
69
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03) !important;
70
  padding: 24px !important;
71
  }
72
+
73
+ /* 标题小修饰 */
74
  .rr-panel-title {
75
+ font-size: 1.15rem !important; font-weight: 700 !important; color: #1e293b !important;
76
+ margin-bottom: 16px !important; padding-bottom: 8px !important; border-bottom: 1px solid #e2e8f0 !important;
77
  }
78
+
79
+ /* 漂亮的运行按钮 */
80
  #run-btn {
81
+ background: linear-gradient(135deg, #4f46e5, #4338ca) !important;
82
+ color: white !important; border: none !important;
83
+ font-size: 1.1rem !important; font-weight: bold !important;
84
+ border-radius: 8px !important; padding: 12px !important;
85
+ margin-top: 10px !important; transition: all 0.2s;
86
  }
87
+ #run-btn:hover { box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3) !important; transform: translateY(-1px); }
88
+
89
+ /* 状态栏 */
90
  .rr-status {
91
+ background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px;
92
+ padding: 12px 16px; font-size: 0.95rem; color: #475569;
93
+ margin-bottom: 16px; font-family: ui-monospace, monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
  """
96
 
 
129
  return f"data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode('utf-8')}"
130
 
131
  def _build_slider_html(before_image: Image.Image, after_image: Image.Image) -> str:
132
+ # 空白状态
133
  if before_image is None or after_image is None:
134
  return """
135
+ <div style='height: 480px; display:flex; align-items:center; justify-content:center; color:#94a3b8; border: 2px dashed #cbd5e0; border-radius: 12px; font-size: 1.1rem; background: #f8fafc;'>
136
+ Upload an image and run to see the comparison here.
137
  </div>
138
  """
139
 
140
+ # 对齐尺寸
141
  if before_image.size != after_image.size:
142
  before_image = before_image.resize(after_image.size, Image.LANCZOS)
143
 
 
146
  width, height = after_image.size
147
  slider_id = f"slider_{int(time.time() * 1000)}"
148
 
149
+ # 交互 JavaScript
150
  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+'%';"
151
 
152
  return f"""
153
+ <div style="position:relative; width:100%; max-height:75vh; aspect-ratio:{width}/{height}; overflow:hidden; border-radius:12px; background:#e2e8f0; border: 1px solid #cbd5e0; margin: 0 auto;">
154
  <!-- 底图: 修复后 -->
155
+ <img src="{after_url}" style="position:absolute; top:0; left:0; width:100%; height:100%; object-fit:contain;" draggable="false" />
156
  <!-- 顶图: 原图 -->
157
+ <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" />
158
 
159
  <!-- 拖拽线与把手 -->
160
+ <div id="{slider_id}_line" style="position:absolute; top:0; left:50%; width:4px; height:100%; background:white; box-shadow:0 0 10px rgba(0,0,0,0.3); transform:translateX(-50%); pointer-events:none; z-index:10;">
161
+ <div style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); width:40px; height:40px; border-radius:50%; background:white; display:flex; align-items:center; justify-content:center; box-shadow:0 2px 10px rgba(0,0,0,0.4); font-weight:bold; color:#1e293b; font-size:1.1rem;">↔</div>
162
  </div>
163
 
164
  <!-- 透明原生滑动条 -->
165
+ <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;" />
166
  </div>
167
 
168
+ <div style="display:flex; justify-content:space-between; color:#64748b; font-size:0.95rem; padding-top:12px; font-weight:600;">
169
  <span>⬅️ Original</span>
170
  <span>Restored ➡️</span>
171
  </div>
 
207
  traceback.print_exc()
208
  return None, f"❌ Failed: {exc}", _build_slider_html(None, None)
209
 
210
+
211
+ # --- 利用原生的 gr.themes.Soft() 来防止渲染崩溃 ---
212
+ custom_theme = gr.themes.Soft(
213
+ primary_hue="indigo",
214
+ neutral_hue="slate"
215
+ ).set(
216
+ body_background_fill="#f1f5f9",
217
+ block_background_fill="#ffffff",
218
+ block_border_color="#e2e8f0",
219
+ block_border_width="1px",
220
+ block_radius="12px",
221
+ input_background_fill="#f8fafc",
222
+ )
223
+
224
  def build_demo():
225
+ with gr.Blocks(css=CUSTOM_CSS, title="RealRestorer", theme=custom_theme) as demo:
 
 
 
 
226
  gr.HTML(TITLE_HTML)
227
 
228
  # 1:1 Symmetrical Layout
229
+ with gr.Row(equal_height=False):
230
 
231
  # Left Column: Setup & Control (scale=1)
232
+ with gr.Column(scale=1, elem_classes=["rr-panel"]):
233
  gr.HTML("<div class='rr-panel-title'>1. Setup & Control</div>")
234
 
235
+ input_image = gr.Image(label="Upload Image", type="pil", height=320, show_label=False)
236
 
237
  task_dropdown = gr.Dropdown(choices=list(TASK_PRESETS.keys()), value=DEFAULT_PRESET, label="Task Preset")
238
  prompt_box = gr.Textbox(label="Instruction", value=TASK_PRESETS[DEFAULT_PRESET], lines=3)
239
 
 
240
  with gr.Row():
241
  guidance_slider = gr.Slider(minimum=1.0, maximum=6.0, value=DEFAULT_CFG, step=0.1, label="CFG / Guidance Scale")
242
  steps_slider = gr.Slider(minimum=12, maximum=40, value=DEFAULT_STEPS, step=1, label="Inference Steps")
243
 
244
+ with gr.Accordion("Advanced Settings", open=False):
245
  seed_box = gr.Number(label="Seed (-1 for random)", value=DEFAULT_SEED, precision=0)
246
 
247
  run_button = gr.Button("🚀 Run Restoration", elem_id="run-btn")
248
 
249
  # Right Column: Results Preview (scale=1)
250
+ with gr.Column(scale=1, elem_classes=["rr-panel"]):
251
  gr.HTML("<div class='rr-panel-title'>2. Results Preview</div>")
252
 
253
  status_box = gr.HTML(
 
259
  slider_view = gr.HTML(_build_slider_html(None, None))
260
 
261
  with gr.Tab("Output Image"):
262
+ output_image = gr.Image(label="Restored Result", type="pil", interactive=False, height=520, show_label=False)
263
 
264
  # Event Bindings
265
  task_dropdown.change(fn=_on_preset_change, inputs=[task_dropdown], outputs=[prompt_box])
 
281
 
282
  if __name__ == "__main__":
283
  demo = build_demo()
284
+ demo.queue(max_size=8).launch(server_name="0.0.0.0", show_error=True)