Spaces:
Running
on
Zero
Running
on
Zero
Bug fixes
Browse files- app.py +16 -11
- utils/file_utils.py +17 -1
- utils/hex_grid.py +3 -3
- utils/image_utils.py +211 -41
app.py
CHANGED
|
@@ -43,7 +43,7 @@ from utils.misc import (
|
|
| 43 |
convert_ratio_to_dimensions,
|
| 44 |
update_dimensions_on_ratio,
|
| 45 |
get_seed,
|
| 46 |
-
get_output_name
|
| 47 |
) #install_cuda_toolkit,install_torch, _get_output, setup_runtime_env)
|
| 48 |
|
| 49 |
from utils.image_utils import (
|
|
@@ -62,7 +62,8 @@ from utils.image_utils import (
|
|
| 62 |
resize_image_with_aspect_ratio,
|
| 63 |
build_prerendered_images_by_quality,
|
| 64 |
get_image_from_dict,
|
| 65 |
-
calculate_optimal_fill_dimensions
|
|
|
|
| 66 |
)
|
| 67 |
|
| 68 |
from utils.hex_grid import (
|
|
@@ -717,8 +718,7 @@ def on_input_image_change(image_path):
|
|
| 717 |
gr.Warning("Please upload an Input Image to get started.")
|
| 718 |
return None, gr.update()
|
| 719 |
img, img_path = convert_to_rgba_png(image_path)
|
| 720 |
-
|
| 721 |
-
width, height = pil_img.size
|
| 722 |
return [img_path, gr.update(width=width, height=height)]
|
| 723 |
|
| 724 |
def update_sketch_dimensions(input_image, sketch_image):
|
|
@@ -773,10 +773,12 @@ def unload_3d_models(is_open: bool = False) -> bool:
|
|
| 773 |
if not is_open:
|
| 774 |
gr.Info("Unloading 3D models...")
|
| 775 |
global image_processor, depth_model, TRELLIS_PIPELINE
|
| 776 |
-
TRELLIS_PIPELINE
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
|
|
|
|
|
|
| 780 |
#torch.cuda.empty_cache()
|
| 781 |
#torch.cuda.ipc_collect()
|
| 782 |
gc.collect()
|
|
@@ -890,7 +892,7 @@ def depth_process_image(image_path, resized_width=800, z_scale=208):
|
|
| 890 |
torch.cuda.ipc_collect()
|
| 891 |
return img
|
| 892 |
|
| 893 |
-
def generate_3d_asset_part1(depth_image_source, randomize_seed, seed, input_image, output_image, overlay_image, bordered_image_output, progress=gr.Progress(track_tqdm=True)):
|
| 894 |
# Choose the image based on source
|
| 895 |
if depth_image_source == "Input Image":
|
| 896 |
image_path = input_image
|
|
@@ -912,6 +914,9 @@ def generate_3d_asset_part1(depth_image_source, randomize_seed, seed, input_imag
|
|
| 912 |
# Process the image for depth estimation
|
| 913 |
depth_img = depth_process_image(image_path, resized_width=1536, z_scale=336)
|
| 914 |
depth_img = resize_image_with_aspect_ratio(depth_img, 1536, 1536)
|
|
|
|
|
|
|
|
|
|
| 915 |
|
| 916 |
return depth_img, image_path, output_name, final_seed
|
| 917 |
|
|
@@ -1200,7 +1205,7 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
|
|
| 1200 |
lut_file.change(get_filename, inputs=[lut_file], outputs=[lut_filename])
|
| 1201 |
lut_filename.change(show_lut, inputs=[lut_filename, lut_example_image], outputs=[lut_example_image])
|
| 1202 |
apply_lut_button.click(
|
| 1203 |
-
lambda lut_filename, input_image: gr.Warning("Please upload an Input Image to get started.") if input_image is None else apply_lut_to_image_path(lut_filename, input_image)[
|
| 1204 |
inputs=[lut_filename, input_image],
|
| 1205 |
outputs=[input_image],
|
| 1206 |
scroll_to_output=True
|
|
@@ -1321,7 +1326,7 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
|
|
| 1321 |
y_spacing = gr.Number(label="Adjust Vertical spacing", value=3, minimum=-200, maximum=200, precision=1)
|
| 1322 |
with gr.Row():
|
| 1323 |
rotation = gr.Slider(-90, 180, 0.0, 0.1, label="Hexagon Rotation (degree)")
|
| 1324 |
-
add_hex_text = gr.Dropdown(label="Add Text to Hexagons", choices=[None, "Row
|
| 1325 |
with gr.Row():
|
| 1326 |
custom_text_list = gr.TextArea(label="Custom Text List", value=constants.cards_alternating, visible=False,)
|
| 1327 |
custom_text_color_list = gr.TextArea(label="Custom Text Color List", value=constants.card_colors_alternating, visible=False)
|
|
|
|
| 43 |
convert_ratio_to_dimensions,
|
| 44 |
update_dimensions_on_ratio,
|
| 45 |
get_seed,
|
| 46 |
+
get_output_name
|
| 47 |
) #install_cuda_toolkit,install_torch, _get_output, setup_runtime_env)
|
| 48 |
|
| 49 |
from utils.image_utils import (
|
|
|
|
| 62 |
resize_image_with_aspect_ratio,
|
| 63 |
build_prerendered_images_by_quality,
|
| 64 |
get_image_from_dict,
|
| 65 |
+
calculate_optimal_fill_dimensions,
|
| 66 |
+
save_image_to_temp_png
|
| 67 |
)
|
| 68 |
|
| 69 |
from utils.hex_grid import (
|
|
|
|
| 718 |
gr.Warning("Please upload an Input Image to get started.")
|
| 719 |
return None, gr.update()
|
| 720 |
img, img_path = convert_to_rgba_png(image_path)
|
| 721 |
+
width, height = img.size
|
|
|
|
| 722 |
return [img_path, gr.update(width=width, height=height)]
|
| 723 |
|
| 724 |
def update_sketch_dimensions(input_image, sketch_image):
|
|
|
|
| 773 |
if not is_open:
|
| 774 |
gr.Info("Unloading 3D models...")
|
| 775 |
global image_processor, depth_model, TRELLIS_PIPELINE
|
| 776 |
+
if TRELLIS_PIPELINE:
|
| 777 |
+
TRELLIS_PIPELINE.to("cpu")
|
| 778 |
+
del TRELLIS_PIPELINE
|
| 779 |
+
if depth_model:
|
| 780 |
+
del image_processor
|
| 781 |
+
del depth_model
|
| 782 |
#torch.cuda.empty_cache()
|
| 783 |
#torch.cuda.ipc_collect()
|
| 784 |
gc.collect()
|
|
|
|
| 892 |
torch.cuda.ipc_collect()
|
| 893 |
return img
|
| 894 |
|
| 895 |
+
def generate_3d_asset_part1(depth_image_source, randomize_seed, seed, input_image, output_image, overlay_image, bordered_image_output, req: gr.Request, progress=gr.Progress(track_tqdm=True)):
|
| 896 |
# Choose the image based on source
|
| 897 |
if depth_image_source == "Input Image":
|
| 898 |
image_path = input_image
|
|
|
|
| 914 |
# Process the image for depth estimation
|
| 915 |
depth_img = depth_process_image(image_path, resized_width=1536, z_scale=336)
|
| 916 |
depth_img = resize_image_with_aspect_ratio(depth_img, 1536, 1536)
|
| 917 |
+
|
| 918 |
+
user_dir = os.path.join(constants.TMPDIR, str(req.session_hash))
|
| 919 |
+
depth_img = save_image_to_temp_png(depth_img, user_dir, f"{output_name}_depth")
|
| 920 |
|
| 921 |
return depth_img, image_path, output_name, final_seed
|
| 922 |
|
|
|
|
| 1205 |
lut_file.change(get_filename, inputs=[lut_file], outputs=[lut_filename])
|
| 1206 |
lut_filename.change(show_lut, inputs=[lut_filename, lut_example_image], outputs=[lut_example_image])
|
| 1207 |
apply_lut_button.click(
|
| 1208 |
+
lambda lut_filename, input_image: gr.Warning("Please upload an Input Image to get started.") if input_image is None else apply_lut_to_image_path(lut_filename, input_image)[1],
|
| 1209 |
inputs=[lut_filename, input_image],
|
| 1210 |
outputs=[input_image],
|
| 1211 |
scroll_to_output=True
|
|
|
|
| 1326 |
y_spacing = gr.Number(label="Adjust Vertical spacing", value=3, minimum=-200, maximum=200, precision=1)
|
| 1327 |
with gr.Row():
|
| 1328 |
rotation = gr.Slider(-90, 180, 0.0, 0.1, label="Hexagon Rotation (degree)")
|
| 1329 |
+
add_hex_text = gr.Dropdown(label="Add Text to Hexagons", choices=[None, "Column-Row Coordinates", "Column(Letter)-Row Coordinates", "Column-Row(Letter) Coordinates", "Sequential Numbers", "Playing Cards Sequential", "Playing Cards Alternate Red and Black", "Custom List"], value=None)
|
| 1330 |
with gr.Row():
|
| 1331 |
custom_text_list = gr.TextArea(label="Custom Text List", value=constants.cards_alternating, visible=False,)
|
| 1332 |
custom_text_color_list = gr.TextArea(label="Custom Text Color List", value=constants.card_colors_alternating, visible=False)
|
utils/file_utils.py
CHANGED
|
@@ -38,7 +38,23 @@ def rename_file_to_lowercase_extension(file_path: str) -> str:
|
|
| 38 |
if ext != new_ext:
|
| 39 |
new_filename = name + new_ext
|
| 40 |
new_file_path = os.path.join(directory, new_filename)
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
return new_file_path
|
| 43 |
else:
|
| 44 |
return file_path
|
|
|
|
| 38 |
if ext != new_ext:
|
| 39 |
new_filename = name + new_ext
|
| 40 |
new_file_path = os.path.join(directory, new_filename)
|
| 41 |
+
try:
|
| 42 |
+
os.rename(file_path, new_file_path)
|
| 43 |
+
print(f"Rename {file_path} to {new_file_path}\n")
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print(f"os.rename failed: {e}. Falling back to binary copy operation.")
|
| 46 |
+
try:
|
| 47 |
+
# Read the file in binary mode and write it to new_file_path
|
| 48 |
+
with open(file_path, 'rb') as f:
|
| 49 |
+
data = f.read()
|
| 50 |
+
with open(new_file_path, 'wb') as f:
|
| 51 |
+
f.write(data)
|
| 52 |
+
print(f"Rename {file_path} to {new_file_path}\n")
|
| 53 |
+
# Optionally, remove the original file after copying
|
| 54 |
+
#os.remove(file_path)
|
| 55 |
+
except Exception as inner_e:
|
| 56 |
+
print(f"Failed to copy file from {file_path} to {new_file_path}: {inner_e}")
|
| 57 |
+
raise inner_e
|
| 58 |
return new_file_path
|
| 59 |
else:
|
| 60 |
return file_path
|
utils/hex_grid.py
CHANGED
|
@@ -274,13 +274,13 @@ def generate_hexagon_grid_with_text(hex_size, border_size, input_image=None, ima
|
|
| 274 |
if font_size:
|
| 275 |
font = ImageFont.truetype(font_path, font_size)
|
| 276 |
# Determine the text to draw
|
| 277 |
-
if add_hex_text_option == "Row
|
| 278 |
text = f"{col},{row}"
|
| 279 |
elif add_hex_text_option == "Sequential Numbers":
|
| 280 |
text = f"{hex_index}"
|
| 281 |
-
elif add_hex_text_option == "Column
|
| 282 |
text = f"{number_to_letter(col)}{row}"
|
| 283 |
-
elif add_hex_text_option == "Column
|
| 284 |
text = f"{col}{number_to_letter(row)}"
|
| 285 |
elif text_list:
|
| 286 |
text = text_list[hex_index % len(text_list)]
|
|
|
|
| 274 |
if font_size:
|
| 275 |
font = ImageFont.truetype(font_path, font_size)
|
| 276 |
# Determine the text to draw
|
| 277 |
+
if add_hex_text_option == "Column-Row Coordinates":
|
| 278 |
text = f"{col},{row}"
|
| 279 |
elif add_hex_text_option == "Sequential Numbers":
|
| 280 |
text = f"{hex_index}"
|
| 281 |
+
elif add_hex_text_option == "Column(Letter)-Row Coordinates":
|
| 282 |
text = f"{number_to_letter(col)}{row}"
|
| 283 |
+
elif add_hex_text_option == "Column-Row(Letter) Coordinates":
|
| 284 |
text = f"{col}{number_to_letter(row)}"
|
| 285 |
elif text_list:
|
| 286 |
text = text_list[hex_index % len(text_list)]
|
utils/image_utils.py
CHANGED
|
@@ -17,6 +17,44 @@ from utils.color_utils import (
|
|
| 17 |
)
|
| 18 |
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 'composite' in image_path:
|
|
@@ -44,14 +82,16 @@ def open_image(image_path):
|
|
| 44 |
Raises:
|
| 45 |
Exception: If there is an error opening the image.
|
| 46 |
"""
|
|
|
|
| 47 |
if isinstance(image_path, Image.Image):
|
| 48 |
return image_path
|
| 49 |
-
|
| 50 |
-
image_path =
|
|
|
|
|
|
|
| 51 |
|
| 52 |
import requests
|
| 53 |
try:
|
| 54 |
-
image_path, is_dict = get_image_from_dict(image_path)
|
| 55 |
# Strip leading and trailing double quotation marks, if present
|
| 56 |
image_path = image_path.strip('"')
|
| 57 |
if image_path.startswith('http'):
|
|
@@ -488,6 +528,24 @@ def resize_and_crop_image(image: Image, new_width: int = 512, new_height: int =
|
|
| 488 |
|
| 489 |
##################################################### LUTs ############################################################
|
| 490 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
def is_3dlut_row(row: List[str]) -> bool:
|
| 492 |
"""
|
| 493 |
Check if one line in the file has exactly 3 numeric values.
|
|
@@ -504,8 +562,68 @@ def is_3dlut_row(row: List[str]) -> bool:
|
|
| 504 |
except ValueError:
|
| 505 |
return False
|
| 506 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
|
| 508 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
"""
|
| 510 |
Read LUT from a raw file.
|
| 511 |
|
|
@@ -562,33 +680,33 @@ def show_lut(lut_filename: str, lut_example_image: Image = default_lut_example_i
|
|
| 562 |
lut_example_image = open_image(default_lut_example_img)
|
| 563 |
return lut_example_image
|
| 564 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
|
| 566 |
|
| 567 |
-
def convert_rgb_to_rgba_safe(image: Image) -> Image:
|
| 568 |
-
"""
|
| 569 |
-
Converts an RGB image to RGBA by adding an alpha channel.
|
| 570 |
-
Ensures that the original image remains unaltered.
|
| 571 |
|
| 572 |
-
Parameters:
|
| 573 |
-
image (PIL.Image.Image): The RGB image to convert.
|
| 574 |
|
| 575 |
-
Returns:
|
| 576 |
-
PIL.Image.Image: The converted RGBA image.
|
| 577 |
-
"""
|
| 578 |
-
if image.mode != 'RGB':
|
| 579 |
-
if image.mode == 'RGBA':
|
| 580 |
-
return image
|
| 581 |
-
elif image.mode == 'P':
|
| 582 |
-
# Convert palette image to RGBA
|
| 583 |
-
image = image.convert('RGB')
|
| 584 |
-
else:
|
| 585 |
-
raise ValueError("Unsupported image mode for conversion to RGBA.")
|
| 586 |
-
# Create a copy of the image to avoid modifying the original
|
| 587 |
-
rgba_image = image.copy()
|
| 588 |
-
# Optionally, set a default alpha value (e.g., fully opaque)
|
| 589 |
-
alpha = Image.new('L', rgba_image.size, 255) # 255 for full opacity
|
| 590 |
-
rgba_image.putalpha(alpha)
|
| 591 |
-
return rgba_image
|
| 592 |
|
| 593 |
def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image, str]:
|
| 594 |
"""
|
|
@@ -602,9 +720,24 @@ def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image,
|
|
| 602 |
Returns:
|
| 603 |
tuple: A tuple containing the PIL Image object with the LUT applied and the new image path as a string.
|
| 604 |
"""
|
|
|
|
|
|
|
|
|
|
| 605 |
if image_path is None:
|
| 606 |
raise UserWarning("No image provided.")
|
| 607 |
return None, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
path = Path(image_path)
|
| 609 |
img = open_image(image_path)
|
| 610 |
if not ((path.suffix.lower() == '.png' and img.mode == 'RGBA')):
|
|
@@ -619,16 +752,50 @@ def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image,
|
|
| 619 |
if image_path != new_image_path:
|
| 620 |
delete_image(image_path)
|
| 621 |
else:
|
| 622 |
-
|
| 623 |
-
|
|
|
|
|
|
|
|
|
|
| 624 |
try:
|
| 625 |
-
|
| 626 |
except Exception as e:
|
| 627 |
print(f"BAD LUT: Error applying LUT {str(e)}.")
|
| 628 |
-
|
| 629 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
|
| 631 |
############################################# RGBA ###########################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
|
| 633 |
# Example usage
|
| 634 |
# convert_jpg_to_rgba('input.jpg', 'output.png')
|
|
@@ -654,12 +821,15 @@ def convert_jpg_to_rgba(input_path) -> tuple[Image, str]:
|
|
| 654 |
|
| 655 |
# Check if the input file exists
|
| 656 |
if not input_path.exists():
|
| 657 |
-
|
|
|
|
|
|
|
|
|
|
| 658 |
|
| 659 |
# Check file extension first to skip unnecessary processing
|
| 660 |
if input_path.suffix.lower() not in ('.jpg', '.jpeg'):
|
| 661 |
print(f"Skipping conversion: {input_path} is not a JPG or JPEG file.")
|
| 662 |
-
return
|
| 663 |
|
| 664 |
print(f"Converting to PNG: {input_path} is a JPG or JPEG file.")
|
| 665 |
|
|
@@ -779,22 +949,22 @@ def resize_all_images_in_folder(target_width: int, output_folder: str = "resized
|
|
| 779 |
with Image.open(file_path) as img:
|
| 780 |
# Convert to RGB if needed (handles RGBA, CMYK, etc.)
|
| 781 |
if img.mode != 'RGB':
|
| 782 |
-
img = img.convert('RGB')
|
| 783 |
# Calculate target height maintaining aspect ratio
|
| 784 |
original_width, original_height = img.size
|
| 785 |
aspect_ratio = original_height / original_width
|
| 786 |
-
target_height = int(target_width * aspect_ratio)
|
| 787 |
# Resize using the reference function
|
| 788 |
-
resized_img = resize_image_with_aspect_ratio(img, target_width, target_height)
|
| 789 |
# Create output filename
|
| 790 |
-
output_filename = output_path / f"{file_prefix}{file_path.name}"
|
| 791 |
# Save the resized image
|
| 792 |
-
resized_img.save(output_filename, quality=95)
|
| 793 |
successful += 1
|
| 794 |
-
print(f"Successfully resized: {file_path.name}")
|
| 795 |
except Exception as e:
|
| 796 |
failed += 1
|
| 797 |
-
print(f"Failed to resize {file_path.name}: {str(e)}")
|
| 798 |
|
| 799 |
print(f"\nResizing complete. Successfully processed: {successful}, Failed: {failed}")
|
| 800 |
return successful, failed
|
|
|
|
| 17 |
)
|
| 18 |
from utils.file_utils import rename_file_to_lowercase_extension
|
| 19 |
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def save_image_to_temp_png(image_source, user_dir: str = None, file_name: str = None):
|
| 24 |
+
"""
|
| 25 |
+
Opens an image from a file path, URL, or DataURL and saves it as a PNG in the user's temporary directory.
|
| 26 |
+
|
| 27 |
+
Parameters:
|
| 28 |
+
image_source (str, dict or PIL.Image.Image): The source of the image to open.
|
| 29 |
+
|
| 30 |
+
Returns:
|
| 31 |
+
str: The file path of the saved PNG image in the temporary directory.
|
| 32 |
+
"""
|
| 33 |
+
import tempfile
|
| 34 |
+
import uuid
|
| 35 |
+
|
| 36 |
+
# Open the image using the existing utility function
|
| 37 |
+
img = open_image(image_source)
|
| 38 |
+
|
| 39 |
+
# Ensure the image is in a format that supports PNG (convert if necessary)
|
| 40 |
+
if img.mode not in ("RGB", "RGBA"):
|
| 41 |
+
img = img.convert("RGBA")
|
| 42 |
+
|
| 43 |
+
# Generate a unique filename in the system temporary directory
|
| 44 |
+
if user_dir is None:
|
| 45 |
+
user_dir = tempfile.gettempdir()
|
| 46 |
+
|
| 47 |
+
if file_name is None:
|
| 48 |
+
file_name = f"{uuid.uuid4()}.png"
|
| 49 |
+
|
| 50 |
+
temp_filepath = os.path.join(user_dir, file_name.lower())
|
| 51 |
+
os.makedirs(user_dir, exist_ok=True)
|
| 52 |
+
|
| 53 |
+
# Save the image as PNG
|
| 54 |
+
img.save(temp_filepath, format="PNG")
|
| 55 |
+
|
| 56 |
+
return temp_filepath
|
| 57 |
+
|
| 58 |
def get_image_from_dict(image_path):
|
| 59 |
if isinstance(image_path, dict) :
|
| 60 |
if 'composite' in image_path:
|
|
|
|
| 82 |
Raises:
|
| 83 |
Exception: If there is an error opening the image.
|
| 84 |
"""
|
| 85 |
+
|
| 86 |
if isinstance(image_path, Image.Image):
|
| 87 |
return image_path
|
| 88 |
+
elif isinstance(image_path, dict):
|
| 89 |
+
image_path, is_dict = get_image_from_dict(image_path)
|
| 90 |
+
|
| 91 |
+
image_path = rename_file_to_lowercase_extension(image_path)
|
| 92 |
|
| 93 |
import requests
|
| 94 |
try:
|
|
|
|
| 95 |
# Strip leading and trailing double quotation marks, if present
|
| 96 |
image_path = image_path.strip('"')
|
| 97 |
if image_path.startswith('http'):
|
|
|
|
| 528 |
|
| 529 |
##################################################### LUTs ############################################################
|
| 530 |
|
| 531 |
+
class Color1DLUT(ImageFilter.Filter):
|
| 532 |
+
"""Custom filter to apply a 1D LUT to an RGB image."""
|
| 533 |
+
def __init__(self, table, size):
|
| 534 |
+
self.table = table
|
| 535 |
+
self.size = size
|
| 536 |
+
if size != 256:
|
| 537 |
+
raise ValueError("Only 1D LUTs with size 256 are supported")
|
| 538 |
+
# Create a 768-entry LUT (256 for R, G, B) scaled to 0-255
|
| 539 |
+
lut_r = [int(table[i][0] * 255) for i in range(256)]
|
| 540 |
+
lut_g = [int(table[i][1] * 255) for i in range(256)]
|
| 541 |
+
lut_b = [int(table[i][2] * 255) for i in range(256)]
|
| 542 |
+
self.lut = lut_r + lut_g + lut_b
|
| 543 |
+
|
| 544 |
+
def filter(self, image):
|
| 545 |
+
if image.mode != 'RGB':
|
| 546 |
+
image = image.convert('RGB')
|
| 547 |
+
return image.point(self.lut)
|
| 548 |
+
|
| 549 |
def is_3dlut_row(row: List[str]) -> bool:
|
| 550 |
"""
|
| 551 |
Check if one line in the file has exactly 3 numeric values.
|
|
|
|
| 562 |
except ValueError:
|
| 563 |
return False
|
| 564 |
|
| 565 |
+
def read_lut(path_lut: Union[str, os.PathLike], num_channels: int = 3) -> Union[ImageFilter.Color3DLUT, Color1DLUT]:
|
| 566 |
+
"""
|
| 567 |
+
Read a LUT from a .cube file and return a filter object.
|
| 568 |
+
|
| 569 |
+
Detects whether the file contains a 1D or 3D LUT based on keywords
|
| 570 |
+
"LUT_1D_SIZE" or "LUT_3D_SIZE". Initially assumes a 3D LUT if no size
|
| 571 |
+
keyword is specified.
|
| 572 |
|
| 573 |
+
Args:
|
| 574 |
+
path_lut: Path to the LUT file (string or os.PathLike).
|
| 575 |
+
num_channels: Number of color channels in the LUT (default is 3).
|
| 576 |
+
|
| 577 |
+
Returns:
|
| 578 |
+
ImageFilter.Color3DLUT for 3D LUTs or Color1DLUT for 1D LUTs.
|
| 579 |
+
|
| 580 |
+
Raises:
|
| 581 |
+
FileNotFoundError: If the file does not exist.
|
| 582 |
+
ValueError: If the LUT data is invalid or size mismatches.
|
| 583 |
+
"""
|
| 584 |
+
with open(path_lut) as f:
|
| 585 |
+
lines = f.read().splitlines()
|
| 586 |
+
|
| 587 |
+
lut_type = "3D" # Initially assume 3D LUT
|
| 588 |
+
size = None
|
| 589 |
+
table = []
|
| 590 |
+
|
| 591 |
+
# Parse the file
|
| 592 |
+
for line in lines:
|
| 593 |
+
line = line.strip()
|
| 594 |
+
if line.startswith("#") or not line:
|
| 595 |
+
continue # Skip comments and empty lines
|
| 596 |
+
parts = line.split()
|
| 597 |
+
if parts[0] == "LUT_3D_SIZE":
|
| 598 |
+
size = int(parts[1])
|
| 599 |
+
lut_type = "3D"
|
| 600 |
+
elif parts[0] == "LUT_1D_SIZE":
|
| 601 |
+
size = int(parts[1])
|
| 602 |
+
lut_type = "1D"
|
| 603 |
+
elif is_3dlut_row(parts):
|
| 604 |
+
table.append(tuple(float(val) for val in parts))
|
| 605 |
+
|
| 606 |
+
# Process based on LUT type
|
| 607 |
+
if lut_type == "3D":
|
| 608 |
+
if size is None:
|
| 609 |
+
# Calculate size assuming 3D LUT
|
| 610 |
+
len_table = len(table)
|
| 611 |
+
if len_table == 0:
|
| 612 |
+
raise ValueError("No valid LUT data found")
|
| 613 |
+
size = round(len_table ** (1 / 3))
|
| 614 |
+
if size ** 3 != len_table:
|
| 615 |
+
raise ValueError(f"Number of table entries {len_table} is not a perfect cube")
|
| 616 |
+
elif len(table) != size ** 3:
|
| 617 |
+
raise ValueError(f"Expected {size**3} entries for 3D LUT, got {len(table)}")
|
| 618 |
+
return ImageFilter.Color3DLUT(size, table, channels=num_channels)
|
| 619 |
+
else: # lut_type == "1D"
|
| 620 |
+
if size is None:
|
| 621 |
+
raise ValueError("LUT_1D_SIZE not specified for 1D LUT")
|
| 622 |
+
if len(table) != size:
|
| 623 |
+
raise ValueError(f"Expected {size} entries for 1D LUT, got {len(table)}")
|
| 624 |
+
return Color1DLUT(table, size)
|
| 625 |
+
|
| 626 |
+
def read_3Dlut(path_lut: Union[str, os.PathLike], num_channels: int = 3) -> ImageFilter.Color3DLUT:
|
| 627 |
"""
|
| 628 |
Read LUT from a raw file.
|
| 629 |
|
|
|
|
| 680 |
lut_example_image = open_image(default_lut_example_img)
|
| 681 |
return lut_example_image
|
| 682 |
|
| 683 |
+
def apply_1d_lut(image, lut_file):
|
| 684 |
+
# Read the 1D LUT
|
| 685 |
+
with open(lut_file) as f:
|
| 686 |
+
lines = f.read().splitlines()
|
| 687 |
+
table = []
|
| 688 |
+
for line in lines:
|
| 689 |
+
if not line.startswith(("#", "LUT", "TITLE", "DOMAIN")) and line.strip():
|
| 690 |
+
values = [float(v) for v in line.split()]
|
| 691 |
+
table.append(tuple(values))
|
| 692 |
+
|
| 693 |
+
# Convert image to grayscale
|
| 694 |
+
if image.mode != 'L':
|
| 695 |
+
image = image.convert('L')
|
| 696 |
+
img_array = np.array(image) / 255.0 # Normalize to [0, 1]
|
| 697 |
+
|
| 698 |
+
# Map grayscale values to colors
|
| 699 |
+
lut_size = len(table)
|
| 700 |
+
indices = (img_array * (lut_size - 1)).astype(int)
|
| 701 |
+
colors = np.array(table)[indices]
|
| 702 |
+
|
| 703 |
+
# Create RGB image
|
| 704 |
+
rgb_image = Image.fromarray((colors * 255).astype(np.uint8), mode='RGB')
|
| 705 |
+
return rgb_image
|
| 706 |
|
| 707 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 708 |
|
|
|
|
|
|
|
| 709 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 710 |
|
| 711 |
def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image, str]:
|
| 712 |
"""
|
|
|
|
| 720 |
Returns:
|
| 721 |
tuple: A tuple containing the PIL Image object with the LUT applied and the new image path as a string.
|
| 722 |
"""
|
| 723 |
+
import gradio as gr
|
| 724 |
+
|
| 725 |
+
img_lut = None
|
| 726 |
if image_path is None:
|
| 727 |
raise UserWarning("No image provided.")
|
| 728 |
return None, None
|
| 729 |
+
|
| 730 |
+
# Split the path into directory and filename
|
| 731 |
+
directory, file_name = os.path.split(image_path)
|
| 732 |
+
lut_directory, lut_file_name = os.path.split(lut_filename)
|
| 733 |
+
|
| 734 |
+
# Split the filename into name and extension
|
| 735 |
+
name, ext = os.path.splitext(file_name)
|
| 736 |
+
lut_name, lut_ext = os.path.splitext(lut_file_name)
|
| 737 |
+
|
| 738 |
+
# Convert the extension to lowercase
|
| 739 |
+
new_ext = ext.lower()
|
| 740 |
+
|
| 741 |
path = Path(image_path)
|
| 742 |
img = open_image(image_path)
|
| 743 |
if not ((path.suffix.lower() == '.png' and img.mode == 'RGBA')):
|
|
|
|
| 752 |
if image_path != new_image_path:
|
| 753 |
delete_image(image_path)
|
| 754 |
else:
|
| 755 |
+
# ensure the file extension is lower_case, otherwise leave as is
|
| 756 |
+
new_filename = name + new_ext
|
| 757 |
+
new_image_path = os.path.join(directory, new_filename)
|
| 758 |
+
# Apply the LUT to the image
|
| 759 |
+
if (lut_filename is not None and img is not None):
|
| 760 |
try:
|
| 761 |
+
img_lut = apply_lut(img, lut_filename)
|
| 762 |
except Exception as e:
|
| 763 |
print(f"BAD LUT: Error applying LUT {str(e)}.")
|
| 764 |
+
if img_lut is not None:
|
| 765 |
+
new_filename = name + "_"+ lut_name + new_ext
|
| 766 |
+
new_image_path = os.path.join(directory, new_filename)
|
| 767 |
+
delete_image(image_path)
|
| 768 |
+
img = img_lut
|
| 769 |
+
img.save(new_image_path, format='PNG')
|
| 770 |
+
print(f"Image with LUT saved as {new_image_path}")
|
| 771 |
+
return img, gr.update(value=str(new_image_path))
|
| 772 |
|
| 773 |
############################################# RGBA ###########################################################
|
| 774 |
+
def convert_rgb_to_rgba_safe(image: Image) -> Image:
|
| 775 |
+
"""
|
| 776 |
+
Converts an RGB image to RGBA by adding an alpha channel.
|
| 777 |
+
Ensures that the original image remains unaltered.
|
| 778 |
+
|
| 779 |
+
Parameters:
|
| 780 |
+
image (PIL.Image.Image): The RGB image to convert.
|
| 781 |
+
|
| 782 |
+
Returns:
|
| 783 |
+
PIL.Image.Image: The converted RGBA image.
|
| 784 |
+
"""
|
| 785 |
+
if image.mode != 'RGB':
|
| 786 |
+
if image.mode == 'RGBA':
|
| 787 |
+
return image
|
| 788 |
+
elif image.mode == 'P':
|
| 789 |
+
# Convert palette image to RGBA
|
| 790 |
+
image = image.convert('RGB')
|
| 791 |
+
else:
|
| 792 |
+
raise ValueError("Unsupported image mode for conversion to RGBA.")
|
| 793 |
+
# Create a copy of the image to avoid modifying the original
|
| 794 |
+
rgba_image = image.copy()
|
| 795 |
+
# Optionally, set a default alpha value (e.g., fully opaque)
|
| 796 |
+
alpha = Image.new('L', rgba_image.size, 255) # 255 for full opacity
|
| 797 |
+
rgba_image.putalpha(alpha)
|
| 798 |
+
return rgba_image
|
| 799 |
|
| 800 |
# Example usage
|
| 801 |
# convert_jpg_to_rgba('input.jpg', 'output.png')
|
|
|
|
| 821 |
|
| 822 |
# Check if the input file exists
|
| 823 |
if not input_path.exists():
|
| 824 |
+
#if file was renamed to lower case, update the input path
|
| 825 |
+
input_path = output_path
|
| 826 |
+
if not input_path.exists():
|
| 827 |
+
raise FileNotFoundError(f"The file {input_path} does not exist.")
|
| 828 |
|
| 829 |
# Check file extension first to skip unnecessary processing
|
| 830 |
if input_path.suffix.lower() not in ('.jpg', '.jpeg'):
|
| 831 |
print(f"Skipping conversion: {input_path} is not a JPG or JPEG file.")
|
| 832 |
+
return None, None
|
| 833 |
|
| 834 |
print(f"Converting to PNG: {input_path} is a JPG or JPEG file.")
|
| 835 |
|
|
|
|
| 949 |
with Image.open(file_path) as img:
|
| 950 |
# Convert to RGB if needed (handles RGBA, CMYK, etc.)
|
| 951 |
if img.mode != 'RGB':
|
| 952 |
+
img = img.convert('RGB')
|
| 953 |
# Calculate target height maintaining aspect ratio
|
| 954 |
original_width, original_height = img.size
|
| 955 |
aspect_ratio = original_height / original_width
|
| 956 |
+
target_height = int(target_width * aspect_ratio)
|
| 957 |
# Resize using the reference function
|
| 958 |
+
resized_img = resize_image_with_aspect_ratio(img, target_width, target_height)
|
| 959 |
# Create output filename
|
| 960 |
+
output_filename = output_path / f"{file_prefix}{file_path.name.lower()}"
|
| 961 |
# Save the resized image
|
| 962 |
+
resized_img.save(output_filename, quality=95)
|
| 963 |
successful += 1
|
| 964 |
+
print(f"Successfully resized: {file_path.name.lower()}")
|
| 965 |
except Exception as e:
|
| 966 |
failed += 1
|
| 967 |
+
print(f"Failed to resize {file_path.name.lower()}: {str(e)}")
|
| 968 |
|
| 969 |
print(f"\nResizing complete. Successfully processed: {successful}, Failed: {failed}")
|
| 970 |
return successful, failed
|