import os import json from PIL import Image, ImageDraw import concurrent.futures import numpy as np def remove_blank_zone(input_folder, output_folder): if not os.path.exists(output_folder): os.makedirs(output_folder) log = [] try: with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: futures = [] for filename in os.listdir(input_folder): if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')): input_path = os.path.join(input_folder, filename) output_path = os.path.join(output_folder, filename) future = executor.submit(process_image, input_path, output_path) futures.append((filename, future)) for filename, future in futures: image_log = future.result() log.append({ "image": filename, "actions": image_log }) with open(os.path.join(output_folder, 'process_log.json'), 'w') as log_file: json.dump(log, log_file, indent=4) print("Log saved to", os.path.join(output_folder, 'process_log.json')) except Exception as e: print("Error:", e) def get_bounding_box_with_threshold(image, threshold): # Convert image to numpy array img_array = np.array(image) # Get alpha channel alpha = img_array[:,:,3] # Find rows and columns where alpha > threshold rows = np.any(alpha > threshold, axis=1) cols = np.any(alpha > threshold, axis=0) # Find the bounding box top, bottom = np.where(rows)[0][[0, -1]] left, right = np.where(cols)[0][[0, -1]] if left < right and top < bottom: return (left, top, right, bottom) else: return None def process_image(image_path, output_path, add_padding_line=False, use_threshold=True): image = Image.open(image_path) image = image.convert("RGBA") # Get the bounding box of the non-blank area with threshold if use_threshold: bbox = get_bounding_box_with_threshold(image, threshold=10) else: bbox = image.getbbox() log = [] if bbox: # Check 1 pixel around the image for non-transparent pixels width, height = image.size cropped_sides = [] # Define tolerance for transparency tolerance = 10 # Adjust this value as needed # Check top edge if any(image.getpixel((x, 0))[3] > tolerance for x in range(width)): cropped_sides.append("top") # Check bottom edge if any(image.getpixel((x, height-1))[3] > tolerance for x in range(width)): cropped_sides.append("bottom") # Check left edge if any(image.getpixel((0, y))[3] > tolerance for y in range(height)): cropped_sides.append("left") # Check right edge if any(image.getpixel((width-1, y))[3] > tolerance for y in range(height)): cropped_sides.append("right") if cropped_sides: info_message = f"Info for {os.path.basename(image_path)}: The following sides of the image may contain cropped objects: {', '.join(cropped_sides)}" print(info_message) log.append({"info": info_message}) else: info_message = f"Info for {os.path.basename(image_path)}: The image is not cropped." print(info_message) log.append({"info": info_message}) # Crop the image to the bounding box image = image.crop(bbox) log.append({"action": "crop", "bbox": [str(bbox[0]), str(bbox[1]), str(bbox[2]), str(bbox[3])]}) # Calculate the new size to expand the image padding = 125 target_size = 1080 aspect_ratio = image.width / image.height if len(cropped_sides) == 4: # If the image is cropped on all sides, center crop it to fit the canvas if aspect_ratio > 1: # Landscape new_height = target_size new_width = int(new_height * aspect_ratio) left = (new_width - target_size) // 2 image = image.resize((new_width, new_height), Image.LANCZOS) image = image.crop((left, 0, left + target_size, target_size)) else: # Portrait or square new_width = target_size new_height = int(new_width / aspect_ratio) top = (new_height - target_size) // 2 image = image.resize((new_width, new_height), Image.LANCZOS) image = image.crop((0, top, target_size, top + target_size)) log.append({"action": "center_crop_resize", "new_size": f"{target_size}x{target_size}"}) x, y = 0, 0 elif not cropped_sides: # If the image is not cropped, expand it from center until it touches the padding new_height = 1080 - 2 * padding # Ensure it touches top and bottom padding new_width = int(new_height * aspect_ratio) if new_width > 1080 - 2 * padding: # If width exceeds available space, adjust based on width new_width = 1080 - 2 * padding new_height = int(new_width / aspect_ratio) # Resize the image image = image.resize((new_width, new_height), Image.LANCZOS) log.append({"action": "resize", "new_width": str(new_width), "new_height": str(new_height)}) x = (1080 - new_width) // 2 y = 1080 - new_height - padding else: # New logic for handling cropped top and left, or top and right if set(cropped_sides) == {"top", "left"} or set(cropped_sides) == {"top", "right"}: new_height = target_size - padding # Ensure bottom padding new_width = int(new_height * aspect_ratio) # If new width exceeds canvas width, adjust based on width if new_width > target_size: new_width = target_size new_height = int(new_width / aspect_ratio) # Resize the image image = image.resize((new_width, new_height), Image.LANCZOS) log.append({"action": "resize", "new_width": str(new_width), "new_height": str(new_height)}) # Set position if "left" in cropped_sides: x = 0 else: # right in cropped_sides x = target_size - new_width y = 0 # If the resized image is taller than the canvas minus padding, crop from the bottom if new_height > target_size - padding: crop_bottom = new_height - (target_size - padding) image = image.crop((0, 0, new_width, new_height - crop_bottom)) new_height = target_size - padding log.append({"action": "crop_vertical", "bottom_pixels_removed": str(crop_bottom)}) log.append({"action": "position", "x": str(x), "y": str(y)}) elif set(cropped_sides) == {"bottom", "left", "right"}: # Expand the image from the center new_width = target_size new_height = int(new_width / aspect_ratio) if new_height < target_size: new_height = target_size new_width = int(new_height * aspect_ratio) image = image.resize((new_width, new_height), Image.LANCZOS) # Crop to fit the canvas left = (new_width - target_size) // 2 top = 0 image = image.crop((left, top, left + target_size, top + target_size)) log.append({"action": "expand_and_crop", "new_size": f"{target_size}x{target_size}"}) x, y = 0, 0 elif cropped_sides == ["top"]: # New logic for handling only top-cropped images if image.width > image.height: new_width = target_size new_height = int(target_size / aspect_ratio) else: new_height = target_size - padding # Ensure bottom padding new_width = int(new_height * aspect_ratio) # Resize the image image = image.resize((new_width, new_height), Image.LANCZOS) log.append({"action": "resize", "new_width": str(new_width), "new_height": str(new_height)}) x = (1080 - new_width) // 2 y = 0 # Align to top # Apply padding only to non-cropped sides x = max(padding, min(x, 1080 - new_width - padding)) elif cropped_sides in [["right"], ["left"]]: # New logic for handling only right-cropped or left-cropped images if image.width > image.height: new_width = target_size - padding # Ensure padding on non-cropped side new_height = int(new_width / aspect_ratio) else: new_height = target_size - padding # Ensure bottom padding new_width = int(new_height * aspect_ratio) # Resize the image image = image.resize((new_width, new_height), Image.LANCZOS) log.append({"action": "resize", "new_width": str(new_width), "new_height": str(new_height)}) if cropped_sides == ["right"]: x = 1080 - new_width # Align to right else: # cropped_sides == ["left"] x = 0 # Align to left without padding y = 1080 - new_height - padding # Respect bottom padding elif set(cropped_sides) == {"left", "right"}: # Logic for handling images cropped on both left and right sides new_width = 1080 # Expand to full width of canvas # Calculate the aspect ratio of the original image aspect_ratio = image.width / image.height # Calculate the new height while maintaining aspect ratio new_height = int(new_width / aspect_ratio) # Resize the image image = image.resize((new_width, new_height), Image.LANCZOS) log.append({"action": "resize", "new_width": str(new_width), "new_height": str(new_height)}) # Set horizontal position (always 0 as it spans full width) x = 0 # Calculate vertical position to respect bottom padding y = 1080 - new_height - padding # If the resized image is taller than the canvas, crop from the top only if new_height > 1080 - padding: crop_top = new_height - (1080 - padding) image = image.crop((0, crop_top, new_width, new_height)) new_height = 1080 - padding y = 0 log.append({"action": "crop_vertical", "top_pixels_removed": str(crop_top)}) else: # Align the image to the bottom with padding y = 1080 - new_height - padding log.append({"action": "position", "x": str(x), "y": str(y)}) else: # Use the original resizing logic for other partially cropped images if image.width > image.height: new_width = target_size new_height = int(target_size / aspect_ratio) else: new_height = target_size new_width = int(target_size * aspect_ratio) # Resize the image image = image.resize((new_width, new_height), Image.LANCZOS) log.append({"action": "resize", "new_width": str(new_width), "new_height": str(new_height)}) # Center horizontally for all images x = (1080 - new_width) // 2 y = 1080 - new_height - padding # Adjust positions for cropped sides if "top" in cropped_sides: y = 0 elif "bottom" in cropped_sides: y = 1080 - new_height if "left" in cropped_sides: x = 0 elif "right" in cropped_sides: x = 1080 - new_width # Apply padding only to non-cropped sides, but keep horizontal centering if "left" not in cropped_sides and "right" not in cropped_sides: x = (1080 - new_width) // 2 # Always center horizontally if "top" not in cropped_sides and "bottom" not in cropped_sides: y = max(padding, min(y, 1080 - new_height - padding)) # Create a new 1080x1080 canvas with a white background canvas = Image.new("RGBA", (1080, 1080), (255, 255, 255, 255)) # Paste the resized image onto the canvas canvas.paste(image, (x, y), image) log.append({"action": "paste", "position": [str(x), str(y)]}) # Add visible black line for padding (for all images) if add_padding_line: draw = ImageDraw.Draw(canvas) draw.rectangle([padding, padding, 1080 - padding, 1080 - padding], outline="black", width=5) log.append({"action": "add_padding_line"}) # Save the final image canvas.save(output_path) log.append({"action": "save", "output_path": output_path}) return log # Example usage remove_blank_zone("bria_output", "test_output")