random2222 commited on
Commit
8e2116d
·
verified ·
1 Parent(s): 89369e2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +508 -154
app.py CHANGED
@@ -4,200 +4,554 @@ import os
4
  import gradio as gr
5
  from PIL import Image
6
  import tempfile
 
7
 
8
  # Custom CSS for styling the interface
9
- custom_css = """
 
10
  .container {
11
- max-width: 1200px;
12
- margin: 0 auto;
13
  }
14
- /* Main styling */
15
  .gradio-container {
16
- font-family: 'Roboto', 'Segoe UI', sans-serif;
17
- color: #f0f0f0;
18
  }
19
  /* Card styling */
20
  .app-card {
21
- border-radius: 16px;
22
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
23
- padding: 24px;
24
- background: linear-gradient(145deg, #2b2f3a, #1f2229);
25
- margin-bottom: 24px;
26
  }
27
  /* Header styling */
28
  h1, h2, h3 {
29
- font-weight: 800 !important;
30
- color: #ffffff !important;
31
  }
32
  /* Labels styling */
33
  label, .label {
34
- font-size: 1rem !important;
35
- font-weight: 600 !important;
36
- color: #d1d1d1 !important;
37
- margin-bottom: 8px !important;
 
 
 
 
 
 
38
  }
39
  /* Button styling */
40
  button.primary {
41
- background: linear-gradient(135deg, #ff7e5f, #feb47b) !important;
42
- color: #ffffff !important;
43
- font-weight: 700 !important;
44
- border-radius: 12px !important;
45
- padding: 14px 28px !important;
46
- font-size: 1.1rem !important;
47
- transition: all 0.3s ease !important;
48
- border: none !important;
49
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15), 0 0 12px rgba(255, 126, 95, 0.4) !important;
50
  }
51
- button.primary:hover {
52
- background: linear-gradient(135deg, #feb47b, #ff7e5f) !important;
53
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.25), 0 0 18px rgba(254, 180, 123, 0.6) !important;
54
- transform: translateY(-2px) !important;
55
  }
56
- /* Slider styling */
57
- .slider-label {
58
- font-weight: 600 !important;
59
- color: #d1d1d1 !important;
60
- font-size: 0.95rem !important;
61
  }
62
- /* Tabs styling */
63
  .tab-nav {
64
- font-weight: 700 !important;
65
- font-size: 1.05rem !important;
66
- color: #ffffff !important;
67
  }
68
- /* Responsive adjustments */
69
  @media (max-width: 768px) {
70
- .gradio-container {
71
- padding: 12px !important;
72
- }
73
- button.primary {
74
- padding: 12px 20px !important;
75
- font-size: 1rem !important;
76
- }
 
 
 
 
 
 
 
 
77
  }
78
  """
79
 
80
  # Enable OpenCL for better performance
 
81
  cv2.ocl.setUseOpenCL(True)
82
 
83
- # ------------------- Image Processing Functions ------------------- #
84
- def convert_to_black_white(image, threshold_value=127, method="otsu"):
85
- if len(image.shape) == 3:
86
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
87
- else:
88
- gray = image
89
-
90
- if method == "adaptive":
91
- return cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
92
- cv2.THRESH_BINARY, 11, 2)
93
- elif method == "otsu":
94
- _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
95
- return binary
96
- else:
97
- _, binary = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY)
98
- return binary
99
-
100
-
101
- def pencil_sketch(image, intensity=255, blur_ksize=21, sigma=0):
102
- if len(image.shape) == 3:
103
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
104
- else:
105
- gray = image
106
- inverted = cv2.bitwise_not(gray)
107
- blur_ksize = blur_ksize if blur_ksize % 2 == 1 else blur_ksize + 1
108
- blurred = cv2.GaussianBlur(inverted, (blur_ksize, blur_ksize), sigma)
109
- sketch = cv2.divide(gray, cv2.bitwise_not(blurred), scale=intensity)
110
- return sketch
111
-
112
- # ------------------- Video Processing ------------------- #
113
- def process_video(func, video_path, is_color, *args):
114
- if not os.path.exists(video_path):
115
- return None, "File not found"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  cap = cv2.VideoCapture(video_path)
117
  if not cap.isOpened():
