eienmojiki commited on
Commit
8a71b8a
·
verified ·
1 Parent(s): 18362de

Update Gradio app with multiple files

Browse files
Files changed (3) hide show
  1. components.py +7 -3
  2. filters.py +528 -1
  3. registry.py +50 -25
components.py CHANGED
@@ -2,14 +2,16 @@ import gradio as gr
2
  from registry import registry
3
 
4
  def create_filter_controls():
 
5
  controls = {}
 
6
  for filter_name in registry.filters:
7
  params = registry.params_map.get(filter_name, {})
8
 
9
  with gr.Group(visible=filter_name == "Original") as filter_group:
10
  filter_controls_list = []
11
 
12
- if params: # Only create controls if there are parameters
13
  for param_name, config in params.items():
14
  if config['type'] == int:
15
  slider = gr.Slider(
@@ -20,6 +22,7 @@ def create_filter_controls():
20
  step=config.get('step', 1),
21
  interactive=True
22
  )
 
23
  elif config['type'] == float:
24
  slider = gr.Slider(
25
  minimum=config.get('min', 0.1),
@@ -29,13 +32,14 @@ def create_filter_controls():
29
  label=f"🎚️ {param_name.replace('_', ' ').title()}",
30
  interactive=True
31
  )
 
32
  elif config['type'] == bool:
33
- slider = gr.Checkbox(
34
  value=config['default'],
35
  label=f"☑️ {param_name.replace('_', ' ').title()}",
36
  interactive=True
37
  )
38
- filter_controls_list.append(slider)
39
  else:
40
  gr.Markdown("*✨ Bộ lọc này không có tham số tùy chỉnh - Nhấn 'Áp dụng' để sử dụng!*")
41
 
 
2
  from registry import registry
3
 
4
  def create_filter_controls():
5
+ """Tạo các điều khiển cho từng bộ lọc"""
6
  controls = {}
7
+
8
  for filter_name in registry.filters:
9
  params = registry.params_map.get(filter_name, {})
10
 
11
  with gr.Group(visible=filter_name == "Original") as filter_group:
12
  filter_controls_list = []
13
 
14
+ if params:
15
  for param_name, config in params.items():
16
  if config['type'] == int:
17
  slider = gr.Slider(
 
22
  step=config.get('step', 1),
23
  interactive=True
24
  )
25
+ filter_controls_list.append(slider)
26
  elif config['type'] == float:
27
  slider = gr.Slider(
28
  minimum=config.get('min', 0.1),
 
32
  label=f"🎚️ {param_name.replace('_', ' ').title()}",
33
  interactive=True
34
  )
35
+ filter_controls_list.append(slider)
36
  elif config['type'] == bool:
37
+ checkbox = gr.Checkbox(
38
  value=config['default'],
39
  label=f"☑️ {param_name.replace('_', ' ').title()}",
40
  interactive=True
41
  )
42
+ filter_controls_list.append(checkbox)
43
  else:
44
  gr.Markdown("*✨ Bộ lọc này không có tham số tùy chỉnh - Nhấn 'Áp dụng' để sử dụng!*")
45
 
