Surn commited on
Commit
47fbc1a
·
1 Parent(s): 06b2c5c
Files changed (4) hide show
  1. app.py +16 -11
  2. utils/file_utils.py +17 -1
  3. utils/hex_grid.py +3 -3
  4. 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
- with Image.open(img_path) as pil_img:
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.to("cpu")
777
- del image_processor
778
- del depth_model
779
- del TRELLIS_PIPELINE
 
 
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)[0],
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-Column Coordinates", "Column Letter, Row Number", "Column Number, Row Letter", "Sequential Numbers", "Playing Cards Sequential", "Playing Cards Alternate Red and Black", "Custom List"], value=None)
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
- os.rename(file_path, new_file_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-Column 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 Number":
282
  text = f"{number_to_letter(col)}{row}"
283
- elif add_hex_text_option == "Column Number Row Letter":
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
- else:
50
- image_path = rename_file_to_lowercase_extension(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
- def read_lut(path_lut: Union[str, os.PathLike], num_channels: int = 3) -> ImageFilter.Color3DLUT:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- new_image_path = image_path
623
- if lut_filename is not None:
 
 
 
624
  try:
625
- img = apply_lut(img, lut_filename)
626
  except Exception as e:
627
  print(f"BAD LUT: Error applying LUT {str(e)}.")
628
- img.save(new_image_path.lower(), format='PNG')
629
- return img, str(new_image_path)
 
 
 
 
 
 
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
- raise FileNotFoundError(f"The file {input_path} does not exist.")
 
 
 
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 Image.open(input_path), str(output_path)
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