118
- return None, "Cannot open video"
119
 
 
120
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
121
- w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
122
- h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
123
- fps = cap.get(cv2.CAP_PROP_FPS)
124
- out_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False).name
125
- out = cv2.VideoWriter(out_file, fourcc, fps, (w, h), is_color)
 
 
 
126
 
127
- while True:
 
 
 
 
128
  ret, frame = cap.read()
129
  if not ret:
130
  break
131
- processed = func(frame, *args)
132
- if not is_color:
133
- out.write(processed)
134
- else:
135
- out.write(cv2.cvtColor(processed, cv2.COLOR_GRAY2BGR))
 
136
  cap.release()
137
  out.release()
138
- return out_file, None
139
 
140
- # ------------------- Gradio Interface ------------------- #
141
- def create_interface():
142
- with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  gr.Markdown("""
144
- ## 🎨 Image & Video Transformer
145
- Effortlessly convert your images and videos into stunning pencil sketches or crisp black & white masterpieces.
146
- """, elem_classes="app-card")
147
-
148
- with gr.Tabs():
149
- with gr.TabItem("Pencil Sketch", elem_classes="app-card"): # Default tab
150
- with gr.Row():
151
- with gr.Column():
152
- inp_img = gr.Image(label="Upload Image")
153
- intensity = gr.Slider(1, 255, 255, step=1, label="Sketch Intensity", elem_classes="slider-label")
154
- blur = gr.Slider(1, 99, 21, step=2, label="Blur Kernel Size", elem_classes="slider-label")
155
- sigma = gr.Slider(0, 50, 0, step=0.1, label="Blur Sigma", elem_classes="slider-label")
156
- btn_img = gr.Button("Generate Sketch", elem_classes="primary")
157
- with gr.Column():
158
- out_img = gr.Image(label="Sketch Result")
159
-
160
- btn_img.click(lambda img, i, b, s: Image.fromarray(pencil_sketch(np.array(img.convert('RGB'))[..., ::-1], i, b, s)),
161
- inputs=[inp_img, intensity, blur, sigma], outputs=out_img)
162
-
163
- gr.Markdown("---")
164
-
165
- inp_vid = gr.Video(label="Upload Video")
166
- btn_vid = gr.Button("Sketch Video", elem_classes="primary")
167
- out_vid = gr.Video(label="Sketch Video Result")
168
- btn_vid.click(lambda v, i, b, s: process_video(pencil_sketch, v, True, i, b, s)[0],
169
- inputs=[inp_vid, intensity, blur, sigma], outputs=out_vid)
170
-
171
- with gr.TabItem("Black & White", elem_classes="app-card"): # Secondary tab
172
- with gr.Row():
173
- with gr.Column():
174
- bw_img = gr.Image(label="Upload Image")
175
- method = gr.Radio(["otsu", "adaptive", "manual"], value="otsu", label="Threshold Method")
176
- thresh = gr.Slider(0, 255, 127, step=1, label="Manual Threshold", elem_classes="slider-label")
177
- btn_bw_img = gr.Button("Convert to B&W", elem_classes="primary")
178
- with gr.Column():
179
- out_bw_img = gr.Image(label="B&W Result")
180
-
181
- method.change(lambda m: gr.update(visible=(m=="manual")), inputs=method, outputs=thresh)
182
- btn_bw_img.click(lambda img, m, t: Image.fromarray(convert_to_black_white(np.array(img.convert('RGB'))[..., ::-1], t, m)),
183
- inputs=[bw_img, method, thresh], outputs=out_bw_img)
184
-
185
- gr.Markdown("---")
186
-
187
- bw_vid = gr.Video(label="Upload Video")
188
- btn_bw_vid = gr.Button("B&W Video", elem_classes="primary")
189
- out_bw_vid = gr.Video(label="B&W Video Result")
190
- btn_bw_vid.click(lambda v, m, t: process_video(convert_to_black_white, v, False, t, m)[0],
191
- inputs=[bw_vid, method, thresh], outputs=out_bw_vid)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
 