filters.py CHANGED
@@ -1 +1,528 @@
1
- *(Giữ nguyên file filters.py)*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from registry import registry
4
+
5
+
6
+ @registry.register("Original")
7
+ def original(image):
8
+ """
9
+ ## Ảnh gốc - không áp dụng bộ lọc nào.
10
+
11
+ **Args:**
12
+ * `image` (numpy.ndarray): Input image
13
+
14
+ **Returns:**
15
+ * `numpy.ndarray`: Original image
16
+ """
17
+ return image
18
+
19
+
20
+ @registry.register("Dot Effect", defaults={
21
+ "dot_size": 10,
22
+ "dot_spacing": 2,
23
+ "invert": False,
24
+ }, min_vals={
25
+ "dot_size": 1,
26
+ "dot_spacing": 1,
27
+ }, max_vals={
28
+ "dot_size": 20,
29
+ "dot_spacing": 10,
30
+ }, step_vals={
31
+ "dot_size": 1,
32
+ "dot_spacing": 1,
33
+ })
34
+ def dot_effect(image, dot_size: int = 10, dot_spacing: int = 2, invert: bool = False):
35
+ """
36
+ ## Chuyển ảnh thành hiệu ứng chấm tròn nghệ thuật.
37
+
38
+ **Args:**
39
+ * `image` (numpy.ndarray): Input image (BGR or grayscale)
40
+ * `dot_size` (int): Kích thước mỗi chấm
41
+ * `dot_spacing` (int): Khoảng cách giữa các chấm
42
+ * `invert` (bool): Đảo ngược màu chấm
43
+
44
+ **Returns:**
45
+ * `numpy.ndarray`: Dotted image
46
+ """
47
+ if len(image.shape) == 3:
48
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
49
+ else:
50
+ gray = image
51
+
52
+ gray = cv2.adaptiveThreshold(
53
+ gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
54
+ cv2.THRESH_BINARY, 25, 5
55
+ )
56
+
57
+ height, width = gray.shape
58
+ canvas = np.zeros_like(gray) if not invert else np.full_like(gray, 255)
59
+
60
+ y_dots = range(0, height, dot_size + dot_spacing)
61
+ x_dots = range(0, width, dot_size + dot_spacing)
62
+
63
+ dot_color = 255 if not invert else 0
64
+ for y in y_dots:
65
+ for x in x_dots:
66
+ region = gray[y:min(y+dot_size, height), x:min(x+dot_size, width)]
67
+ if region.size > 0:
68
+ brightness = np.mean(region)
69
+ relative_brightness = brightness / 255.0
70
+ if invert:
71
+ relative_brightness = 1 - relative_brightness
72
+ radius = int((dot_size/2) * relative_brightness)
73
+ if radius > 0:
74
+ cv2.circle(canvas, (x + dot_size//2, y + dot_size//2),
75
+ radius, (dot_color), -1)
76
+
77
+ return canvas
78
+
79
+
80
+ @registry.register("Pixelize", defaults={
81
+ "pixel_size": 10,
82
+ }, min_vals={
83
+ "pixel_size": 1,
84
+ }, max_vals={
85
+ "pixel_size": 50,
86
+ }, step_vals={
87
+ "pixel_size": 1,
88
+ })
89
+ def pixelize(image, pixel_size: int = 10):
90
+ """
91
+ ## Tạo hiệu ứng pixel hóa cho ảnh (8-bit retro style).
92
+
93
+ **Args:**
94
+ * `image` (numpy.ndarray): Input image
95
+ * `pixel_size` (int): Kích thước mỗi khối pixel
96
+
97
+ **Returns:**
98
+ * `numpy.ndarray`: Pixelized image
99
+ """
100
+ height, width = image.shape[:2]
101
+ small_height = height // pixel_size
102
+ small_width = width // pixel_size
103
+ small_image = cv2.resize(image, (small_width, small_height), interpolation=cv2.INTER_LINEAR)
104
+ pixelized_image = cv2.resize(small_image, (width, height), interpolation=cv2.INTER_NEAREST)
105
+ return pixelized_image
106
+
107
+
108
+ @registry.register("Sketch Effect")
109
+ def sketch_effect(image):
110
+ """
111
+ ## Chuyển ảnh thành bản phác thảo bút chì.
112
+
113
+ **Args:**
114
+ * `image` (numpy.ndarray): Input image
115
+
116
+ **Returns:**
117
+ * `numpy.ndarray`: Sketch effect image
118
+ """
119
+ if len(image.shape) == 3:
120
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
121
+ else:
122
+ gray = image
123
+
124
+ inverted_gray = cv2.bitwise_not(gray)
125
+ blurred = cv2.GaussianBlur(inverted_gray, (21, 21), 0)
126
+ sketch = cv2.divide(gray, 255 - blurred, scale=256)
127
+ return sketch
128
+
129
+
130
+ @registry.register("Warm", defaults={
131
+ "intensity": 30,
132
+ }, min_vals={
133
+ "intensity": 0,
134
+ }, max_vals={
135
+ "intensity": 100,
136
+ }, step_vals={
137
+ "intensity": 1,
138
+ })
139
+ def warm_filter(image, intensity: int = 30):
140
+ """
141
+ ## Thêm tông màu ấm áp cho ảnh (hoàng hôn, mùa thu).
142
+
143
+ **Args:**
144
+ * `image` (numpy.ndarray): Input image (BGR)
145
+ * `intensity` (int): Cường độ hiệu ứng (0-100)
146
+
147
+ **Returns:**
148
+ * `numpy.ndarray`: Warm-toned image
149
+ """
150
+ intensity_scale = intensity / 100.0
151
+ b, g, r = cv2.split(image.astype(np.float32))
152
+ r = np.clip(r * (1 + 0.5 * intensity_scale), 0, 255)
153
+ g = np.clip(g * (1 + 0.1 * intensity_scale), 0, 255)
154
+ b = np.clip(b * (1 - 0.1 * intensity_scale), 0, 255)
155
+ return cv2.merge([b, g, r]).astype(np.uint8)
156
+
157
+
158
+ @registry.register("Cool", defaults={
159
+ "intensity": 30,
160
+ }, min_vals={
161
+ "intensity": 0,
162
+ }, max_vals={
163
+ "intensity": 100,
164
+ }, step_vals={
165
+ "intensity": 1,
166
+ })
167
+ def cool_filter(image, intensity: int = 30):
168
+ """
169
+ ## Thêm tông màu lạnh cho ảnh (băng tuyết, biển xanh).
170
+
171
+ **Args:**
172
+ * `image` (numpy.ndarray): Input image (BGR)
173
+ * `intensity` (int): Cường độ hiệu ứng (0-100)
174
+
175
+ **Returns:**
176
+ * `numpy.ndarray`: Cool-toned image
177
+ """
178
+ intensity_scale = intensity / 100.0
179
+ b, g, r = cv2.split(image.astype(np.float32))
180
+ b = np.clip(b * (1 + 0.5 * intensity_scale), 0, 255)
181
+ g = np.clip(g * (1 + 0.1 * intensity_scale), 0, 255)
182
+ r = np.clip(r * (1 - 0.1 * intensity_scale), 0, 255)
183
+ return cv2.merge([b, g, r]).astype(np.uint8)
184
+
185
+
186
+ @registry.register("Saturation", defaults={
187
+ "factor": 50,
188
+ }, min_vals={
189
+ "factor": 0,
190
+ }, max_vals={
191
+ "factor": 100,
192
+ }, step_vals={
193
+ "factor": 1,
194
+ })
195
+ def adjust_saturation(image, factor: int = 50):
196
+ """
197
+ ## Điều chỉnh độ bão hòa màu sắc của ảnh.
198
+
199
+ **Args:**
200
+ * `image` (numpy.ndarray): Input image (BGR)
201
+ * `factor` (int): Hệ số bão hòa (0-100, 50 là bình thường)
202
+
203
+ **Returns:**
204
+ * `numpy.ndarray`: Saturation-adjusted image
205
+ """
206
+ factor = (factor / 50.0)
207
+ hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV).astype(np.float32)
208
+ hsv[:, :, 1] = np.clip(hsv[:, :, 1] * factor, 0, 255)
209
+ return cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
210
+
211
+
212
+ @registry.register("Vintage", defaults={
213
+ "intensity": 50,
214
+ }, min_vals={
215
+ "intensity": 0,
216
+ }, max_vals={
217
+ "intensity": 100,
218
+ }, step_vals={
219
+ "intensity": 1,
220
+ })
221
+ def vintage_filter(image, intensity: int = 50):
222
+ """
223
+ ## Tạo hiệu ứng ảnh cổ điển/retro (phong cách những năm 70).
224
+
225
+ **Args:**
226
+ * `image` (numpy.ndarray): Input image (BGR)
227
+ * `intensity` (int): Cường độ hiệu ứng vintage (0-100)
228
+
229
+ **Returns:**
230
+ * `numpy.ndarray`: Vintage-styled image
231
+ """
232
+ intensity_scale = intensity / 100.0
233
+ b, g, r = cv2.split(image.astype(np.float32))
234
+ r = np.clip(r * (1 + 0.3 * intensity_scale), 0, 255)
235
+ g = np.clip(g * (1 - 0.1 * intensity_scale), 0, 255)
236
+ b = np.clip(b * (1 - 0.2 * intensity_scale), 0, 255)
237
+ result = cv2.merge([b, g, r]).astype(np.uint8)
238
+ if intensity > 0:
239
+ blur_amount = int(3 * intensity_scale) * 2 + 1
240
+ result = cv2.GaussianBlur(result, (blur_amount, blur_amount), 0)
241
+ return result
242
+
243
+
244
+ @registry.register("Vignette", defaults={
245
+ "intensity": 50,
246
+ }, min_vals={
247
+ "intensity": 0,
248
+ }, max_vals={
249
+ "intensity": 100,
250
+ }, step_vals={
251
+ "intensity": 1,
252
+ })
253
+ def vignette_effect(image, intensity: int = 50):
254
+ """
255
+ ## Thêm hiệu ứng làm tối các góc ảnh (vignette).
256
+
257
+ **Args:**
258
+ * `image` (numpy.ndarray): Input image (BGR)
259
+ * `intensity` (int): Cường độ vignette (0-100)
260
+
261
+ **Returns:**
262
+ * `numpy.ndarray`: Vignetted image
263
+ """
264
+ height, width = image.shape[:2]
265
+ X_resultant = np.abs(np.linspace(-1, 1, width)[None, :])
266
+ Y_resultant = np.abs(np.linspace(-1, 1, height)[:, None])
267
+ mask = np.sqrt(X_resultant**2 + Y_resultant**2)
268
+ mask = 1 - np.clip(mask, 0, 1)
269
+ mask = (mask - mask.min()) / (mask.max() - mask.min())
270
+ mask = mask ** (1 + intensity/50)
271
+ mask = mask[:, :, None]
272
+ result = image.astype(np.float32) * mask
273
+ return np.clip(result, 0, 255).astype(np.uint8)
274
+
275
+
276
+ @registry.register("HDR Effect", defaults={
277
+ "strength": 50,
278
+ }, min_vals={
279
+ "strength": 0,
280
+ }, max_vals={
281
+ "strength": 100,
282
+ }, step_vals={
283
+ "strength": 1,
284
+ })
285
+ def hdr_effect(image, strength: int = 50):
286
+ """
287
+ ## Tăng cường chi tiết ảnh với hiệu ứng HDR.
288
+
289
+ **Args:**
290
+ * `image` (numpy.ndarray): Input image (BGR)
291
+ * `strength` (int): Cường độ HDR (0-100)
292
+
293
+ **Returns:**
294
+ * `numpy.ndarray`: HDR-enhanced image
295
+ """
296
+ strength_scale = strength / 100.0
297
+ lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB).astype(np.float32)
298
+ l, a, b = cv2.split(lab)
299
+ clahe = cv2.createCLAHE(clipLimit=3.0 * strength_scale, tileGridSize=(8, 8))
300
+ l = clahe.apply(l.astype(np.uint8)).astype(np.float32)
301
+ if strength > 0:
302
+ blur = cv2.GaussianBlur(l, (0, 0), 3)
303
+ detail = cv2.addWeighted(l, 1 + strength_scale, blur, -strength_scale, 0)
304
+ l = cv2.addWeighted(l, 1 - strength_scale/2, detail, strength_scale/2, 0)
305
+ enhanced_lab = cv2.merge([l, a, b])
306
+ result = cv2.cvtColor(enhanced_lab.astype(np.uint8), cv2.COLOR_LAB2BGR)
307
+ return result
308
+
309
+
310
+ @registry.register("Gaussian Blur", defaults={
311
+ "kernel_size": 5,
312
+ }, min_vals={
313
+ "kernel_size": 1,
314
+ }, max_vals={
315
+ "kernel_size": 31,
316
+ }, step_vals={
317
+ "kernel_size": 2,
318
+ })
319
+ def gaussian_blur(image, kernel_size: int = 5):
320
+ """
321
+ ## Làm mờ ảnh với bộ lọc Gaussian.
322
+
323
+ **Args:**
324
+ * `image` (numpy.ndarray): Input image
325
+ * `kernel_size` (int): Kích thước kernel (phải là số lẻ)
326
+
327
+ **Returns:**
328
+ * `numpy.ndarray`: Blurred image
329
+ """
330
+ if kernel_size % 2 == 0:
331
+ kernel_size += 1
332
+ return cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
333
+
334
+
335
+ @registry.register("Sharpen", defaults={
336
+ "amount": 50,
337
+ }, min_vals={
338
+ "amount": 0,
339
+ }, max_vals={
340
+ "amount": 100,
341
+ }, step_vals={
342
+ "amount": 1,
343
+ })
344
+ def sharpen(image, amount: int = 50):
345
+ """
346
+ ## Làm sắc nét ảnh.
347
+
348
+ **Args:**
349
+ * `image` (numpy.ndarray): Input image
350
+ * `amount` (int): Cường độ làm sắc nét (0-100)
351
+
352
+ **Returns:**
353
+ * `numpy.ndarray`: Sharpened image
354
+ """
355
+ amount = amount / 100.0
356
+ kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
357
+ sharpened = cv2.filter2D(image, -1, kernel)
358
+ return cv2.addWeighted(image, 1 - amount, sharpened, amount, 0)
359
+
360
+
361
+ @registry.register("Emboss", defaults={
362
+ "strength": 50,
363
+ "direction": 0,
364
+ }, min_vals={
365
+ "strength": 0,
366
+ "direction": 0,
367
+ }, max_vals={
368
+ "strength": 100,
369
+ "direction": 7,
370
+ }, step_vals={
371
+ "strength": 1,
372
+ "direction": 1,
373
+ })
374
+ def emboss(image, strength: int = 50, direction: int = 0):
375
+ """
376
+ ## Tạo hiệu ứng nổi 3D cho ảnh.
377
+
378
+ **Args:**
379
+ * `image` (numpy.ndarray): Input image
380
+ * `strength` (int): Cường độ emboss (0-100)
381
+ * `direction` (int): Hướng ánh sáng (0-7)
382
+
383
+ **Returns:**
384
+ * `numpy.ndarray`: Embossed image
385
+ """
386
+ strength = strength / 100.0 * 2.0
387
+ kernels = [
388
+ np.array([[-1, -1, 0], [-1, 1, 1], [0, 1, 1]]),
389
+ np.array([[-1, 0, 1], [-1, 1, 1], [-1, 0, 1]]),
390
+ np.array([[0, 1, 1], [-1, 1, 1], [-1, -1, 0]]),
391
+ np.array([[1, 1, 1], [0, 1, 0], [-1, -1, -1]]),
392
+ np.array([[1, 1, 0], [1, 1, -1], [0, -1, -1]]),
393
+ np.array([[1, 0, -1], [1, 1, -1], [1, 0, -1]]),
394
+ np.array([[0, -1, -1], [1, 1, -1], [1, 1, 0]]),
395
+ np.array([[-1, -1, -1], [0, 1, 0], [1, 1, 1]])
396
+ ]
397
+ kernel = kernels[direction % 8]
398
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
399
+ embossed = cv2.filter2D(gray, -1, kernel * strength)
400
+ embossed = cv2.normalize(embossed, None, 0, 255, cv2.NORM_MINMAX)
401
+ return cv2.cvtColor(embossed.astype(np.uint8), cv2.COLOR_GRAY2BGR)
402
+
403
+
404
+ @registry.register("Oil Painting", defaults={
405
+ "size": 5,
406
+ "dynRatio": 1,
407
+ }, min_vals={
408
+ "size": 1,
409
+ "dynRatio": 1,
410
+ }, max_vals={
411
+ "size": 15,
412
+ "dynRatio": 7,
413
+ }, step_vals={
414
+ "size": 2,
415
+ "dynRatio": 1,
416
+ })
417
+ def oil_painting(image, size: int = 5, dynRatio: int = 1):
418
+ """
419
+ ## Tạo hiệu ứng tranh sơn dầu.
420
+
421
+ **Args:**
422
+ * `image` (numpy.ndarray): Input image
423
+ * `size` (int): Kích thước vùng xử lý
424
+ * `dynRatio` (int): Tỷ lệ động ảnh hưởng đến cường độ màu
425
+
426
+ **Returns:**
427
+ * `numpy.ndarray`: Oil painting styled image
428
+ """
429
+ return cv2.xphoto.oilPainting(image, size, dynRatio)
430
+
431
+
432
+ @registry.register("Black and White")
433
+ def black_and_white(image):
434
+ """
435
+ ## Chuyển ảnh sang đen trắng cổ điển.
436
+
437
+ **Args:**
438
+ * `image` (numpy.ndarray): Input image
439
+
440
+ **Returns:**
441
+ * `numpy.ndarray`: Grayscale image
442
+ """
443
+ return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
444
+
445
+
446
+ @registry.register("Sepia")
447
+ def sepia(image):
448
+ """
449
+ ## Tạo hiệu ứng sepia tông màu nâu cổ điển.
450
+
451
+ **Args:**
452
+ * `image` (numpy.ndarray): Input image
453
+
454
+ **Returns:**
455
+ * `numpy.ndarray`: Sepia-toned image
456
+ """
457
+ rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
458
+ sepia_matrix = np.array([
459
+ [0.393, 0.769, 0.189],
460
+ [0.349, 0.686, 0.168],
461
+ [0.272, 0.534, 0.131]
462
+ ])
463
+ sepia_image = np.dot(rgb, sepia_matrix.T)
464
+ sepia_image = np.clip(sepia_image, 0, 255)
465
+ return cv2.cvtColor(sepia_image.astype(np.uint8), cv2.COLOR_RGB2BGR)
466
+
467
+
468
+ @registry.register("Negative")
469
+ def negative(image):
470
+ """
471
+ ## Đảo ngược màu sắc tạo hiệu ứng negative film.
472
+
473
+ **Args:**
474
+ * `image` (numpy.ndarray): Input image
475
+
476
+ **Returns:**
477
+ * `numpy.ndarray`: Negative image
478
+ """
479
+ return cv2.bitwise_not(image)
480
+
481
+
482
+ @registry.register("Watercolor")
483
+ def watercolor(image):
484
+ """
485
+ ## Tạo hiệu ứng tranh màu nước.
486
+
487
+ **Args:**
488
+ * `image` (numpy.ndarray): Input image
489
+
490
+ **Returns:**
491
+ * `numpy.ndarray`: Watercolor styled image
492
+ """
493
+ return cv2.xphoto.oilPainting(image, 7, 1)
494
+
495
+
496
+ @registry.register("Posterization")
497
+ def posterize(image):
498
+ """
499
+ ## Giảm số màu tạo hiệu ứng poster nghệ thuật.
500
+
501
+ **Args:**
502
+ * `image` (numpy.ndarray): Input image
503
+
504
+ **Returns:**
505
+ * `numpy.ndarray`: Posterized image
506
+ """
507
+ hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
508
+ hsv[:, :, 1] = cv2.equalizeHist(hsv[:, :, 1])
509
+ hsv[:, :, 2] = cv2.equalizeHist(hsv[:, :, 2])
510
+ return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
511
+
512
+
513
+ @registry.register("Cross Process")
514
+ def cross_process(image):
515
+ """
516
+ ## Hiệu ứng xử lý chéo film (cross-processing).
517
+
518
+ **Args:**
519
+ * `image` (numpy.ndarray): Input image
520
+
521
+ **Returns:**
522
+ * `numpy.ndarray`: Cross-processed image
523
+ """
524
+ b, g, r = cv2.split(image.astype(np.float32))
525
+ b = np.clip(b * 1.2, 0, 255)
526
+ g = np.clip(g * 0.8, 0, 255)
527
+ r = np.clip(r * 1.4, 0, 255)
528
+ return cv2.merge([b, g, r]).astype(np.uint8)
registry.py CHANGED
@@ -1,28 +1,53 @@
1
- *(Giữ nguyên file registry.py)*
 
