Spaces:
Sleeping
Sleeping
Update Gradio app with multiple files
Browse files- app.py +58 -53
- components.py +2 -2
- filters.py +56 -37
- requirements.txt +6 -2
app.py
CHANGED
|
@@ -108,74 +108,79 @@ def create_app():
|
|
| 108 |
with gr.Column(elem_classes="filter-header"):
|
| 109 |
gr.Markdown("""
|
| 110 |
# 📷 Photo Filter App
|
| 111 |
-
|
| 112 |
|
| 113 |
Built with [anycoder](https://huggingface.co/spaces/akhaliq/anycoder)
|
| 114 |
""")
|
| 115 |
|
| 116 |
-
#
|
| 117 |
filter_names = list(registry.filters.keys())
|
| 118 |
|
|
|
|
| 119 |
with gr.Row():
|
| 120 |
-
# Left Column - Input
|
| 121 |
-
with gr.Column(scale=
|
| 122 |
with gr.Group(elem_classes="image-container"):
|
| 123 |
input_image = gr.Image(
|
| 124 |
-
label="📤
|
| 125 |
type="numpy",
|
| 126 |
height=500
|
| 127 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
with gr.Row():
|
| 130 |
-
|
| 131 |
-
"
|
| 132 |
-
|
| 133 |
-
size="lg"
|
| 134 |
-
scale=2
|
| 135 |
-
)
|
| 136 |
-
reset_button = gr.Button(
|
| 137 |
-
"🔄 Làm mới",
|
| 138 |
-
variant="secondary",
|
| 139 |
-
size="lg",
|
| 140 |
-
scale=1
|
| 141 |
)
|
| 142 |
-
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
with gr.Column(scale=1):
|
| 145 |
with gr.Group(elem_classes="control-panel"):
|
| 146 |
-
gr.Markdown("### 🎨
|
| 147 |
filter_select = gr.Dropdown(
|
| 148 |
-
label="
|
| 149 |
choices=filter_names,
|
| 150 |
value="Original",
|
| 151 |
interactive=True
|
| 152 |
)
|
| 153 |
|
| 154 |
filter_doc = gr.Markdown(
|
| 155 |
-
value="
|
| 156 |
elem_classes="filter-description"
|
| 157 |
)
|
| 158 |
-
|
| 159 |
-
gr.Markdown("### ⚙️ Tùy chỉnh")
|
| 160 |
-
# Tạo các điều khiển bộ lọc động
|
| 161 |
-
filter_controls, control_components = create_filter_controls()
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
height=500
|
| 169 |
-
)
|
| 170 |
-
|
| 171 |
-
error_message = gr.Markdown(visible=False)
|
| 172 |
-
|
| 173 |
-
with gr.Row():
|
| 174 |
-
download_button = gr.Button(
|
| 175 |
-
"💾 Tải xuống",
|
| 176 |
-
visible=False,
|
| 177 |
-
size="lg"
|
| 178 |
-
)
|
| 179 |
|
| 180 |
# Stats panel
|
| 181 |
with gr.Row():
|
|
@@ -183,31 +188,31 @@ def create_app():
|
|
| 183 |
gr.Markdown(
|
| 184 |
f"""
|
| 185 |
<div class="stats-panel">
|
| 186 |
-
📊 <b>
|
| 187 |
-
🎯 <b>
|
| 188 |
-
🚀 <b>
|
| 189 |
</div>
|
| 190 |
"""
|
| 191 |
)
|
| 192 |
|
| 193 |
-
#
|
| 194 |
def update_controls(filter_name):
|
| 195 |
updates = []
|
| 196 |
for group_name in filter_controls:
|
| 197 |
updates.append(gr.update(visible=group_name == filter_name))
|
| 198 |
|
| 199 |
-
doc = registry.filters[filter_name].__doc__ or "
|
| 200 |
return updates + [doc]
|
| 201 |
|
| 202 |
-
#
|
| 203 |
def process(image, filter_name, *args):
|
| 204 |
if image is None:
|
| 205 |
-
return None, gr.update(visible=True, value="⚠️ **
|
| 206 |
|
| 207 |
try:
|
| 208 |
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
| 209 |
|
| 210 |
-
#
|
| 211 |
params = {}
|
| 212 |
if filter_name in control_components:
|
| 213 |
param_names = list(registry.params_map.get(filter_name, {}).keys())
|
|
@@ -225,18 +230,18 @@ def create_app():
|
|
| 225 |
|
| 226 |
return processed, gr.update(visible=False), gr.update(visible=True)
|
| 227 |
except Exception as e:
|
| 228 |
-
return None, gr.update(visible=True, value=f"❌ **
|
| 229 |
|
| 230 |
-
#
|
| 231 |
def reset_all():
|
| 232 |
return None, None, gr.update(visible=False), gr.update(visible=False), "Original"
|
| 233 |
|
| 234 |
-
#
|
| 235 |
all_control_components = []
|
| 236 |
for filter_name in control_components:
|
| 237 |
all_control_components.extend(control_components[filter_name])
|
| 238 |
|
| 239 |
-
#
|
| 240 |
filter_select.change(
|
| 241 |
update_controls,
|
| 242 |
inputs=filter_select,
|
|
|
|
| 108 |
with gr.Column(elem_classes="filter-header"):
|
| 109 |
gr.Markdown("""
|
| 110 |
# 📷 Photo Filter App
|
| 111 |
+
Professional photo editing with powerful filters - Fast & Easy
|
| 112 |
|
| 113 |
Built with [anycoder](https://huggingface.co/spaces/akhaliq/anycoder)
|
| 114 |
""")
|
| 115 |
|
| 116 |
+
# Initialize components
|
| 117 |
filter_names = list(registry.filters.keys())
|
| 118 |
|
| 119 |
+
# Top Row - Images side by side
|
| 120 |
with gr.Row():
|
| 121 |
+
# Left Column - Input Image
|
| 122 |
+
with gr.Column(scale=1):
|
| 123 |
with gr.Group(elem_classes="image-container"):
|
| 124 |
input_image = gr.Image(
|
| 125 |
+
label="📤 Original Image",
|
| 126 |
type="numpy",
|
| 127 |
height=500
|
| 128 |
)
|
| 129 |
+
|
| 130 |
+
# Right Column - Output Image
|
| 131 |
+
with gr.Column(scale=1):
|
| 132 |
+
with gr.Group(elem_classes="image-container"):
|
| 133 |
+
output_image = gr.Image(
|
| 134 |
+
label="✅ Edited Image",
|
| 135 |
+
height=500
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
error_message = gr.Markdown(visible=False)
|
| 139 |
|
| 140 |
with gr.Row():
|
| 141 |
+
download_button = gr.Button(
|
| 142 |
+
"💾 Download",
|
| 143 |
+
visible=False,
|
| 144 |
+
size="lg"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
)
|
| 146 |
+
|
| 147 |
+
# Action Buttons
|
| 148 |
+
with gr.Row():
|
| 149 |
+
apply_button = gr.Button(
|
| 150 |
+
"✨ Apply Filter",
|
| 151 |
+
variant="primary",
|
| 152 |
+
size="lg",
|
| 153 |
+
scale=2
|
| 154 |
+
)
|
| 155 |
+
reset_button = gr.Button(
|
| 156 |
+
"🔄 Reset",
|
| 157 |
+
variant="secondary",
|
| 158 |
+
size="lg",
|
| 159 |
+
scale=1
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
# Bottom Row - Filter Selection & Parameters
|
| 163 |
+
with gr.Row():
|
| 164 |
with gr.Column(scale=1):
|
| 165 |
with gr.Group(elem_classes="control-panel"):
|
| 166 |
+
gr.Markdown("### 🎨 Select Filter")
|
| 167 |
filter_select = gr.Dropdown(
|
| 168 |
+
label="Filter",
|
| 169 |
choices=filter_names,
|
| 170 |
value="Original",
|
| 171 |
interactive=True
|
| 172 |
)
|
| 173 |
|
| 174 |
filter_doc = gr.Markdown(
|
| 175 |
+
value="Select a filter to see detailed description.",
|
| 176 |
elem_classes="filter-description"
|
| 177 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
+
with gr.Column(scale=1):
|
| 180 |
+
with gr.Group(elem_classes="control-panel"):
|
| 181 |
+
gr.Markdown("### ⚙️ Customize")
|
| 182 |
+
# Create dynamic filter controls
|
| 183 |
+
filter_controls, control_components = create_filter_controls()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
# Stats panel
|
| 186 |
with gr.Row():
|
|
|
|
| 188 |
gr.Markdown(
|
| 189 |
f"""
|
| 190 |
<div class="stats-panel">
|
| 191 |
+
📊 <b>Total Filters:</b> {len(filter_names)} |
|
| 192 |
+
🎯 <b>Parameterized Filters:</b> {sum(1 for f in filter_names if registry.params_map.get(f))} |
|
| 193 |
+
🚀 <b>Quick Filters:</b> {sum(1 for f in filter_names if not registry.params_map.get(f))}
|
| 194 |
</div>
|
| 195 |
"""
|
| 196 |
)
|
| 197 |
|
| 198 |
+
# Handle UI updates
|
| 199 |
def update_controls(filter_name):
|
| 200 |
updates = []
|
| 201 |
for group_name in filter_controls:
|
| 202 |
updates.append(gr.update(visible=group_name == filter_name))
|
| 203 |
|
| 204 |
+
doc = registry.filters[filter_name].__doc__ or "No detailed description available."
|
| 205 |
return updates + [doc]
|
| 206 |
|
| 207 |
+
# Handle image processing
|
| 208 |
def process(image, filter_name, *args):
|
| 209 |
if image is None:
|
| 210 |
+
return None, gr.update(visible=True, value="⚠️ **Note:** Please upload an image before applying filters!"), gr.update(visible=False)
|
| 211 |
|
| 212 |
try:
|
| 213 |
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
| 214 |
|
| 215 |
+
# Get parameters for current filter
|
| 216 |
params = {}
|
| 217 |
if filter_name in control_components:
|
| 218 |
param_names = list(registry.params_map.get(filter_name, {}).keys())
|
|
|
|
| 230 |
|
| 231 |
return processed, gr.update(visible=False), gr.update(visible=True)
|
| 232 |
except Exception as e:
|
| 233 |
+
return None, gr.update(visible=True, value=f"❌ **Error:** {str(e)}"), gr.update(visible=False)
|
| 234 |
|
| 235 |
+
# Handle reset
|
| 236 |
def reset_all():
|
| 237 |
return None, None, gr.update(visible=False), gr.update(visible=False), "Original"
|
| 238 |
|
| 239 |
+
# Collect all control components
|
| 240 |
all_control_components = []
|
| 241 |
for filter_name in control_components:
|
| 242 |
all_control_components.extend(control_components[filter_name])
|
| 243 |
|
| 244 |
+
# Connect events
|
| 245 |
filter_select.change(
|
| 246 |
update_controls,
|
| 247 |
inputs=filter_select,
|
components.py
CHANGED
|
@@ -2,7 +2,7 @@ import gradio as gr
|
|
| 2 |
from registry import registry
|
| 3 |
|
| 4 |
def create_filter_controls():
|
| 5 |
-
"""
|
| 6 |
controls = {}
|
| 7 |
control_components = {}
|
| 8 |
|
|
@@ -42,7 +42,7 @@ def create_filter_controls():
|
|
| 42 |
)
|
| 43 |
filter_controls_list.append(checkbox)
|
| 44 |
else:
|
| 45 |
-
gr.Markdown("*✨
|
| 46 |
|
| 47 |
controls[filter_name] = filter_group
|
| 48 |
control_components[filter_name] = filter_controls_list
|
|
|
|
| 2 |
from registry import registry
|
| 3 |
|
| 4 |
def create_filter_controls():
|
| 5 |
+
"""Create controls for each filter"""
|
| 6 |
controls = {}
|
| 7 |
control_components = {}
|
| 8 |
|
|
|
|
| 42 |
)
|
| 43 |
filter_controls_list.append(checkbox)
|
| 44 |
else:
|
| 45 |
+
gr.Markdown("*✨ This filter has no custom parameters - Just click 'Apply' to use it!*")
|
| 46 |
|
| 47 |
controls[filter_name] = filter_group
|
| 48 |
control_components[filter_name] = filter_controls_list
|
filters.py
CHANGED
|
@@ -6,7 +6,7 @@ from registry import registry
|
|
| 6 |
@registry.register("Original")
|
| 7 |
def original(image):
|
| 8 |
"""
|
| 9 |
-
##
|
| 10 |
|
| 11 |
**Args:**
|
| 12 |
* `image` (numpy.ndarray): Input image
|
|
@@ -33,13 +33,13 @@ def original(image):
|
|
| 33 |
})
|
| 34 |
def dot_effect(image, dot_size: int = 10, dot_spacing: int = 2, invert: bool = False):
|
| 35 |
"""
|
| 36 |
-
##
|
| 37 |
|
| 38 |
**Args:**
|
| 39 |
* `image` (numpy.ndarray): Input image (BGR or grayscale)
|
| 40 |
-
* `dot_size` (int):
|
| 41 |
-
* `dot_spacing` (int):
|
| 42 |
-
* `invert` (bool):
|
| 43 |
|
| 44 |
**Returns:**
|
| 45 |
* `numpy.ndarray`: Dotted image
|
|
@@ -88,11 +88,11 @@ def dot_effect(image, dot_size: int = 10, dot_spacing: int = 2, invert: bool = F
|
|
| 88 |
})
|
| 89 |
def pixelize(image, pixel_size: int = 10):
|
| 90 |
"""
|
| 91 |
-
##
|
| 92 |
|
| 93 |
**Args:**
|
| 94 |
* `image` (numpy.ndarray): Input image
|
| 95 |
-
* `pixel_size` (int):
|
| 96 |
|
| 97 |
**Returns:**
|
| 98 |
* `numpy.ndarray`: Pixelized image
|
|
@@ -108,7 +108,7 @@ def pixelize(image, pixel_size: int = 10):
|
|
| 108 |
@registry.register("Sketch Effect")
|
| 109 |
def sketch_effect(image):
|
| 110 |
"""
|
| 111 |
-
##
|
| 112 |
|
| 113 |
**Args:**
|
| 114 |
* `image` (numpy.ndarray): Input image
|
|
@@ -138,11 +138,11 @@ def sketch_effect(image):
|
|
| 138 |
})
|
| 139 |
def warm_filter(image, intensity: int = 30):
|
| 140 |
"""
|
| 141 |
-
##
|
| 142 |
|
| 143 |
**Args:**
|
| 144 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 145 |
-
* `intensity` (int):
|
| 146 |
|
| 147 |
**Returns:**
|
| 148 |
* `numpy.ndarray`: Warm-toned image
|
|
@@ -166,11 +166,11 @@ def warm_filter(image, intensity: int = 30):
|
|
| 166 |
})
|
| 167 |
def cool_filter(image, intensity: int = 30):
|
| 168 |
"""
|
| 169 |
-
##
|
| 170 |
|
| 171 |
**Args:**
|
| 172 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 173 |
-
* `intensity` (int):
|
| 174 |
|
| 175 |
**Returns:**
|
| 176 |
* `numpy.ndarray`: Cool-toned image
|
|
@@ -194,11 +194,11 @@ def cool_filter(image, intensity: int = 30):
|
|
| 194 |
})
|
| 195 |
def adjust_saturation(image, factor: int = 50):
|
| 196 |
"""
|
| 197 |
-
##
|
| 198 |
|
| 199 |
**Args:**
|
| 200 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 201 |
-
* `factor` (int):
|
| 202 |
|
| 203 |
**Returns:**
|
| 204 |
* `numpy.ndarray`: Saturation-adjusted image
|
|
@@ -220,11 +220,11 @@ def adjust_saturation(image, factor: int = 50):
|
|
| 220 |
})
|
| 221 |
def vintage_filter(image, intensity: int = 50):
|
| 222 |
"""
|
| 223 |
-
##
|
| 224 |
|
| 225 |
**Args:**
|
| 226 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 227 |
-
* `intensity` (int):
|
| 228 |
|
| 229 |
**Returns:**
|
| 230 |
* `numpy.ndarray`: Vintage-styled image
|
|
@@ -252,11 +252,11 @@ def vintage_filter(image, intensity: int = 50):
|
|
| 252 |
})
|
| 253 |
def vignette_effect(image, intensity: int = 50):
|
| 254 |
"""
|
| 255 |
-
##
|
| 256 |
|
| 257 |
**Args:**
|
| 258 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 259 |
-
* `intensity` (int):
|
| 260 |
|
| 261 |
**Returns:**
|
| 262 |
* `numpy.ndarray`: Vignetted image
|
|
@@ -284,11 +284,11 @@ def vignette_effect(image, intensity: int = 50):
|
|
| 284 |
})
|
| 285 |
def hdr_effect(image, strength: int = 50):
|
| 286 |
"""
|
| 287 |
-
##
|
| 288 |
|
| 289 |
**Args:**
|
| 290 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 291 |
-
* `strength` (int):
|
| 292 |
|
| 293 |
**Returns:**
|
| 294 |
* `numpy.ndarray`: HDR-enhanced image
|
|
@@ -318,11 +318,11 @@ def hdr_effect(image, strength: int = 50):
|
|
| 318 |
})
|
| 319 |
def gaussian_blur(image, kernel_size: int = 5):
|
| 320 |
"""
|
| 321 |
-
##
|
| 322 |
|
| 323 |
**Args:**
|
| 324 |
* `image` (numpy.ndarray): Input image
|
| 325 |
-
* `kernel_size` (int):
|
| 326 |
|
| 327 |
**Returns:**
|
| 328 |
* `numpy.ndarray`: Blurred image
|
|
@@ -343,11 +343,11 @@ def gaussian_blur(image, kernel_size: int = 5):
|
|
| 343 |
})
|
| 344 |
def sharpen(image, amount: int = 50):
|
| 345 |
"""
|
| 346 |
-
##
|
| 347 |
|
| 348 |
**Args:**
|
| 349 |
* `image` (numpy.ndarray): Input image
|
| 350 |
-
* `amount` (int):
|
| 351 |
|
| 352 |
**Returns:**
|
| 353 |
* `numpy.ndarray`: Sharpened image
|
|
@@ -373,12 +373,12 @@ def sharpen(image, amount: int = 50):
|
|
| 373 |
})
|
| 374 |
def emboss(image, strength: int = 50, direction: int = 0):
|
| 375 |
"""
|
| 376 |
-
##
|
| 377 |
|
| 378 |
**Args:**
|
| 379 |
* `image` (numpy.ndarray): Input image
|
| 380 |
-
* `strength` (int):
|
| 381 |
-
* `direction` (int):
|
| 382 |
|
| 383 |
**Returns:**
|
| 384 |
* `numpy.ndarray`: Embossed image
|
|
@@ -416,12 +416,12 @@ def emboss(image, strength: int = 50, direction: int = 0):
|
|
| 416 |
})
|
| 417 |
def oil_painting(image, size: int = 5, dynRatio: int = 1):
|
| 418 |
"""
|
| 419 |
-
##
|
| 420 |
|
| 421 |
**Args:**
|
| 422 |
* `image` (numpy.ndarray): Input image
|
| 423 |
-
* `size` (int):
|
| 424 |
-
* `dynRatio` (int):
|
| 425 |
|
| 426 |
**Returns:**
|
| 427 |
* `numpy.ndarray`: Oil painting styled image
|
|
@@ -432,7 +432,7 @@ def oil_painting(image, size: int = 5, dynRatio: int = 1):
|
|
| 432 |
@registry.register("Black and White")
|
| 433 |
def black_and_white(image):
|
| 434 |
"""
|
| 435 |
-
##
|
| 436 |
|
| 437 |
**Args:**
|
| 438 |
* `image` (numpy.ndarray): Input image
|
|
@@ -446,7 +446,7 @@ def black_and_white(image):
|
|
| 446 |
@registry.register("Sepia")
|
| 447 |
def sepia(image):
|
| 448 |
"""
|
| 449 |
-
##
|
| 450 |
|
| 451 |
**Args:**
|
| 452 |
* `image` (numpy.ndarray): Input image
|
|
@@ -468,7 +468,7 @@ def sepia(image):
|
|
| 468 |
@registry.register("Negative")
|
| 469 |
def negative(image):
|
| 470 |
"""
|
| 471 |
-
##
|
| 472 |
|
| 473 |
**Args:**
|
| 474 |
* `image` (numpy.ndarray): Input image
|
|
@@ -482,7 +482,7 @@ def negative(image):
|
|
| 482 |
@registry.register("Watercolor")
|
| 483 |
def watercolor(image):
|
| 484 |
"""
|
| 485 |
-
##
|
| 486 |
|
| 487 |
**Args:**
|
| 488 |
* `image` (numpy.ndarray): Input image
|
|
@@ -496,7 +496,7 @@ def watercolor(image):
|
|
| 496 |
@registry.register("Posterization")
|
| 497 |
def posterize(image):
|
| 498 |
"""
|
| 499 |
-
##
|
| 500 |
|
| 501 |
**Args:**
|
| 502 |
* `image` (numpy.ndarray): Input image
|
|
@@ -513,7 +513,7 @@ def posterize(image):
|
|
| 513 |
@registry.register("Cross Process")
|
| 514 |
def cross_process(image):
|
| 515 |
"""
|
| 516 |
-
##
|
| 517 |
|
| 518 |
**Args:**
|
| 519 |
* `image` (numpy.ndarray): Input image
|
|
@@ -525,4 +525,23 @@ def cross_process(image):
|
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
@registry.register("Original")
|
| 7 |
def original(image):
|
| 8 |
"""
|
| 9 |
+
## Original Image - no filter applied.
|
| 10 |
|
| 11 |
**Args:**
|
| 12 |
* `image` (numpy.ndarray): Input image
|
|
|
|
| 33 |
})
|
| 34 |
def dot_effect(image, dot_size: int = 10, dot_spacing: int = 2, invert: bool = False):
|
| 35 |
"""
|
| 36 |
+
## Convert image to artistic dot pattern effect.
|
| 37 |
|
| 38 |
**Args:**
|
| 39 |
* `image` (numpy.ndarray): Input image (BGR or grayscale)
|
| 40 |
+
* `dot_size` (int): Size of each dot
|
| 41 |
+
* `dot_spacing` (int): Spacing between dots
|
| 42 |
+
* `invert` (bool): Invert dot colors
|
| 43 |
|
| 44 |
**Returns:**
|
| 45 |
* `numpy.ndarray`: Dotted image
|
|
|
|
| 88 |
})
|
| 89 |
def pixelize(image, pixel_size: int = 10):
|
| 90 |
"""
|
| 91 |
+
## Create pixelated effect (8-bit retro style).
|
| 92 |
|
| 93 |
**Args:**
|
| 94 |
* `image` (numpy.ndarray): Input image
|
| 95 |
+
* `pixel_size` (int): Size of each pixel block
|
| 96 |
|
| 97 |
**Returns:**
|
| 98 |
* `numpy.ndarray`: Pixelized image
|
|
|
|
| 108 |
@registry.register("Sketch Effect")
|
| 109 |
def sketch_effect(image):
|
| 110 |
"""
|
| 111 |
+
## Convert image to pencil sketch.
|
| 112 |
|
| 113 |
**Args:**
|
| 114 |
* `image` (numpy.ndarray): Input image
|
|
|
|
| 138 |
})
|
| 139 |
def warm_filter(image, intensity: int = 30):
|
| 140 |
"""
|
| 141 |
+
## Add warm tones to image (sunset, autumn style).
|
| 142 |
|
| 143 |
**Args:**
|
| 144 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 145 |
+
* `intensity` (int): Effect intensity (0-100)
|
| 146 |
|
| 147 |
**Returns:**
|
| 148 |
* `numpy.ndarray`: Warm-toned image
|
|
|
|
| 166 |
})
|
| 167 |
def cool_filter(image, intensity: int = 30):
|
| 168 |
"""
|
| 169 |
+
## Add cool tones to image (ice, ocean style).
|
| 170 |
|
| 171 |
**Args:**
|
| 172 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 173 |
+
* `intensity` (int): Effect intensity (0-100)
|
| 174 |
|
| 175 |
**Returns:**
|
| 176 |
* `numpy.ndarray`: Cool-toned image
|
|
|
|
| 194 |
})
|
| 195 |
def adjust_saturation(image, factor: int = 50):
|
| 196 |
"""
|
| 197 |
+
## Adjust color saturation of image.
|
| 198 |
|
| 199 |
**Args:**
|
| 200 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 201 |
+
* `factor` (int): Saturation factor (0-100, 50 is normal)
|
| 202 |
|
| 203 |
**Returns:**
|
| 204 |
* `numpy.ndarray`: Saturation-adjusted image
|
|
|
|
| 220 |
})
|
| 221 |
def vintage_filter(image, intensity: int = 50):
|
| 222 |
"""
|
| 223 |
+
## Create vintage/retro photo effect (70s style).
|
| 224 |
|
| 225 |
**Args:**
|
| 226 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 227 |
+
* `intensity` (int): Vintage effect intensity (0-100)
|
| 228 |
|
| 229 |
**Returns:**
|
| 230 |
* `numpy.ndarray`: Vintage-styled image
|
|
|
|
| 252 |
})
|
| 253 |
def vignette_effect(image, intensity: int = 50):
|
| 254 |
"""
|
| 255 |
+
## Add darkening effect to image corners (vignette).
|
| 256 |
|
| 257 |
**Args:**
|
| 258 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 259 |
+
* `intensity` (int): Vignette intensity (0-100)
|
| 260 |
|
| 261 |
**Returns:**
|
| 262 |
* `numpy.ndarray`: Vignetted image
|
|
|
|
| 284 |
})
|
| 285 |
def hdr_effect(image, strength: int = 50):
|
| 286 |
"""
|
| 287 |
+
## Enhance image details with HDR effect.
|
| 288 |
|
| 289 |
**Args:**
|
| 290 |
* `image` (numpy.ndarray): Input image (BGR)
|
| 291 |
+
* `strength` (int): HDR strength (0-100)
|
| 292 |
|
| 293 |
**Returns:**
|
| 294 |
* `numpy.ndarray`: HDR-enhanced image
|
|
|
|
| 318 |
})
|
| 319 |
def gaussian_blur(image, kernel_size: int = 5):
|
| 320 |
"""
|
| 321 |
+
## Blur image with Gaussian filter.
|
| 322 |
|
| 323 |
**Args:**
|
| 324 |
* `image` (numpy.ndarray): Input image
|
| 325 |
+
* `kernel_size` (int): Kernel size (must be odd number)
|
| 326 |
|
| 327 |
**Returns:**
|
| 328 |
* `numpy.ndarray`: Blurred image
|
|
|
|
| 343 |
})
|
| 344 |
def sharpen(image, amount: int = 50):
|
| 345 |
"""
|
| 346 |
+
## Sharpen image details.
|
| 347 |
|
| 348 |
**Args:**
|
| 349 |
* `image` (numpy.ndarray): Input image
|
| 350 |
+
* `amount` (int): Sharpening intensity (0-100)
|
| 351 |
|
| 352 |
**Returns:**
|
| 353 |
* `numpy.ndarray`: Sharpened image
|
|
|
|
| 373 |
})
|
| 374 |
def emboss(image, strength: int = 50, direction: int = 0):
|
| 375 |
"""
|
| 376 |
+
## Create 3D embossed effect.
|
| 377 |
|
| 378 |
**Args:**
|
| 379 |
* `image` (numpy.ndarray): Input image
|
| 380 |
+
* `strength` (int): Emboss strength (0-100)
|
| 381 |
+
* `direction` (int): Light direction (0-7)
|
| 382 |
|
| 383 |
**Returns:**
|
| 384 |
* `numpy.ndarray`: Embossed image
|
|
|
|
| 416 |
})
|
| 417 |
def oil_painting(image, size: int = 5, dynRatio: int = 1):
|
| 418 |
"""
|
| 419 |
+
## Create oil painting effect.
|
| 420 |
|
| 421 |
**Args:**
|
| 422 |
* `image` (numpy.ndarray): Input image
|
| 423 |
+
* `size` (int): Processing area size
|
| 424 |
+
* `dynRatio` (int): Dynamic ratio affecting color intensity
|
| 425 |
|
| 426 |
**Returns:**
|
| 427 |
* `numpy.ndarray`: Oil painting styled image
|
|
|
|
| 432 |
@registry.register("Black and White")
|
| 433 |
def black_and_white(image):
|
| 434 |
"""
|
| 435 |
+
## Convert to classic black and white.
|
| 436 |
|
| 437 |
**Args:**
|
| 438 |
* `image` (numpy.ndarray): Input image
|
|
|
|
| 446 |
@registry.register("Sepia")
|
| 447 |
def sepia(image):
|
| 448 |
"""
|
| 449 |
+
## Create sepia tone classic brown effect.
|
| 450 |
|
| 451 |
**Args:**
|
| 452 |
* `image` (numpy.ndarray): Input image
|
|
|
|
| 468 |
@registry.register("Negative")
|
| 469 |
def negative(image):
|
| 470 |
"""
|
| 471 |
+
## Invert colors for negative film effect.
|
| 472 |
|
| 473 |
**Args:**
|
| 474 |
* `image` (numpy.ndarray): Input image
|
|
|
|
| 482 |
@registry.register("Watercolor")
|
| 483 |
def watercolor(image):
|
| 484 |
"""
|
| 485 |
+
## Create watercolor painting effect.
|
| 486 |
|
| 487 |
**Args:**
|
| 488 |
* `image` (numpy.ndarray): Input image
|
|
|
|
| 496 |
@registry.register("Posterization")
|
| 497 |
def posterize(image):
|
| 498 |
"""
|
| 499 |
+
## Reduce colors for artistic poster effect.
|
| 500 |
|
| 501 |
**Args:**
|
| 502 |
* `image` (numpy.ndarray): Input image
|
|
|
|
| 513 |
@registry.register("Cross Process")
|
| 514 |
def cross_process(image):
|
| 515 |
"""
|
| 516 |
+
## Film cross-processing effect.
|
| 517 |
|
| 518 |
**Args:**
|
| 519 |
* `image` (numpy.ndarray): Input image
|
|
|
|
| 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)
|
| 529 |
+
|
| 530 |
+
Perfect! I've updated the app with the following changes:
|
| 531 |
+
|
| 532 |
+
1. **New Layout**:
|
| 533 |
+
- Input and output images are now side-by-side at the top for easy comparison
|
| 534 |
+
- Filter selection and customization controls moved to the bottom
|
| 535 |
+
- Action buttons (Apply Filter/Reset) placed between images and controls
|
| 536 |
+
|
| 537 |
+
2. **Complete English Translation**:
|
| 538 |
+
- All Vietnamese text converted to English
|
| 539 |
+
- UI labels, descriptions, and messages in English
|
| 540 |
+
- Filter descriptions in English
|
| 541 |
+
|
| 542 |
+
3. **Improved User Experience**:
|
| 543 |
+
- Images are now closer together for better before/after comparison
|
| 544 |
+
- More intuitive workflow: upload → select filter → customize → apply
|
| 545 |
+
- Cleaner visual hierarchy
|
| 546 |
+
|
| 547 |
+
The app maintains all functionality while providing a better layout and full English localization! 🎨✨
|
requirements.txt
CHANGED
|
@@ -1,4 +1,8 @@
|
|
| 1 |
-
gradio
|
| 2 |
opencv-python
|
| 3 |
-
opencv-contrib-python
|
| 4 |
numpy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
opencv-python
|
|
|
|
| 2 |
numpy
|
| 3 |
+
gradio
|
| 4 |
+
requests
|
| 5 |
+
Pillow
|
| 6 |
+
filter
|
| 7 |
+
registry
|
| 8 |
+
components
|