193
  gr.Markdown("""
194
- **How It Works**
195
- 1. Choose the "Pencil Sketch" tab for sketch effects (default).
196
- 2. Adjust intensity, blur, and sigma to refine the look.
197
- 3. For black & white, switch to the "Black & White" tab.
198
- 4. Upload media, tweak settings, and click the button.
199
- """, elem_classes="container")
200
- return demo
201
-
202
- if __name__ == "__main__":
203
- create_interface().launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import gradio as gr
5
  from PIL import Image
6
  import tempfile
7
+ from typing import Union, Tuple
8
 
9
  # Custom CSS for styling the interface
10
+
11
+ custom\_css = """
12
  .container {
13
+ max-width: 1200px;
14
+ margin: 0 auto;
15
  }
16
+ /\* Main styling */
17
  .gradio-container {
18
+ font-family: 'Roboto', 'Segoe UI', sans-serif;
19
+ color: white;
20
  }
21
  /* Card styling */
22
  .app-card {
23
+ border-radius: 12px;
24
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
25
+ padding: 20px;
26
+ background: linear-gradient(135deg, #2a3a4a 0%, #1e2a3a 100%);
27
+ margin-bottom: 20px;
28
  }
29
  /* Header styling */
30
  h1, h2, h3 {
31
+ font-weight: 700 !important;
32
+ color: white !important;
33
  }
34
  /* Labels styling */
35
  label, .label {
36
+ font-size: 1rem !important;
37
+ font-weight: 600 !important;
38
+ color: white !important;
39
+ margin-bottom: 6px !important;
40
+ }
41
+ /* Input and slider styling */
42
+ .slider-label {
43
+ font-weight: 600 !important;
44
+ color: white !important;
45
+ font-size: 0.95rem !important;
46
  }
47
  /* Button styling */
48
  button.primary {
49
+ background: linear-gradient(135deg, #3498db, #2980b9) !important;
50
+ color: white !important;
51
+ font-weight: 600 !important;
52
+ border-radius: 8px !important;
53
+ padding: 12px 24px !important;
54
+ font-size: 1.1rem !important;
55
+ transition: all 0.3s ease !important;
56
+ border: none !important;
57
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 0 10px rgba(52, 152, 219, 0.4) !important;
58
  }
59
+ button.primary\:hover {
60
+ background: linear-gradient(135deg, #2980b9, #2573a7) !important;
61
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 0 15px rgba(52, 152, 219, 0.6) !important;
62
+ transform: translateY(-2px) !important;
63
  }
64
+ /* Radio buttons */
65
+ .radio-group label {
66
+ font-weight: 600 !important;
67
+ color: white !important;
 
68
  }
69
+ /* Tab styling */
70
  .tab-nav {
71
+ font-weight: 600 !important;
72
+ font-size: 1.05rem !important;
 
73
  }
74
+ /* Responsive adjustments \*/
75
  @media (max-width: 768px) {
76
+ .gradio-container {
77
+ padding: 10px !important;
78
+ }
79
+
80
+ ```
81
+ label, .label {
82
+ font-size: 0.95rem !important;
83
+ }
84
+
85
+ button.primary {
86
+ padding: 10px 18px !important;
87
+ font-size: 1rem !important;
88
+ }
89
+ ```
90
+
91
  }
92
  """
93
 
94
  # Enable OpenCL for better performance
95
+
96
  cv2.ocl.setUseOpenCL(True)
97
 