2
 
3
- **Các cải tiến đã thực hiện:**
 
 
 
 
 
4
 
5
- 1. **📐 Bố cục mới (3 cột):**
6
- - Cột trái (scale=2): Ảnh đầu vào + nút điều khiển chính
7
- - Cột giữa (scale=1): Chọn bộ lọc + tham số tùy chỉnh
8
- - Cột phải (scale=2): Ảnh đầu ra
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- 2. **🌓 Tương thích Dark Mode:**
11
- - CSS riêng cho `.dark` class
12
- - Background và màu chữ tự động điều chỉnh
13
- - Border và shadow tối ưu cho cả 2 chế độ
14
-
15
- 3. **🎨 Cải thiện UX:**
16
- - Ảnh lớn hơn (500px thay vì 400px)
17
- - Nút điều khiển to và rõ ràng hơn
18
- - Thêm panel thống kê số lượng bộ lọc
19
- - Icon emoji cho các control
20
-
21
- 4. **✨ Hiệu ứng:**
22
- - Hover effect mượt mà
23
- - Transform và shadow động
24
- - Transition animation
25
-
26
- 5. **📱 Responsive:**
27
- - Tối ưu cho màn hình lớn với max-width 1600px
28
- - Scale cột hợp lý để hiển thị tốt nhất
 
