Spaces:
Running
on
Zero
Running
on
Zero
Fix for pngs and add warnings
Browse files- .gitignore +1 -0
- app.py +54 -26
- requirements.txt +4 -2
- utils/constants.py +23 -9
- utils/image_utils.py +160 -50
.gitignore
CHANGED
|
@@ -164,3 +164,4 @@ cython_debug/
|
|
| 164 |
/src/__pycache__
|
| 165 |
/utils/__pycache__
|
| 166 |
/__pycache__
|
|
|
|
|
|
| 164 |
/src/__pycache__
|
| 165 |
/utils/__pycache__
|
| 166 |
/__pycache__
|
| 167 |
+
/temp_models
|
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
from PIL import Image, ImageDraw, ImageChops, ImageColor
|
| 3 |
from haishoku.haishoku import Haishoku
|
| 4 |
import os
|
|
@@ -298,6 +298,9 @@ with gr.Blocks(css_paths="style_20250128.css", title="HexaGrid Creator", theme='
|
|
| 298 |
</details>
|
| 299 |
""", elem_classes="intro")
|
| 300 |
with gr.Row():
|
|
|
|
|
|
|
|
|
|
| 301 |
with gr.Column(scale=2):
|
| 302 |
input_image = gr.Image(
|
| 303 |
label="Input Image",
|
|
@@ -305,21 +308,36 @@ with gr.Blocks(css_paths="style_20250128.css", title="HexaGrid Creator", theme='
|
|
| 305 |
interactive=True,
|
| 306 |
elem_classes="centered solid imgcontainer",
|
| 307 |
key="imgInput",
|
| 308 |
-
image_mode=
|
| 309 |
-
format="PNG"
|
|
|
|
| 310 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
with gr.Column():
|
| 312 |
with gr.Accordion("Hex Coloring and Exclusion", open = False):
|
| 313 |
with gr.Row():
|
| 314 |
with gr.Column():
|
| 315 |
color_picker = gr.ColorPicker(label="Pick a color to exclude",value="#505050")
|
| 316 |
with gr.Column():
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
with gr.Accordion("Image Filters", open = False):
|
| 324 |
with gr.Row():
|
| 325 |
with gr.Column():
|
|
@@ -364,7 +382,12 @@ with gr.Blocks(css_paths="style_20250128.css", title="HexaGrid Creator", theme='
|
|
| 364 |
|
| 365 |
lut_file.change(get_filename, inputs=[lut_file], outputs=[lut_filename])
|
| 366 |
lut_filename.change(show_lut, inputs=[lut_filename, lut_example_image], outputs=[lut_example_image])
|
| 367 |
-
apply_lut_button.click(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
|
| 369 |
with gr.Row():
|
| 370 |
with gr.Accordion("Generative AI", open = False):
|
|
@@ -460,30 +483,29 @@ with gr.Blocks(css_paths="style_20250128.css", title="HexaGrid Creator", theme='
|
|
| 460 |
with gr.Row():
|
| 461 |
rotation = gr.Slider(-90, 180, 0.0, 0.1, label="Hexagon Rotation (degree)")
|
| 462 |
add_hex_text = gr.Dropdown(label="Add Text to Hexagons", choices=[None, "Row-Column Coordinates", "Sequential Numbers", "Playing Cards Sequential", "Playing Cards Alternate Red and Black", "Custom List"], value=None)
|
| 463 |
-
|
| 464 |
custom_text_list = gr.TextArea(label="Custom Text List", value=constants.cards_alternating, visible=False,)
|
| 465 |
custom_text_color_list = gr.TextArea(label="Custom Text Color List", value=constants.card_colors_alternating, visible=False)
|
| 466 |
-
|
| 467 |
hex_text_info = gr.Markdown("""
|
| 468 |
### Text Color uses the Border Color and Border Opacity, unless you use a custom list.
|
| 469 |
### The Custom Text List and Custom Text Color List are comma separated lists.
|
| 470 |
### The custom color list is a comma separated list of hex colors.
|
| 471 |
#### Example: "A,2,3,4,5,6,7,8,9,10,J,Q,K", "red,#0000FF,#00FF00,red,#FFFF00,#00FFFF,#FF8000,#FF00FF,#FF0080,#FF8000,#FF0080,lightblue"
|
| 472 |
""", elem_id="hex_text_info", visible=False)
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
with gr.Row():
|
| 483 |
hex_size = gr.Number(label="Hexagon Size", value=32, minimum=1, maximum=768)
|
| 484 |
border_size = gr.Slider(-5,25,value=0,step=1,label="Border Size")
|
| 485 |
-
with gr.Row():
|
| 486 |
-
rotation = gr.Slider(-90, 180, 0.0, 0.1, label="deg. Rotation")
|
| 487 |
background_color = gr.ColorPicker(label="Background Color", value="#000000", interactive=True)
|
| 488 |
background_opacity = gr.Slider(0,100,0,1,label="Background Opacity %")
|
| 489 |
border_color = gr.ColorPicker(label="Border Color", value="#7b7b7b", interactive=True)
|
|
@@ -519,7 +541,7 @@ with gr.Blocks(css_paths="style_20250128.css", title="HexaGrid Creator", theme='
|
|
| 519 |
depth_image_source = gr.Radio(label="Depth Image Source", choices=["Input Image", "Output Image", "Overlay Image","Image with Margins"], value="Input Image")
|
| 520 |
with gr.Row():
|
| 521 |
generate_depth_button = gr.Button("Generate Depth Map and 3D Model From Selected Image", elem_classes="solid", variant="secondary")
|
| 522 |
-
|
| 523 |
depth_map_output = gr.Image(label="Depth Map", image_mode="L", elem_classes="centered solid imgcontainer", format="PNG", type="filepath", key="ImgDepth")
|
| 524 |
model_output = gr.Model3D(label="3D Model", clear_color=[1.0, 1.0, 1.0, 0.25], key="Img3D", elem_classes="centered solid imgcontainer")
|
| 525 |
with gr.Row():
|
|
@@ -540,7 +562,13 @@ with gr.Blocks(css_paths="style_20250128.css", title="HexaGrid Creator", theme='
|
|
| 540 |
|
| 541 |
delete_button.click(fn=delete_color, inputs=[selected_row, color_display], outputs=[color_display])
|
| 542 |
exclude_color_button.click(fn=add_color, inputs=[color_picker, gr.State(excluded_color_list)], outputs=[color_display, gr.State(excluded_color_list)])
|
| 543 |
-
hex_button.click(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 544 |
generate_input_image.click(
|
| 545 |
fn=generate_input_image_click,
|
| 546 |
inputs=[map_options, prompt_textbox, negative_prompt_textbox, model_textbox, gr.State(False), gr.State(0.5), image_size_ratio],
|
|
@@ -567,7 +595,7 @@ with gr.Blocks(css_paths="style_20250128.css", title="HexaGrid Creator", theme='
|
|
| 567 |
outputs=prompt_notes_label
|
| 568 |
)
|
| 569 |
composite_button.click(
|
| 570 |
-
fn=change_color,
|
| 571 |
inputs=[input_image, composite_color, composite_opacity],
|
| 572 |
outputs=[input_image]
|
| 573 |
)
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
from PIL import Image, ImageDraw, ImageChops, ImageColor
|
| 3 |
from haishoku.haishoku import Haishoku
|
| 4 |
import os
|
|
|
|
| 298 |
</details>
|
| 299 |
""", elem_classes="intro")
|
| 300 |
with gr.Row():
|
| 301 |
+
from utils.image_utils import convert_to_rgba_png
|
| 302 |
+
|
| 303 |
+
# Existing code
|
| 304 |
with gr.Column(scale=2):
|
| 305 |
input_image = gr.Image(
|
| 306 |
label="Input Image",
|
|
|
|
| 308 |
interactive=True,
|
| 309 |
elem_classes="centered solid imgcontainer",
|
| 310 |
key="imgInput",
|
| 311 |
+
image_mode=None,
|
| 312 |
+
format="PNG",
|
| 313 |
+
show_download_button=True,
|
| 314 |
)
|
| 315 |
+
|
| 316 |
+
# New code to convert input image to RGBA PNG
|
| 317 |
+
def on_input_image_change(image_path):
|
| 318 |
+
if image_path is None:
|
| 319 |
+
gr.Warning("Please upload an Input Image to get started.")
|
| 320 |
+
return None
|
| 321 |
+
img, img_path = convert_to_rgba_png(image_path)
|
| 322 |
+
return img_path
|
| 323 |
+
|
| 324 |
+
input_image.change(
|
| 325 |
+
fn=on_input_image_change,
|
| 326 |
+
inputs=[input_image],
|
| 327 |
+
outputs=[input_image], scroll_to_output=True,
|
| 328 |
+
)
|
| 329 |
with gr.Column():
|
| 330 |
with gr.Accordion("Hex Coloring and Exclusion", open = False):
|
| 331 |
with gr.Row():
|
| 332 |
with gr.Column():
|
| 333 |
color_picker = gr.ColorPicker(label="Pick a color to exclude",value="#505050")
|
| 334 |
with gr.Column():
|
| 335 |
+
filter_color = gr.Checkbox(label="Filter Excluded Colors from Sampling", value=False,)
|
| 336 |
+
exclude_color_button = gr.Button("Exclude Color", elem_id="exlude_color_button", elem_classes="solid")
|
| 337 |
+
color_display = gr.DataFrame(label="List of Excluded RGBA Colors", headers=["R", "G", "B", "A"], elem_id="excluded_colors", type="array", value=build_dataframe(excluded_color_list), interactive=True, elem_classes="solid centered")
|
| 338 |
+
selected_row = gr.Number(0, label="Selected Row", visible=False)
|
| 339 |
+
delete_button = gr.Button("Delete Row", elem_id="delete_exclusion_button", elem_classes="solid")
|
| 340 |
+
fill_hex = gr.Checkbox(label="Fill Hex with color from Image", value=True)
|
| 341 |
with gr.Accordion("Image Filters", open = False):
|
| 342 |
with gr.Row():
|
| 343 |
with gr.Column():
|
|
|
|
| 382 |
|
| 383 |
lut_file.change(get_filename, inputs=[lut_file], outputs=[lut_filename])
|
| 384 |
lut_filename.change(show_lut, inputs=[lut_filename, lut_example_image], outputs=[lut_example_image])
|
| 385 |
+
apply_lut_button.click(
|
| 386 |
+
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)[0],
|
| 387 |
+
inputs=[lut_filename, input_image],
|
| 388 |
+
outputs=[input_image],
|
| 389 |
+
scroll_to_output=True
|
| 390 |
+
)
|
| 391 |
|
| 392 |
with gr.Row():
|
| 393 |
with gr.Accordion("Generative AI", open = False):
|
|
|
|
| 483 |
with gr.Row():
|
| 484 |
rotation = gr.Slider(-90, 180, 0.0, 0.1, label="Hexagon Rotation (degree)")
|
| 485 |
add_hex_text = gr.Dropdown(label="Add Text to Hexagons", choices=[None, "Row-Column Coordinates", "Sequential Numbers", "Playing Cards Sequential", "Playing Cards Alternate Red and Black", "Custom List"], value=None)
|
| 486 |
+
with gr.Row():
|
| 487 |
custom_text_list = gr.TextArea(label="Custom Text List", value=constants.cards_alternating, visible=False,)
|
| 488 |
custom_text_color_list = gr.TextArea(label="Custom Text Color List", value=constants.card_colors_alternating, visible=False)
|
| 489 |
+
with gr.Row():
|
| 490 |
hex_text_info = gr.Markdown("""
|
| 491 |
### Text Color uses the Border Color and Border Opacity, unless you use a custom list.
|
| 492 |
### The Custom Text List and Custom Text Color List are comma separated lists.
|
| 493 |
### The custom color list is a comma separated list of hex colors.
|
| 494 |
#### Example: "A,2,3,4,5,6,7,8,9,10,J,Q,K", "red,#0000FF,#00FF00,red,#FFFF00,#00FFFF,#FF8000,#FF00FF,#FF0080,#FF8000,#FF0080,lightblue"
|
| 495 |
""", elem_id="hex_text_info", visible=False)
|
| 496 |
+
add_hex_text.change(
|
| 497 |
+
fn=lambda x: (
|
| 498 |
+
gr.update(visible=(x == "Custom List")),
|
| 499 |
+
gr.update(visible=(x == "Custom List")),
|
| 500 |
+
gr.update(visible=(x != None))
|
| 501 |
+
),
|
| 502 |
+
inputs=add_hex_text,
|
| 503 |
+
outputs=[custom_text_list, custom_text_color_list, hex_text_info]
|
| 504 |
+
)
|
| 505 |
with gr.Row():
|
| 506 |
hex_size = gr.Number(label="Hexagon Size", value=32, minimum=1, maximum=768)
|
| 507 |
border_size = gr.Slider(-5,25,value=0,step=1,label="Border Size")
|
| 508 |
+
with gr.Row():
|
|
|
|
| 509 |
background_color = gr.ColorPicker(label="Background Color", value="#000000", interactive=True)
|
| 510 |
background_opacity = gr.Slider(0,100,0,1,label="Background Opacity %")
|
| 511 |
border_color = gr.ColorPicker(label="Border Color", value="#7b7b7b", interactive=True)
|
|
|
|
| 541 |
depth_image_source = gr.Radio(label="Depth Image Source", choices=["Input Image", "Output Image", "Overlay Image","Image with Margins"], value="Input Image")
|
| 542 |
with gr.Row():
|
| 543 |
generate_depth_button = gr.Button("Generate Depth Map and 3D Model From Selected Image", elem_classes="solid", variant="secondary")
|
| 544 |
+
with gr.Row():
|
| 545 |
depth_map_output = gr.Image(label="Depth Map", image_mode="L", elem_classes="centered solid imgcontainer", format="PNG", type="filepath", key="ImgDepth")
|
| 546 |
model_output = gr.Model3D(label="3D Model", clear_color=[1.0, 1.0, 1.0, 0.25], key="Img3D", elem_classes="centered solid imgcontainer")
|
| 547 |
with gr.Row():
|
|
|
|
| 562 |
|
| 563 |
delete_button.click(fn=delete_color, inputs=[selected_row, color_display], outputs=[color_display])
|
| 564 |
exclude_color_button.click(fn=add_color, inputs=[color_picker, gr.State(excluded_color_list)], outputs=[color_display, gr.State(excluded_color_list)])
|
| 565 |
+
hex_button.click(
|
| 566 |
+
fn=lambda hex_size, border_size, input_image, start_x, start_y, end_x, end_y, rotation, background_color, background_opacity, border_color, border_opacity, fill_hex, color_display, filter_color, x_spacing, y_spacing, add_hex_text, custom_text_list, custom_text_color_list:
|
| 567 |
+
gr.Warning("Please upload an Input Image to get started.") if input_image is None else hex_create(hex_size, border_size, input_image, start_x, start_y, end_x, end_y, rotation, background_color, background_opacity, border_color, border_opacity, fill_hex, color_display, filter_color, x_spacing, y_spacing, add_hex_text, custom_text_list, custom_text_color_list),
|
| 568 |
+
inputs=[hex_size, border_size, input_image, start_x, start_y, end_x, end_y, rotation, background_color, background_opacity, border_color, border_opacity, fill_hex, color_display, filter_color, x_spacing, y_spacing, add_hex_text, custom_text_list, custom_text_color_list],
|
| 569 |
+
outputs=[output_image, overlay_image],
|
| 570 |
+
scroll_to_output=True
|
| 571 |
+
)
|
| 572 |
generate_input_image.click(
|
| 573 |
fn=generate_input_image_click,
|
| 574 |
inputs=[map_options, prompt_textbox, negative_prompt_textbox, model_textbox, gr.State(False), gr.State(0.5), image_size_ratio],
|
|
|
|
| 595 |
outputs=prompt_notes_label
|
| 596 |
)
|
| 597 |
composite_button.click(
|
| 598 |
+
fn=lambda input_image, composite_color, composite_opacity: gr.Warning("Please upload an Input Image to get started.") if input_image is None else change_color(input_image, composite_color, composite_opacity),
|
| 599 |
inputs=[input_image, composite_color, composite_opacity],
|
| 600 |
outputs=[input_image]
|
| 601 |
)
|
requirements.txt
CHANGED
|
@@ -13,7 +13,7 @@ huggingface_hub
|
|
| 13 |
# git+https://github.com/huggingface/transformers@v4.48.1#egg=transformers
|
| 14 |
transformers==4.48.1
|
| 15 |
gradio[oauth]
|
| 16 |
-
Pillow
|
| 17 |
numpy
|
| 18 |
requests
|
| 19 |
# git+https://github.com/huggingface/diffusers
|
|
@@ -34,4 +34,6 @@ pycairo
|
|
| 34 |
cairocffi
|
| 35 |
pangocffi
|
| 36 |
pangocairocffi
|
| 37 |
-
tensorflow
|
|
|
|
|
|
|
|
|
| 13 |
# git+https://github.com/huggingface/transformers@v4.48.1#egg=transformers
|
| 14 |
transformers==4.48.1
|
| 15 |
gradio[oauth]
|
| 16 |
+
Pillow>=11.1.0
|
| 17 |
numpy
|
| 18 |
requests
|
| 19 |
# git+https://github.com/huggingface/diffusers
|
|
|
|
| 34 |
cairocffi
|
| 35 |
pangocffi
|
| 36 |
pangocairocffi
|
| 37 |
+
tensorflow
|
| 38 |
+
cairosvg
|
| 39 |
+
python-dotenv
|
utils/constants.py
CHANGED
|
@@ -1,22 +1,36 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
#Set the environment variables
|
| 3 |
-
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
|
| 4 |
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "max_split_size_mb:256,expandable_segments:True"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
IS_SHARED_SPACE = "Surn/HexaGrid" in os.environ.get('SPACE_ID', '')
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
# Set the temporary folder location
|
| 8 |
#os.environ['TEMP'] = r'e:\\TMP'
|
| 9 |
#os.environ['TMPDIR'] = r'e:\\TMP'
|
| 10 |
#os.environ['XDG_CACHE_HOME'] = r'E:\\cache'
|
| 11 |
-
os.environ['USE_FLASH_ATTENTION'] = '1'
|
| 12 |
-
#os.environ['XFORMERS_FORCE_DISABLE_TRITON']= '1'
|
| 13 |
-
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
|
| 14 |
-
os.environ["PYTORCH_NVML_BASED_CUDA_CHECK"] = "1"
|
| 15 |
-
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2,3"
|
| 16 |
|
| 17 |
-
# constants.py contains all the constants used in the project
|
| 18 |
-
#os.environ["HF_TOKEN"] = ""
|
| 19 |
HF_API_TOKEN = os.getenv("HF_TOKEN")
|
|
|
|
|
|
|
|
|
|
| 20 |
default_lut_example_img = "./LUT/daisy.jpg"
|
| 21 |
|
| 22 |
PROMPTS = {
|
|
|
|
| 1 |
+
# utils/constants.py
|
| 2 |
+
# constants.py contains all the constants used in the project such as the default LUT example image, prompts, negative prompts, pre-rendered maps, models, LoRA weights, and more.
|
| 3 |
+
# execptions made for some environmental variables
|
| 4 |
+
import os
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
#Set the environment variables
|
|
|
|
| 9 |
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "max_split_size_mb:256,expandable_segments:True"
|
| 10 |
+
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
|
| 11 |
+
#os.environ["TF_CPP_MIN_LOG_LEVEL"] = '2'
|
| 12 |
+
os.environ['USE_FLASH_ATTENTION'] = '1'
|
| 13 |
+
#os.environ['XFORMERS_FORCE_DISABLE_TRITON']= '1'
|
| 14 |
+
#os.environ['XFORMERS_FORCE_DISABLE_TORCHSCRIPT']= '1'
|
| 15 |
+
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
|
| 16 |
+
os.environ["PYTORCH_NVML_BASED_CUDA_CHECK"] = "1"
|
| 17 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2,3"
|
| 18 |
+
|
| 19 |
IS_SHARED_SPACE = "Surn/HexaGrid" in os.environ.get('SPACE_ID', '')
|
| 20 |
|
| 21 |
+
# Load environment variables from .env file
|
| 22 |
+
dotenv_path = Path(__file__).parent.parent / '.env'
|
| 23 |
+
load_dotenv(dotenv_path)
|
| 24 |
+
|
| 25 |
# Set the temporary folder location
|
| 26 |
#os.environ['TEMP'] = r'e:\\TMP'
|
| 27 |
#os.environ['TMPDIR'] = r'e:\\TMP'
|
| 28 |
#os.environ['XDG_CACHE_HOME'] = r'E:\\cache'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
|
|
|
|
|
|
| 30 |
HF_API_TOKEN = os.getenv("HF_TOKEN")
|
| 31 |
+
if not HF_API_TOKEN:
|
| 32 |
+
raise ValueError("HF_TOKEN is not set. Please check your .env file.")
|
| 33 |
+
|
| 34 |
default_lut_example_img = "./LUT/daisy.jpg"
|
| 35 |
|
| 36 |
PROMPTS = {
|
utils/image_utils.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
# utils/image_utils.py
|
| 2 |
import os
|
| 3 |
from io import BytesIO
|
|
|
|
| 4 |
import base64
|
| 5 |
import numpy as np
|
| 6 |
#from decimal import ROUND_CEILING
|
|
@@ -8,6 +9,7 @@ from PIL import Image, ImageChops, ImageDraw, ImageEnhance, ImageFilter, ImageDr
|
|
| 8 |
from typing import List, Union
|
| 9 |
#import numpy as np
|
| 10 |
#import math
|
|
|
|
| 11 |
from utils.constants import default_lut_example_img
|
| 12 |
from utils.color_utils import (
|
| 13 |
detect_color_format,
|
|
@@ -18,13 +20,14 @@ from utils.misc import (pause)
|
|
| 18 |
def open_image(image_path):
|
| 19 |
"""
|
| 20 |
Opens an image from a file path or URL, or decodes a DataURL string into an image.
|
| 21 |
-
|
|
|
|
| 22 |
Parameters:
|
| 23 |
image_path (str): The file path, URL, or DataURL string of the image to open.
|
| 24 |
-
|
| 25 |
Returns:
|
| 26 |
Image: A PIL Image object of the opened image.
|
| 27 |
-
|
| 28 |
Raises:
|
| 29 |
Exception: If there is an error opening the image.
|
| 30 |
"""
|
|
@@ -33,17 +36,32 @@ def open_image(image_path):
|
|
| 33 |
# Strip leading and trailing double quotation marks, if present
|
| 34 |
image_path = image_path.strip('"')
|
| 35 |
if image_path.startswith('http'):
|
| 36 |
-
# If the image path is a URL, download the image using requests
|
| 37 |
response = requests.get(image_path)
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
elif image_path.startswith('data'):
|
| 40 |
-
# If the image path is a DataURL, decode the base64 string
|
| 41 |
encoded_data = image_path.split(',')[1]
|
| 42 |
decoded_data = base64.b64decode(encoded_data)
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
else:
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
except Exception as e:
|
| 48 |
raise Exception(f'Error opening image: {e}')
|
| 49 |
return img
|
|
@@ -75,15 +93,16 @@ def build_encoded_images(images_list):
|
|
| 75 |
def image_to_base64(image):
|
| 76 |
"""
|
| 77 |
Encodes an image to a base64 string.
|
| 78 |
-
|
|
|
|
| 79 |
Parameters:
|
| 80 |
image (str or PIL.Image.Image): The file path, URL, DataURL string, or PIL Image object of the image to encode.
|
| 81 |
-
|
| 82 |
Returns:
|
| 83 |
str: A base64-encoded string of the image.
|
| 84 |
"""
|
| 85 |
buffered = BytesIO()
|
| 86 |
-
if
|
| 87 |
image = open_image(image)
|
| 88 |
image.save(buffered, format="PNG")
|
| 89 |
return "data:image/png;base64," + base64.b64encode(buffered.getvalue()).decode()
|
|
@@ -449,11 +468,9 @@ def read_lut(path_lut: Union[str, os.PathLike], num_channels: int = 3) -> ImageF
|
|
| 449 |
"""
|
| 450 |
with open(path_lut) as f:
|
| 451 |
lut_raw = f.read().splitlines()
|
| 452 |
-
|
| 453 |
size = round(len(lut_raw) ** (1 / 3))
|
| 454 |
row2val = lambda row: tuple([float(val) for val in row.split(" ")])
|
| 455 |
lut_table = [row2val(row) for row in lut_raw if is_3dlut_row(row.split(" "))]
|
| 456 |
-
|
| 457 |
return ImageFilter.Color3DLUT(size, lut_table, num_channels)
|
| 458 |
|
| 459 |
def apply_lut(img: Image, lut_path: str = "", lut: ImageFilter.Color3DLUT = None) -> Image:
|
|
@@ -477,7 +494,6 @@ def apply_lut(img: Image, lut_path: str = "", lut: ImageFilter.Color3DLUT = None
|
|
| 477 |
if lut_path == "":
|
| 478 |
raise ValueError("Either lut_path or lut argument must be provided.")
|
| 479 |
lut = read_lut(lut_path)
|
| 480 |
-
|
| 481 |
return img.filter(lut)
|
| 482 |
|
| 483 |
def show_lut(lut_filename: str, lut_example_image: Image = default_lut_example_img) -> Image:
|
|
@@ -490,6 +506,8 @@ def show_lut(lut_filename: str, lut_example_image: Image = default_lut_example_i
|
|
| 490 |
lut_example_image = open_image(default_lut_example_img)
|
| 491 |
return lut_example_image
|
| 492 |
|
|
|
|
|
|
|
| 493 |
def convert_rgb_to_rgba_safe(image: Image) -> Image:
|
| 494 |
"""
|
| 495 |
Converts an RGB image to RGBA by adding an alpha channel.
|
|
@@ -509,70 +527,162 @@ def convert_rgb_to_rgba_safe(image: Image) -> Image:
|
|
| 509 |
image = image.convert('RGB')
|
| 510 |
else:
|
| 511 |
raise ValueError("Unsupported image mode for conversion to RGBA.")
|
| 512 |
-
|
| 513 |
# Create a copy of the image to avoid modifying the original
|
| 514 |
rgba_image = image.copy()
|
| 515 |
-
|
| 516 |
# Optionally, set a default alpha value (e.g., fully opaque)
|
| 517 |
alpha = Image.new('L', rgba_image.size, 255) # 255 for full opacity
|
| 518 |
-
rgba_image.putalpha(alpha)
|
| 519 |
-
|
| 520 |
return rgba_image
|
| 521 |
|
| 522 |
-
def apply_lut_to_image_path(lut_filename: str, image_path: str) -> Image:
|
| 523 |
"""
|
| 524 |
Apply a LUT to an image and return the result.
|
| 525 |
-
|
|
|
|
| 526 |
Args:
|
| 527 |
lut_filename: A string representing the path to the LUT file.
|
| 528 |
image_path: A string representing the path to the input image.
|
| 529 |
-
|
| 530 |
Returns:
|
| 531 |
-
A PIL Image object with the LUT applied.
|
| 532 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
img = open_image(image_path)
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
|
|
|
|
|
|
| 544 |
if lut_filename is not None:
|
| 545 |
try:
|
| 546 |
img = apply_lut(img, lut_filename)
|
| 547 |
except Exception as e:
|
| 548 |
print(f"BAD LUT: Error applying LUT {str(e)}.")
|
| 549 |
-
|
|
|
|
| 550 |
|
|
|
|
| 551 |
|
| 552 |
-
|
|
|
|
|
|
|
| 553 |
"""
|
| 554 |
-
|
| 555 |
|
| 556 |
Args:
|
| 557 |
-
|
| 558 |
|
| 559 |
Raises:
|
| 560 |
-
|
| 561 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
"""
|
| 563 |
try:
|
| 564 |
-
#
|
| 565 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
|
| 567 |
-
#
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
except ValueError as ve:
|
| 576 |
print(f"ValueError: {ve}")
|
| 577 |
except Exception as e:
|
| 578 |
-
print(f"Error converting image: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# utils/image_utils.py
|
| 2 |
import os
|
| 3 |
from io import BytesIO
|
| 4 |
+
import cairosvg
|
| 5 |
import base64
|
| 6 |
import numpy as np
|
| 7 |
#from decimal import ROUND_CEILING
|
|
|
|
| 9 |
from typing import List, Union
|
| 10 |
#import numpy as np
|
| 11 |
#import math
|
| 12 |
+
from pathlib import Path
|
| 13 |
from utils.constants import default_lut_example_img
|
| 14 |
from utils.color_utils import (
|
| 15 |
detect_color_format,
|
|
|
|
| 20 |
def open_image(image_path):
|
| 21 |
"""
|
| 22 |
Opens an image from a file path or URL, or decodes a DataURL string into an image.
|
| 23 |
+
Supports SVG and ICO by converting them to PNG.
|
| 24 |
+
|
| 25 |
Parameters:
|
| 26 |
image_path (str): The file path, URL, or DataURL string of the image to open.
|
| 27 |
+
|
| 28 |
Returns:
|
| 29 |
Image: A PIL Image object of the opened image.
|
| 30 |
+
|
| 31 |
Raises:
|
| 32 |
Exception: If there is an error opening the image.
|
| 33 |
"""
|
|
|
|
| 36 |
# Strip leading and trailing double quotation marks, if present
|
| 37 |
image_path = image_path.strip('"')
|
| 38 |
if image_path.startswith('http'):
|
|
|
|
| 39 |
response = requests.get(image_path)
|
| 40 |
+
if image_path.lower().endswith('.svg'):
|
| 41 |
+
png_data = cairosvg.svg2png(bytestring=response.content)
|
| 42 |
+
img = Image.open(BytesIO(png_data))
|
| 43 |
+
elif image_path.lower().endswith('.ico'):
|
| 44 |
+
img = Image.open(BytesIO(response.content)).convert('RGBA')
|
| 45 |
+
else:
|
| 46 |
+
img = Image.open(BytesIO(response.content))
|
| 47 |
elif image_path.startswith('data'):
|
|
|
|
| 48 |
encoded_data = image_path.split(',')[1]
|
| 49 |
decoded_data = base64.b64decode(encoded_data)
|
| 50 |
+
if image_path.lower().endswith('.svg'):
|
| 51 |
+
png_data = cairosvg.svg2png(bytestring=decoded_data)
|
| 52 |
+
img = Image.open(BytesIO(png_data))
|
| 53 |
+
elif image_path.lower().endswith('.ico'):
|
| 54 |
+
img = Image.open(BytesIO(decoded_data)).convert('RGBA')
|
| 55 |
+
else:
|
| 56 |
+
img = Image.open(BytesIO(decoded_data))
|
| 57 |
else:
|
| 58 |
+
if image_path.lower().endswith('.svg'):
|
| 59 |
+
png_data = cairosvg.svg2png(url=image_path)
|
| 60 |
+
img = Image.open(BytesIO(png_data))
|
| 61 |
+
elif image_path.lower().endswith('.ico'):
|
| 62 |
+
img = Image.open(image_path).convert('RGBA')
|
| 63 |
+
else:
|
| 64 |
+
img = Image.open(image_path)
|
| 65 |
except Exception as e:
|
| 66 |
raise Exception(f'Error opening image: {e}')
|
| 67 |
return img
|
|
|
|
| 93 |
def image_to_base64(image):
|
| 94 |
"""
|
| 95 |
Encodes an image to a base64 string.
|
| 96 |
+
Supports ICO files by converting them to PNG with RGBA channels.
|
| 97 |
+
|
| 98 |
Parameters:
|
| 99 |
image (str or PIL.Image.Image): The file path, URL, DataURL string, or PIL Image object of the image to encode.
|
| 100 |
+
|
| 101 |
Returns:
|
| 102 |
str: A base64-encoded string of the image.
|
| 103 |
"""
|
| 104 |
buffered = BytesIO()
|
| 105 |
+
if isinstance(image, str):
|
| 106 |
image = open_image(image)
|
| 107 |
image.save(buffered, format="PNG")
|
| 108 |
return "data:image/png;base64," + base64.b64encode(buffered.getvalue()).decode()
|
|
|
|
| 468 |
"""
|
| 469 |
with open(path_lut) as f:
|
| 470 |
lut_raw = f.read().splitlines()
|
|
|
|
| 471 |
size = round(len(lut_raw) ** (1 / 3))
|
| 472 |
row2val = lambda row: tuple([float(val) for val in row.split(" ")])
|
| 473 |
lut_table = [row2val(row) for row in lut_raw if is_3dlut_row(row.split(" "))]
|
|
|
|
| 474 |
return ImageFilter.Color3DLUT(size, lut_table, num_channels)
|
| 475 |
|
| 476 |
def apply_lut(img: Image, lut_path: str = "", lut: ImageFilter.Color3DLUT = None) -> Image:
|
|
|
|
| 494 |
if lut_path == "":
|
| 495 |
raise ValueError("Either lut_path or lut argument must be provided.")
|
| 496 |
lut = read_lut(lut_path)
|
|
|
|
| 497 |
return img.filter(lut)
|
| 498 |
|
| 499 |
def show_lut(lut_filename: str, lut_example_image: Image = default_lut_example_img) -> Image:
|
|
|
|
| 506 |
lut_example_image = open_image(default_lut_example_img)
|
| 507 |
return lut_example_image
|
| 508 |
|
| 509 |
+
|
| 510 |
+
|
| 511 |
def convert_rgb_to_rgba_safe(image: Image) -> Image:
|
| 512 |
"""
|
| 513 |
Converts an RGB image to RGBA by adding an alpha channel.
|
|
|
|
| 527 |
image = image.convert('RGB')
|
| 528 |
else:
|
| 529 |
raise ValueError("Unsupported image mode for conversion to RGBA.")
|
|
|
|
| 530 |
# Create a copy of the image to avoid modifying the original
|
| 531 |
rgba_image = image.copy()
|
|
|
|
| 532 |
# Optionally, set a default alpha value (e.g., fully opaque)
|
| 533 |
alpha = Image.new('L', rgba_image.size, 255) # 255 for full opacity
|
| 534 |
+
rgba_image.putalpha(alpha)
|
|
|
|
| 535 |
return rgba_image
|
| 536 |
|
| 537 |
+
def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image, str]:
|
| 538 |
"""
|
| 539 |
Apply a LUT to an image and return the result.
|
| 540 |
+
Supports ICO files by converting them to PNG with RGBA channels.
|
| 541 |
+
|
| 542 |
Args:
|
| 543 |
lut_filename: A string representing the path to the LUT file.
|
| 544 |
image_path: A string representing the path to the input image.
|
| 545 |
+
|
| 546 |
Returns:
|
| 547 |
+
tuple: A tuple containing the PIL Image object with the LUT applied and the new image path as a string.
|
| 548 |
"""
|
| 549 |
+
if image_path is None:
|
| 550 |
+
raise UserWarning("No image provided.")
|
| 551 |
+
return None, None
|
| 552 |
+
path = Path(image_path)
|
| 553 |
img = open_image(image_path)
|
| 554 |
+
if not ((path.suffix.lower() == '.png' and img.mode == 'RGBA')):
|
| 555 |
+
if image_path.lower().endswith(('.jpg', '.jpeg')):
|
| 556 |
+
img, new_image_path = convert_jpg_to_rgba(path)
|
| 557 |
+
elif image_path.lower().endswith('.ico'):
|
| 558 |
+
img, new_image_path = convert_to_rgba_png(image_path)
|
| 559 |
+
elif image_path.lower().endswith(('.gif', '.webp')):
|
| 560 |
+
img, new_image_path = convert_to_rgba_png(image_path)
|
| 561 |
+
else:
|
| 562 |
+
img, new_image_path = convert_to_rgba_png(image_path)
|
| 563 |
+
delete_image(image_path)
|
| 564 |
+
else:
|
| 565 |
+
new_image_path = image_path
|
| 566 |
if lut_filename is not None:
|
| 567 |
try:
|
| 568 |
img = apply_lut(img, lut_filename)
|
| 569 |
except Exception as e:
|
| 570 |
print(f"BAD LUT: Error applying LUT {str(e)}.")
|
| 571 |
+
img.save(new_image_path, format='PNG')
|
| 572 |
+
return img, str(new_image_path)
|
| 573 |
|
| 574 |
+
############################################# RGBA ###########################################################
|
| 575 |
|
| 576 |
+
# Example usage
|
| 577 |
+
# convert_jpg_to_rgba('input.jpg', 'output.png')
|
| 578 |
+
def convert_jpg_to_rgba(input_path) -> tuple[Image, str]:
|
| 579 |
"""
|
| 580 |
+
Convert a JPG image to RGBA format and save it as a PNG.
|
| 581 |
|
| 582 |
Args:
|
| 583 |
+
input_path (str or Path): Path to the input JPG image file.
|
| 584 |
|
| 585 |
Raises:
|
| 586 |
+
FileNotFoundError: If the input file does not exist.
|
| 587 |
+
ValueError: If the input file is not a JPG.
|
| 588 |
+
OSError: If there's an error reading or writing the file.
|
| 589 |
+
|
| 590 |
+
Returns:
|
| 591 |
+
tuple: A tuple containing the RGBA image and the output path as a string.
|
| 592 |
"""
|
| 593 |
try:
|
| 594 |
+
# Convert input_path to Path object if it's a string
|
| 595 |
+
input_path = Path(input_path)
|
| 596 |
+
output_path = input_path.with_suffix('.png')
|
| 597 |
+
|
| 598 |
+
# Check if the input file exists
|
| 599 |
+
if not input_path.exists():
|
| 600 |
+
raise FileNotFoundError(f"The file {input_path} does not exist.")
|
| 601 |
|
| 602 |
+
# Check file extension first to skip unnecessary processing
|
| 603 |
+
if input_path.suffix.lower() not in ('.jpg', '.jpeg'):
|
| 604 |
+
print(f"Skipping conversion: {input_path} is not a JPG or JPEG file.")
|
| 605 |
+
return None, str(output_path)
|
| 606 |
+
|
| 607 |
+
print(f"Converting to PNG: {input_path} is a JPG or JPEG file.")
|
| 608 |
+
|
| 609 |
+
# Open the image file
|
| 610 |
+
with Image.open(input_path) as img:
|
| 611 |
+
# Convert the image to RGBA mode
|
| 612 |
+
rgba_img = img.convert('RGBA')
|
| 613 |
+
|
| 614 |
+
# Ensure the directory exists for the output file
|
| 615 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 616 |
+
|
| 617 |
+
# Save the image with RGBA mode as PNG
|
| 618 |
+
rgba_img.save(output_path)
|
| 619 |
+
|
| 620 |
+
except FileNotFoundError as e:
|
| 621 |
+
print(f"Error: {e}")
|
| 622 |
+
except ValueError as e:
|
| 623 |
+
print(f"Error: {e}")
|
| 624 |
+
except OSError as e:
|
| 625 |
+
print(f"Error: An OS error occurred while processing the image - {e}")
|
| 626 |
+
except Exception as e:
|
| 627 |
+
print(f"An unexpected error occurred: {e}")
|
| 628 |
+
return rgba_img, str(output_path)
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
def convert_to_rgba_png(file_path: str) -> tuple[Image, str]:
|
| 632 |
+
"""
|
| 633 |
+
Converts an image to RGBA PNG format and saves it with the same base name and a .png extension.
|
| 634 |
+
Supports ICO files.
|
| 635 |
+
|
| 636 |
+
Args:
|
| 637 |
+
file_path (str): The path to the input image file.
|
| 638 |
+
|
| 639 |
+
Returns:
|
| 640 |
+
tuple: A tuple containing the RGBA image and the new file path as a string.
|
| 641 |
+
"""
|
| 642 |
+
new_file_path = None
|
| 643 |
+
rgba_img = None
|
| 644 |
+
img = None
|
| 645 |
+
if file_path is None:
|
| 646 |
+
raise UserWarning("No image provided.")
|
| 647 |
+
return None, None
|
| 648 |
+
try:
|
| 649 |
+
img = open_image(file_path)
|
| 650 |
+
print(f"Opened image: {file_path}\n")
|
| 651 |
+
# Handle ICO files
|
| 652 |
+
if file_path.lower().endswith('.ico'):
|
| 653 |
+
rgba_img = img.convert('RGBA')
|
| 654 |
+
new_file_path = Path(file_path).with_suffix('.png')
|
| 655 |
+
rgba_img.save(new_file_path, format='PNG')
|
| 656 |
+
print(f"Converted ICO to PNG: {new_file_path}")
|
| 657 |
+
else:
|
| 658 |
+
rgba_img, new_file_path = convert_jpg_to_rgba(file_path)
|
| 659 |
+
if rgba_img is None:
|
| 660 |
+
rgba_img = convert_rgb_to_rgba_safe(img)
|
| 661 |
+
new_file_path = Path(file_path).with_suffix('.png')
|
| 662 |
+
rgba_img.save(new_file_path, format='PNG')
|
| 663 |
+
print(f"Image saved as {new_file_path}")
|
| 664 |
except ValueError as ve:
|
| 665 |
print(f"ValueError: {ve}")
|
| 666 |
except Exception as e:
|
| 667 |
+
print(f"Error converting image: {e}")
|
| 668 |
+
return rgba_img if rgba_img else img, str(new_file_path)
|
| 669 |
+
|
| 670 |
+
def delete_image(file_path: str) -> None:
|
| 671 |
+
"""
|
| 672 |
+
Deletes the specified image file.
|
| 673 |
+
|
| 674 |
+
Parameters:
|
| 675 |
+
file_path (str): The path to the image file to delete.
|
| 676 |
+
|
| 677 |
+
Raises:
|
| 678 |
+
FileNotFoundError: If the file does not exist.
|
| 679 |
+
Exception: If there is an error deleting the file.
|
| 680 |
+
"""
|
| 681 |
+
try:
|
| 682 |
+
path = Path(file_path)
|
| 683 |
+
path.unlink()
|
| 684 |
+
print(f"Deleted original image: {file_path}")
|
| 685 |
+
except FileNotFoundError:
|
| 686 |
+
print(f"File not found: {file_path}")
|
| 687 |
+
except Exception as e:
|
| 688 |
+
print(f"Error deleting image: {e}")
|