Spaces:
Running
on
Zero
Running
on
Zero
Add Sketch to system
Browse files- app.py +96 -22
- style_20250128.css +15 -0
- utils/file_utils.py +22 -21
- utils/image_utils.py +3 -3
app.py
CHANGED
|
@@ -102,8 +102,6 @@ from utils.version_info import (
|
|
| 102 |
)
|
| 103 |
#from utils.depth_estimation import (get_depth_map_from_state)
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
input_image_palette = []
|
| 108 |
current_prerendered_image = gr.State("./images/images/Beeuty-1.png")
|
| 109 |
user_dir = constants.TMPDIR
|
|
@@ -619,7 +617,43 @@ def add_border(image, mask_width, mask_height, blank_color):
|
|
| 619 |
print(f"Adding border to image with width: {mask_width}, height: {mask_height}, color: {margin_color}")
|
| 620 |
return shrink_and_paste_on_blank(bordered_image_output, mask_width, mask_height, margin_color)
|
| 621 |
|
| 622 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
####################################### DEPTH ESTIMATION #######################################
|
| 624 |
|
| 625 |
|
|
@@ -634,7 +668,6 @@ def preprocess_image(image: Image.Image) -> Image.Image:
|
|
| 634 |
processed_image = TRELLIS_PIPELINE.preprocess_image(image)
|
| 635 |
return processed_image
|
| 636 |
|
| 637 |
-
|
| 638 |
def pack_state(gs: Gaussian, mesh: MeshExtractResult, name: str) -> dict:
|
| 639 |
return {
|
| 640 |
'gaussian': {
|
|
@@ -651,8 +684,7 @@ def pack_state(gs: Gaussian, mesh: MeshExtractResult, name: str) -> dict:
|
|
| 651 |
},
|
| 652 |
'name': name
|
| 653 |
}
|
| 654 |
-
|
| 655 |
-
|
| 656 |
def unpack_state(state: dict) -> Tuple[Gaussian, edict, str]:
|
| 657 |
gs = Gaussian(
|
| 658 |
aabb=state['gaussian']['aabb'],
|
|
@@ -954,22 +986,35 @@ with gr.Blocks(css_paths="style_20250128.css", title=title, theme='Surn/beeuty',
|
|
| 954 |
elem_classes="centered solid imgcontainer",
|
| 955 |
key="imgInput",
|
| 956 |
image_mode=None,
|
| 957 |
-
format="PNG"
|
|
|
|
| 958 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 959 |
|
| 960 |
-
# New code to convert input image to RGBA PNG
|
| 961 |
-
def on_input_image_change(image_path):
|
| 962 |
-
if image_path is None:
|
| 963 |
-
gr.Warning("Please upload an Input Image to get started.")
|
| 964 |
-
return None
|
| 965 |
-
img, img_path = convert_to_rgba_png(image_path)
|
| 966 |
-
return img_path
|
| 967 |
-
|
| 968 |
-
input_image.change(
|
| 969 |
-
fn=on_input_image_change,
|
| 970 |
-
inputs=[input_image],
|
| 971 |
-
outputs=[input_image], scroll_to_output=True,
|
| 972 |
-
)
|
| 973 |
with gr.Column():
|
| 974 |
with gr.Accordion("Hex Coloring and Exclusion", open = False):
|
| 975 |
with gr.Row():
|
|
@@ -1032,7 +1077,7 @@ with gr.Blocks(css_paths="style_20250128.css", title=title, theme='Surn/beeuty',
|
|
| 1032 |
outputs=[input_image],
|
| 1033 |
scroll_to_output=True
|
| 1034 |
)
|
| 1035 |
-
|
| 1036 |
with gr.Row():
|
| 1037 |
with gr.Accordion("Generate AI Image (click here for options)", open = False):
|
| 1038 |
with gr.Row():
|
|
@@ -1267,8 +1312,29 @@ with gr.Blocks(css_paths="style_20250128.css", title=title, theme='Surn/beeuty',
|
|
| 1267 |
fn=generate_input_image_click,
|
| 1268 |
inputs=[input_image,map_options, prompt_textbox, negative_prompt_textbox, model_textbox, randomize_seed, seed_slider, gr.State(False), gr.State(0.5), image_size_ratio],
|
| 1269 |
outputs=[input_image, seed_slider], scroll_to_output=True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1270 |
)
|
| 1271 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1272 |
model_textbox.change(
|
| 1273 |
fn=update_prompt_notes,
|
| 1274 |
inputs=model_textbox,
|
|
@@ -1295,6 +1361,10 @@ with gr.Blocks(css_paths="style_20250128.css", title=title, theme='Surn/beeuty',
|
|
| 1295 |
fn=generate_input_image_click,
|
| 1296 |
inputs=[input_image, map_options, prompt_textbox, negative_prompt_textbox, model_textbox,randomize_seed, seed_slider, gr.State(True), image_guidance_stength, image_size_ratio],
|
| 1297 |
outputs=[input_image, seed_slider], scroll_to_output=True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1298 |
)
|
| 1299 |
|
| 1300 |
# Update the state variable with the prerendered image filepath when an image is selected
|
|
@@ -1309,6 +1379,10 @@ with gr.Blocks(css_paths="style_20250128.css", title=title, theme='Surn/beeuty',
|
|
| 1309 |
lambda: current_prerendered_image.value,
|
| 1310 |
inputs=None,
|
| 1311 |
outputs=[input_image], scroll_to_output=True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1312 |
)
|
| 1313 |
output_overlay_composite.change(
|
| 1314 |
fn=combine_images_with_lerp,
|
|
|
|
| 102 |
)
|
| 103 |
#from utils.depth_estimation import (get_depth_map_from_state)
|
| 104 |
|
|
|
|
|
|
|
| 105 |
input_image_palette = []
|
| 106 |
current_prerendered_image = gr.State("./images/images/Beeuty-1.png")
|
| 107 |
user_dir = constants.TMPDIR
|
|
|
|
| 617 |
print(f"Adding border to image with width: {mask_width}, height: {mask_height}, color: {margin_color}")
|
| 618 |
return shrink_and_paste_on_blank(bordered_image_output, mask_width, mask_height, margin_color)
|
| 619 |
|
| 620 |
+
def on_input_image_change(image_path):
|
| 621 |
+
if image_path is None:
|
| 622 |
+
gr.Warning("Please upload an Input Image to get started.")
|
| 623 |
+
return None, gr.update()
|
| 624 |
+
img, img_path = convert_to_rgba_png(image_path)
|
| 625 |
+
with Image.open(img_path) as pil_img:
|
| 626 |
+
width, height = pil_img.size
|
| 627 |
+
return [img_path, gr.update(width=width, height=height)]
|
| 628 |
+
|
| 629 |
+
def update_sketch_dimensions(input_image, sketch_image):
|
| 630 |
+
# Load the images using open_image() if they are provided as file paths.
|
| 631 |
+
in_img = open_image(input_image) if isinstance(input_image, str) else input_image
|
| 632 |
+
sk_img_path, _ = get_image_from_dict(sketch_image)
|
| 633 |
+
sk_img = open_image(sk_img_path)
|
| 634 |
+
# Resize sketch image if dimensions don't match input image.
|
| 635 |
+
if in_img.size != sk_img.size:
|
| 636 |
+
sk_img = sk_img.resize(in_img.size, Image.LANCZOS)
|
| 637 |
+
return [sk_img, gr.update(width=in_img.width, height=in_img.height)]
|
| 638 |
+
|
| 639 |
+
def composite_with_control_sync(input_image, sketch_image, slider_value):
|
| 640 |
+
# Load the images using open_image() if they are provided as file paths.
|
| 641 |
+
in_img = open_image(input_image) if isinstance(input_image, str) else input_image
|
| 642 |
+
sk_img_path, _ = get_image_from_dict(sketch_image)
|
| 643 |
+
sk_img = open_image(sk_img_path)
|
| 644 |
+
|
| 645 |
+
# Resize sketch image if dimensions don't match input image.
|
| 646 |
+
if in_img.size != sk_img.size:
|
| 647 |
+
sk_img = sk_img.resize(in_img.size, Image.LANCZOS)
|
| 648 |
+
|
| 649 |
+
# Now composite using the original alpha_composite_with_control function.
|
| 650 |
+
result_img = alpha_composite_with_control(in_img, sk_img, slider_value)
|
| 651 |
+
return result_img
|
| 652 |
+
|
| 653 |
+
def replace_input_with_sketch_image(sketch_image):
|
| 654 |
+
print(f"Sketch Image: {sketch_image}\n")
|
| 655 |
+
sketch, is_dict = get_image_from_dict(sketch_image)
|
| 656 |
+
return sketch
|
| 657 |
####################################### DEPTH ESTIMATION #######################################
|
| 658 |
|
| 659 |
|
|
|
|
| 668 |
processed_image = TRELLIS_PIPELINE.preprocess_image(image)
|
| 669 |
return processed_image
|
| 670 |
|
|
|
|
| 671 |
def pack_state(gs: Gaussian, mesh: MeshExtractResult, name: str) -> dict:
|
| 672 |
return {
|
| 673 |
'gaussian': {
|
|
|
|
| 684 |
},
|
| 685 |
'name': name
|
| 686 |
}
|
| 687 |
+
|
|
|
|
| 688 |
def unpack_state(state: dict) -> Tuple[Gaussian, edict, str]:
|
| 689 |
gs = Gaussian(
|
| 690 |
aabb=state['gaussian']['aabb'],
|
|
|
|
| 986 |
elem_classes="centered solid imgcontainer",
|
| 987 |
key="imgInput",
|
| 988 |
image_mode=None,
|
| 989 |
+
format="PNG",
|
| 990 |
+
height=512
|
| 991 |
)
|
| 992 |
+
with gr.Accordion("Sketch Pad", open = False, elem_id="sketchpd"):
|
| 993 |
+
with gr.Row():
|
| 994 |
+
sketch_image = gr.Sketchpad(
|
| 995 |
+
label="Sketch Image",
|
| 996 |
+
type="filepath",
|
| 997 |
+
#invert_colors=True,
|
| 998 |
+
#sources=['upload','canvas'],
|
| 999 |
+
#tool=['editor','select','color-sketch'],
|
| 1000 |
+
placeholder="Draw a sketch or upload an image. Currently broken in gradio 5.17.1",
|
| 1001 |
+
interactive=True,
|
| 1002 |
+
elem_classes="centered solid imgcontainer",
|
| 1003 |
+
key="imgSketch",
|
| 1004 |
+
image_mode="RGBA",
|
| 1005 |
+
format="PNG",
|
| 1006 |
+
brush=gr.Brush()
|
| 1007 |
+
)
|
| 1008 |
+
with gr.Row():
|
| 1009 |
+
with gr.Column(scale=1):
|
| 1010 |
+
sketch_replace_input_image_button = gr.Button(
|
| 1011 |
+
"Replace Input Image with sketch",
|
| 1012 |
+
elem_id="sketch_replace_input_image_button",
|
| 1013 |
+
elem_classes="solid"
|
| 1014 |
+
)
|
| 1015 |
+
sketch_alpha_composite_slider = gr.Slider(0,100,50,0.5, label="Sketch Transparancy", elem_id="alpha_composite_slider")
|
| 1016 |
+
btn_sketch_alpha_composite = gr.Button("Overlay Sketch on Input Image", elem_id="btn_sketchninput", elem_classes="solid")
|
| 1017 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1018 |
with gr.Column():
|
| 1019 |
with gr.Accordion("Hex Coloring and Exclusion", open = False):
|
| 1020 |
with gr.Row():
|
|
|
|
| 1077 |
outputs=[input_image],
|
| 1078 |
scroll_to_output=True
|
| 1079 |
)
|
| 1080 |
+
|
| 1081 |
with gr.Row():
|
| 1082 |
with gr.Accordion("Generate AI Image (click here for options)", open = False):
|
| 1083 |
with gr.Row():
|
|
|
|
| 1312 |
fn=generate_input_image_click,
|
| 1313 |
inputs=[input_image,map_options, prompt_textbox, negative_prompt_textbox, model_textbox, randomize_seed, seed_slider, gr.State(False), gr.State(0.5), image_size_ratio],
|
| 1314 |
outputs=[input_image, seed_slider], scroll_to_output=True
|
| 1315 |
+
).then(
|
| 1316 |
+
fn=update_sketch_dimensions,
|
| 1317 |
+
inputs=[input_image, sketch_image],
|
| 1318 |
+
outputs=[sketch_image, sketch_image]
|
| 1319 |
)
|
| 1320 |
+
input_image.input(
|
| 1321 |
+
fn=on_input_image_change,
|
| 1322 |
+
inputs=[input_image],
|
| 1323 |
+
outputs=[input_image,sketch_image], scroll_to_output=True,
|
| 1324 |
+
)
|
| 1325 |
+
###################### sketchpad ############################
|
| 1326 |
+
btn_sketch_alpha_composite.click(
|
| 1327 |
+
fn=composite_with_control_sync,
|
| 1328 |
+
inputs=[input_image, sketch_image, sketch_alpha_composite_slider],
|
| 1329 |
+
outputs=[input_image],
|
| 1330 |
+
scroll_to_output=True
|
| 1331 |
+
)
|
| 1332 |
+
sketch_replace_input_image_button.click(
|
| 1333 |
+
lambda sketch_image: replace_input_with_sketch_image(sketch_image),
|
| 1334 |
+
inputs=[sketch_image],
|
| 1335 |
+
outputs=[input_image], scroll_to_output=True
|
| 1336 |
+
)
|
| 1337 |
+
##################### model #######################################
|
| 1338 |
model_textbox.change(
|
| 1339 |
fn=update_prompt_notes,
|
| 1340 |
inputs=model_textbox,
|
|
|
|
| 1361 |
fn=generate_input_image_click,
|
| 1362 |
inputs=[input_image, map_options, prompt_textbox, negative_prompt_textbox, model_textbox,randomize_seed, seed_slider, gr.State(True), image_guidance_stength, image_size_ratio],
|
| 1363 |
outputs=[input_image, seed_slider], scroll_to_output=True
|
| 1364 |
+
).then(
|
| 1365 |
+
fn=update_sketch_dimensions,
|
| 1366 |
+
inputs=[input_image, sketch_image],
|
| 1367 |
+
outputs=[sketch_image, sketch_image]
|
| 1368 |
)
|
| 1369 |
|
| 1370 |
# Update the state variable with the prerendered image filepath when an image is selected
|
|
|
|
| 1379 |
lambda: current_prerendered_image.value,
|
| 1380 |
inputs=None,
|
| 1381 |
outputs=[input_image], scroll_to_output=True
|
| 1382 |
+
).then(
|
| 1383 |
+
fn=update_sketch_dimensions,
|
| 1384 |
+
inputs=[input_image, sketch_image],
|
| 1385 |
+
outputs=[sketch_image, sketch_image]
|
| 1386 |
)
|
| 1387 |
output_overlay_composite.change(
|
| 1388 |
fn=combine_images_with_lerp,
|
style_20250128.css
CHANGED
|
@@ -125,4 +125,19 @@ a {
|
|
| 125 |
.gradio-container, .gradio-container::before {
|
| 126 |
max-width: 1920px !important;
|
| 127 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
}
|
|
|
|
| 125 |
.gradio-container, .gradio-container::before {
|
| 126 |
max-width: 1920px !important;
|
| 127 |
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.sidebar .toggle-button::before {
|
| 131 |
+
content: 'Sketch Pad';
|
| 132 |
+
font-weight: bold;
|
| 133 |
+
transform: rotate(180deg);
|
| 134 |
+
margin-right: -120px;
|
| 135 |
+
width: 120px;
|
| 136 |
+
background-color: rgba(242, 218, 163, 0.62);
|
| 137 |
+
}
|
| 138 |
+
.dark .sidebar .toggle-button::before {
|
| 139 |
+
background-color: rgba(41, 18, 5, 0.38) !important;
|
| 140 |
+
}
|
| 141 |
+
.sidebar.open .toggle-button::before {
|
| 142 |
+
content: '';
|
| 143 |
}
|
utils/file_utils.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
# file_utils
|
| 2 |
import os
|
| 3 |
import utils.constants as constants
|
| 4 |
-
import shutil
|
| 5 |
from pathlib import Path
|
| 6 |
|
| 7 |
def cleanup_temp_files():
|
|
@@ -11,32 +11,33 @@ def cleanup_temp_files():
|
|
| 11 |
except Exception as e:
|
| 12 |
print(f"Failed to delete temp file {file_path}: {e}")
|
| 13 |
|
| 14 |
-
def rename_file_to_lowercase_extension(
|
| 15 |
"""
|
| 16 |
-
Renames
|
| 17 |
|
| 18 |
Parameters:
|
| 19 |
-
|
| 20 |
|
| 21 |
Returns:
|
| 22 |
-
str: The new file path
|
| 23 |
|
| 24 |
Raises:
|
| 25 |
-
|
| 26 |
"""
|
| 27 |
-
path
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
| 41 |
else:
|
| 42 |
-
return
|
|
|
|
| 1 |
# file_utils
|
| 2 |
import os
|
| 3 |
import utils.constants as constants
|
| 4 |
+
#import shutil
|
| 5 |
from pathlib import Path
|
| 6 |
|
| 7 |
def cleanup_temp_files():
|
|
|
|
| 11 |
except Exception as e:
|
| 12 |
print(f"Failed to delete temp file {file_path}: {e}")
|
| 13 |
|
| 14 |
+
def rename_file_to_lowercase_extension(file_path: str) -> str:
|
| 15 |
"""
|
| 16 |
+
Renames a file's extension to lowercase in place.
|
| 17 |
|
| 18 |
Parameters:
|
| 19 |
+
file_path (str): The original file path.
|
| 20 |
|
| 21 |
Returns:
|
| 22 |
+
str: The new file path with the lowercase extension.
|
| 23 |
|
| 24 |
Raises:
|
| 25 |
+
OSError: If there is an error renaming the file (e.g., file not found, permissions issue).
|
| 26 |
"""
|
| 27 |
+
# Split the path into directory and filename
|
| 28 |
+
directory, filename = os.path.split(file_path)
|
| 29 |
+
|
| 30 |
+
# Split the filename into name and extension
|
| 31 |
+
name, ext = os.path.splitext(filename)
|
| 32 |
+
|
| 33 |
+
# Convert the extension to lowercase
|
| 34 |
+
new_ext = ext.lower()
|
| 35 |
+
|
| 36 |
+
# If the extension changes, rename the file
|
| 37 |
+
if ext != new_ext:
|
| 38 |
+
new_filename = name + new_ext
|
| 39 |
+
new_file_path = os.path.join(directory, new_filename)
|
| 40 |
+
os.rename(file_path, new_file_path)
|
| 41 |
+
return new_file_path
|
| 42 |
else:
|
| 43 |
+
return file_path
|
utils/image_utils.py
CHANGED
|
@@ -19,10 +19,10 @@ from utils.file_utils import rename_file_to_lowercase_extension
|
|
| 19 |
|
| 20 |
def get_image_from_dict(image_path):
|
| 21 |
if isinstance(image_path, dict) :
|
| 22 |
-
if '
|
| 23 |
-
image_path = image_path.get('image')
|
| 24 |
-
elif 'composite' in image_path:
|
| 25 |
image_path = image_path.get('composite')
|
|
|
|
|
|
|
| 26 |
else:
|
| 27 |
print("\n Unknown image dictionary.\n")
|
| 28 |
raise UserWarning("Unknown image dictionary.")
|
|
|
|
| 19 |
|
| 20 |
def get_image_from_dict(image_path):
|
| 21 |
if isinstance(image_path, dict) :
|
| 22 |
+
if 'composite' in image_path:
|
|
|
|
|
|
|
| 23 |
image_path = image_path.get('composite')
|
| 24 |
+
elif 'image' in image_path:
|
| 25 |
+
image_path = image_path.get('image')
|
| 26 |
else:
|
| 27 |
print("\n Unknown image dictionary.\n")
|
| 28 |
raise UserWarning("Unknown image dictionary.")
|