1
+ from functools import wraps
2
+ import inspect
3
 
4
+ class FilterRegistry:
5
+ """Registry để quản lý các bộ lọc và tham số của chúng"""
6
+
7
+ def __init__(self):
8
+ self.filters = {}
9
+ self.params_map = {}
10
 
11
+ def register(self, name, defaults=None, min_vals=None, max_vals=None, step_vals=None):
12
+ """
13
+ Decorator để đăng một bộ lọc
14
+
15
+ Args:
16
+ name: Tên bộ lọc
17
+ defaults: Giá trị mặc định cho các tham số
18
+ min_vals: Giá trị tối thiểu cho các tham số
19
+ max_vals: Giá trị tối đa cho các tham số
20
+ step_vals: Bước nhảy cho các tham số
21
+ """
22
+ if defaults is None:
23
+ defaults = {}
24
+ if min_vals is None:
25
+ min_vals = {}
26
+ if max_vals is None:
27
+ max_vals = {}
28
+ if step_vals is None:
29
+ step_vals = {}
30
+
31
+ def decorator(func):
32
+ self.filters[name] = func
33
+ sig = inspect.signature(func)
34
+ params = {}
35
+
36
+ for param in sig.parameters.values():
37
+ if param.name == 'image':
38
+ continue
39
+
40
+ params[param.name] = {
41
+ 'type': param.annotation,
42
+ 'default': param.default if param.default != inspect.Parameter.empty else defaults.get(param.name),
43
+ 'min': min_vals.get(param.name),
44
+ 'max': max_vals.get(param.name),
45
+ 'step': step_vals.get(param.name)
46
+ }
47
+
48
+ self.params_map[name] = params
49
+ return func
50
+
51
+ return decorator
52
 
53
+ registry = FilterRegistry()