98
+ # ------------------- Theme Setup -------------------
99
+
100
+ def create\_custom\_theme():
101
+ """Create a custom dark theme for the interface"""
102
+ return gr.themes.Base().set(
103
+ body\_background\_fill="linear-gradient(to bottom right, #1a1f2c, #121620)",
104
+ body\_background\_fill\_dark="linear-gradient(to bottom right, #1a1f2c, #121620)",
105
+ body\_text\_color="white",
106
+ body\_text\_color\_dark="white",
107
+ button\_primary\_background\_fill="linear-gradient(135deg, #3498db, #2980b9)",
108
+ button\_primary\_background\_fill\_hover="linear-gradient(135deg, #2980b9, #2573a7)",
109
+ button\_primary\_text\_color="white",
110
+ button\_primary\_text\_color\_dark="white",
111
+ button\_primary\_border\_color="transparent",
112
+ button\_primary\_border\_color\_dark="transparent",
113
+ button\_secondary\_background\_fill="#34495e",
114
+ button\_secondary\_background\_fill\_hover="#2c3e50",
115
+ button\_secondary\_text\_color="white",
116
+ button\_secondary\_text\_color\_dark="white",
117
+ block\_title\_text\_color="white",
118
+ block\_title\_text\_color\_dark="white",
119
+ block\_label\_text\_color="white",
120
+ block\_label\_text\_color\_dark="white",
121
+ slider\_color="#3498db",
122
+ slider\_color\_dark="#3498db",
123
+ border\_color\_primary="#3498db",
124
+ border\_color\_primary\_dark="#3498db",
125
+ background\_fill\_primary="#2a3a4a",
126
+ background\_fill\_primary\_dark="#2a3a4a",
127
+ background\_fill\_secondary="#1e2a3a",
128
+ background\_fill\_secondary\_dark="#1e2a3a",
129
+ border\_radius\_size="12px",
130
+ spacing\_md="12px",
131
+ spacing\_lg="16px",
132
+ text\_size="16px",
133
+ text\_md="18px",
134
+ text\_lg="20px",
135
+ text\_xl="24px",
136
+ font=\["Roboto", "ui-sans-serif", "system-ui", "sans-serif"],
137
+ )
138
+
139
+ # ------------------- Black & White Converter Functions -------------------
140
+
141
+ def convert\_to\_black\_white(image, threshold\_value=127, method="otsu"):
142
+ """Convert image to black and white using specified thresholding method"""
143
+ if isinstance(image, str):
144
+ image = cv2.imread(image)
145
+
146
+ ```
147
+ # Convert to grayscale if not already
148
+ if len(image.shape) == 3:
149
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
150
+ else:
151
+ gray = image
152
+
153
+ if method == "adaptive":
154
+ binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
155
+ cv2.THRESH_BINARY, 11, 2)
156
+ elif method == "otsu":
157
+ _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
158
+ else:
159
+ _, binary = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY)
160
+
161
+ return binary
162
+ ```
163
+
164
+ def process\_image\_bw(image, threshold\_value, method):
165
+ """Process image with black and white thresholding"""
166
+ if image is None:
167
+ return None
168
+
169
+ ```
170
+ # Convert to numpy array if PIL Image
171
+ if isinstance(image, Image.Image):
172
+ image_np = np.array(image)
173
+ # Convert RGB to BGR for OpenCV
174
+ if len(image_np.shape) == 3:
175
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
176
+ else:
177
+ image_np = image
178
+
179
+ result = convert_to_black_white(image_np, threshold_value, method)
180
+ return result
181
+ ```
182
+
183
+ def process\_video\_bw(video\_path, threshold\_value, method):
184
+ """Process video with black and white thresholding"""
185
+ if not os.path.exists(video\_path):
186
+ return "Video file not found", None
187
+
188
+ ```
189
+ try:
190
+ cap = cv2.VideoCapture(video_path)
191
+ if not cap.isOpened():
192
+ return "Could not open video file", None
193
+
194
+ # Get video properties
195
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
196
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
197
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
198
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
199
+
200
+ # Create temporary output file
201
+ temp_output = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
202
+ output_path = temp_output.name
203
+ temp_output.close()
204
+
205
+ # Create video writer
206
+ out = cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height), isColor=False)
207
+
208
+ # Process each frame
209
+ while cap.isOpened():
210
+ ret, frame = cap.read()
211
+ if not ret:
212
+ break
213
+
214
+ bw_frame = convert_to_black_white(frame, threshold_value, method)
215
+ out.write(bw_frame)
216
+
217
+ cap.release()
218
+ out.release()
219
+
220
+ return "Video processed successfully", output_path
221
+ except Exception as e:
222
+ return f"Error processing video: {str(e)}", None
223
+ ```
224
+
225
+ # ------------------- Pencil Sketch Converter Functions -------------------
226
+
227
+ def process\_image\_sketch(image, intensity=255, blur\_ksize=21, sigma=0):
228
+ """Convert image to pencil sketch effect"""
229
+ if image is None:
230
+ return None
231
+
232
+ ```
233
+ # Convert to numpy array if PIL Image
234
+ if isinstance(image, Image.Image):
235
+ image_np = np.array(image)
236
+ # Convert RGB to BGR for OpenCV
237
+ if len(image_np.shape) == 3:
238
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
239
+ else:
240
+ image_np = image
241
+
242
+ # Convert to grayscale
243
+ gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY) if len(image_np.shape) == 3 else image_np
244
+
245
+ # Create sketch effect
246
+ inverted = cv2.bitwise_not(gray)
247
+ blur_ksize = blur_ksize if blur_ksize % 2 == 1 else blur_ksize + 1 # Ensure kernel size is odd
248
+ blurred = cv2.GaussianBlur(inverted, (blur_ksize, blur_ksize), sigma)
249
+ sketch = cv2.divide(gray, cv2.bitwise_not(blurred), scale=intensity)
250
+
251
+ return sketch
252
+ ```
253
+
254
+ def process\_video\_sketch(video\_path, intensity=255, blur\_ksize=21, sigma=0):
255
+ """Process video with pencil sketch effect"""
256
+ if not os.path.exists(video\_path):
257
+ return "Video file not found", None
258
+
259
+ ```
260
+ try:
261
  cap = cv2.VideoCapture(video_path)
262
  if not cap.isOpened():
263
+ return "Could not open video file", None
264
 
265
+ # Get video properties
266
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
267
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
268
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
269
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
270
+
271
+ # Create temporary output file
272
+ temp_output = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
273
+ output_path = temp_output.name
274
+ temp_output.close()
275
 
276
+ # Create video writer
277
+ out = cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height), isColor=True)
278
+
279
+ # Process each frame
280
+ while cap.isOpened():
281
  ret, frame = cap.read()
282
  if not ret:
283
  break
284
+
285
+ sketch_frame = process_image_sketch(frame, intensity, blur_ksize, sigma)
286
+ # Convert grayscale to BGR for video output
287
+ sketch_bgr = cv2.cvtColor(sketch_frame, cv2.COLOR_GRAY2BGR)
288
+ out.write(sketch_bgr)
289
+
290
  cap.release()
291
  out.release()
 
292
 
293
+ return "Video processed successfully", output_path
294
+ except Exception as e:
295
+ return f"Error processing video: {str(e)}", None
296
+ ```
297
+
298
+ # ------------------- Gradio Interface Functions -------------------
299
+
300
+ def black\_white\_image(image, threshold\_method, threshold\_value):
301
+ """Process image with black and white filter for Gradio"""
302
+ if threshold\_method != "manual":
303
+ threshold\_value = 0 # Not used for adaptive or Otsu
304
+
305
+ ```
306
+ result = process_image_bw(image, threshold_value, threshold_method)
307
+ return Image.fromarray(result)
308
+ ```
309
+
310
+ def black\_white\_video(video, threshold\_method, threshold\_value):
311
+ """Process video with black and white filter for Gradio"""
312
+ if threshold\_method != "manual":
313
+ threshold\_value = 0 # Not used for adaptive or Otsu
314
+
315
+ ```
316
+ message, output_path = process_video_bw(video, threshold_value, threshold_method)
317
+ if output_path:
318
+ return output_path
319
+ else:
320
+ raise gr.Error(message)
321
+ ```
322
+
323
+ def sketch\_image(image, intensity, blur\_ksize, sigma):
324
+ """Process image with pencil sketch filter for Gradio"""
325
+ result = process\_image\_sketch(image, intensity, blur\_ksize, sigma)
326
+ return Image.fromarray(result)
327
+
328
+ def sketch\_video(video, intensity, blur\_ksize, sigma):
329
+ """Process video with pencil sketch filter for Gradio"""
330
+ message, output\_path = process\_video\_sketch(video, intensity, blur\_ksize, sigma)
331
+ if output\_path:
332
+ return output\_path
333
+ else:
334
+ raise gr.Error(message)
335
+
336
+ # ------------------- Create Gradio Interface -------------------
337
+
338
+ def create\_interface():
339
+ \# Tooltip content
340
+ otsu\_tooltip = "Otsu automatically determines the optimal threshold value by analyzing the image histogram."
341
+ adaptive\_tooltip = "Adaptive thresholding calculates different thresholds for different areas of the image, useful for images with varying lighting conditions."
342
+ manual\_tooltip = "Manual threshold lets you set a specific brightness cutoff point between black and white pixels."
343
+ intensity\_tooltip = "Controls the strength of the pencil sketch effect. Higher values create more contrast."
344
+ blur\_tooltip = "Controls how much the image is blurred. Higher values create a softer sketch effect."
345
+ sigma\_tooltip = "Controls the standard deviation of the Gaussian blur. Higher values increase the blurring effect."
346
+
347
+ ```
348
+ # Black and White Image Interface
349
+ with gr.Blocks(title="Image Processor", css=custom_css, theme=gr.themes.Base()) as app:
350
+ with gr.Row(elem_classes="container"):
351
  gr.Markdown("""
352
+ # Image and Video Processor
353
+ Transform your media with professional black & white conversion and pencil sketch effects
354
+ """)
355
+
356
+ with gr.Tabs(value="Pencil Sketch Converter") as tabs:
357
+ with gr.TabItem("Black & White Converter", elem_classes="app-card"):
358
+ with gr.Tabs() as bw_tabs:
359
+ with gr.TabItem("Image Processing"):
360
+ with gr.Row(equal_height=True):
361
+ with gr.Column(scale=1):
362
+ bw_image_input = gr.Image(label="Input Image", elem_classes="input-image")
363
+
364
+ with gr.Group():
365
+ bw_method = gr.Radio(
366
+ choices=["otsu", "adaptive", "manual"],
367
+ value="otsu",
368
+ label="Thresholding Method",
369
+ info=otsu_tooltip,
370
+ elem_classes="radio-group"
371
+ )
372
+
373
+ bw_threshold = gr.Slider(
374
+ minimum=0,
375
+ maximum=255,
376
+ value=127,
377
+ step=1,
378
+ label="Manual Threshold Value",
379
+ info=manual_tooltip,
380
+ interactive=True,
381
+ elem_classes="slider-label"
382
+ )
383
+
384
+ bw_image_btn = gr.Button("Convert", elem_classes="primary")
385
+
386
+ with gr.Column(scale=1):
387
+ bw_image_output = gr.Image(label="Processed Image")
388
+
389
+ with gr.TabItem("Video Processing"):
390
+ with gr.Row(equal_height=True):
391
+ with gr.Column(scale=1):
392
+ bw_video_input = gr.Video(label="Input Video")
393
+
394
+ with gr.Group():
395
+ bw_video_method = gr.Radio(
396
+ choices=["otsu", "adaptive", "manual"],
397
+ value="otsu",
398
+ label="Thresholding Method",
399
+ info=otsu_tooltip,
400
+ elem_classes="radio-group"
401
+ )
402
+
403
+ bw_video_threshold = gr.Slider(
404
+ minimum=0,
405
+ maximum=255,
406
+ value=127,
407
+ step=1,
408
+ label="Manual Threshold Value",
409
+ info=manual_tooltip,
410
+ interactive=True,
411
+ elem_classes="slider-label"
412
+ )
413
+
414
+ bw_video_btn = gr.Button("Convert", elem_classes="primary")
415
+
416
+ with gr.Column(scale=1):
417
+ bw_video_output = gr.Video(label="Processed Video")
418
+
419
+ with gr.TabItem("Pencil Sketch Converter", elem_classes="app-card"):
420
+ with gr.Tabs() as sketch_tabs:
421
+ with gr.TabItem("Image Processing"):
422
+ with gr.Row(equal_height=True):
423
+ with gr.Column(scale=1):
424
+ sketch_image_input = gr.Image(label="Input Image")
425
+
426
+ with gr.Group():
427
+ sketch_intensity = gr.Slider(
428
+ minimum=1,
429
+ maximum=255,
430
+ value=255,
431
+ step=1,
432
+ label="Intensity",
433
+ info=intensity_tooltip,
434
+ elem_classes="slider-label"
435
+ )
436
+
437
+ sketch_blur = gr.Slider(
438
+ minimum=1,
439
+ maximum=99,
440
+ value=21,
441
+ step=2,
442
+ label="Blur Kernel Size",
443
+ info=blur_tooltip,
444
+ elem_classes="slider-label"
445
+ )
446
+
447
+ sketch_sigma = gr.Slider(
448
+ minimum=0,
449
+ maximum=50,
450
+ value=0,
451
+ step=0.1,
452
+ label="Standard Deviation",
453
+ info=sigma_tooltip,
454
+ elem_classes="slider-label"
455
+ )
456
+
457
+ sketch_image_btn = gr.Button("Convert", elem_classes="primary")
458
+
459
+ with gr.Column(scale=1):
460
+ sketch_image_output = gr.Image(label="Processed Image")
461
+
462
+ with gr.TabItem("Video Processing"):
463
+ with gr.Row(equal_height=True):
464
+ with gr.Column(scale=1):
465
+ sketch_video_input = gr.Video(label="Input Video")
466
+
467
+ with gr.Group():
468
+ sketch_video_intensity = gr.Slider(
469
+ minimum=1,
470
+ maximum=255,
471
+ value=255,
472
+ step=1,
473
+ label="Intensity",
474
+ info=intensity_tooltip,
475
+ elem_classes="slider-label"
476
+ )
477
+
478
+ sketch_video_blur = gr.Slider(
479
+ minimum=1,
480
+ maximum=99,
481
+ value=21,
482
+ step=2,
483
+ label="Blur Kernel Size",
484
+ info=blur_tooltip,
485
+ elem_classes="slider-label"
486
+ )
487
+
488
+ sketch_video_sigma = gr.Slider(
489
+ minimum=0,
490
+ maximum=50,
491
+ value=0,
492
+ step=0.1,
493
+ label="Standard Deviation",
494
+ info=sigma_tooltip,
495
+ elem_classes="slider-label"
496
+ )
497
+
498
+ sketch_video_btn = gr.Button("Convert", elem_classes="primary")
499
+
500
+ with gr.Column(scale=1):
501
+ sketch_video_output = gr.Video(label="Processed Video")
502
 
503
+ with gr.Row(elem_classes="container"):
504
  gr.Markdown("""
505
+ ### How to use:
506
+ 1. Upload an image or video
507
+ 2. Adjust the settings as needed
508
+ 3. Click the Convert button to process your media
509
+ """)
510
+
511
+ # Set up event listeners
512
+ bw_image_btn.click(
513
+ fn=black_white_image,
514
+ inputs=[bw_image_input, bw_method, bw_threshold],
515
+ outputs=bw_image_output
516
+ )
517
+
518
+ bw_video_btn.click(
519
+ fn=black_white_video,
520
+ inputs=[bw_video_input, bw_video_method, bw_video_threshold],
521
+ outputs=bw_video_output
522
+ )
523
+
524
+ sketch_image_btn.click(
525
+ fn=sketch_image,
526
+ inputs=[sketch_image_input, sketch_intensity, sketch_blur, sketch_sigma],
527
+ outputs=sketch_image_output
528
+ )
529
+
530
+ sketch_video_btn.click(
531
+ fn=sketch_video,
532
+ inputs=[sketch_video_input, sketch_video_intensity, sketch_video_blur, sketch_video_sigma],
533
+ outputs=sketch_video_output
534
+ )
535
+
536
+ # Make blur slider always odd
537
+ def update_blur(value):
538
+ return value if value % 2 == 1 else value + 1
539
+
540
+ sketch_blur.change(update_blur, sketch_blur, sketch_blur)
541
+ sketch_video_blur.change(update_blur, sketch_video_blur, sketch_video_blur)
542
+
543
+ # Add visibility toggle based on method selection
544
+ def update_threshold_visibility(method):
545
+ return gr.update(visible=(method == "manual"))
546
+
547
+ bw_method.change(update_threshold_visibility, bw_method, bw_threshold)
548
+ bw_video_method.change(update_threshold_visibility, bw_video_method, bw_video_threshold)
549
+
550
+ return app
551
+ ```
552
+
553
+ # ------------------- Launch App -------------------
554
+
555
+ if **name** == "**main**":
556
+ app = create\_interface()
557
+ app.launch()