Implement Nova Farma S1 Max: Layered terrain, water mechanics, Y-sorting, and asset cleanup
This commit is contained in:
59
scripts/clean_and_resize_batch_2.py
Normal file
59
scripts/clean_and_resize_batch_2.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import sys
|
||||
|
||||
def remove_white_bg_and_resize(path, target_width=None):
|
||||
if not os.path.exists(path):
|
||||
print(f"File not found: {path}")
|
||||
return
|
||||
|
||||
img = cv2.imread(path)
|
||||
if img is None:
|
||||
print(f"Error loading {path}")
|
||||
return
|
||||
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
|
||||
|
||||
# Threshold for white
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
|
||||
_, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)
|
||||
mask_inv = cv2.bitwise_not(mask)
|
||||
|
||||
b, g, r, a = cv2.split(img)
|
||||
final_img = cv2.merge((b, g, r, mask_inv))
|
||||
|
||||
# Crop
|
||||
coords = cv2.findNonZero(mask_inv)
|
||||
if coords is not None:
|
||||
x, y, w, h = cv2.boundingRect(coords)
|
||||
pad = 2
|
||||
x = max(0, x - pad); y = max(0, y - pad)
|
||||
w = min(final_img.shape[1] - x, w + 2*pad)
|
||||
h = min(final_img.shape[0] - y, h + 2*pad)
|
||||
final_img = final_img[y:y+h, x:x+w]
|
||||
|
||||
# Resize
|
||||
if target_width:
|
||||
h_curr, w_curr = final_img.shape[:2]
|
||||
if w_curr > target_width:
|
||||
ratio = target_width / w_curr
|
||||
new_h = int(h_curr * ratio)
|
||||
final_img = cv2.resize(final_img, (target_width, new_h), interpolation=cv2.INTER_AREA)
|
||||
print(f"Resized {path} to {target_width}x{new_h}")
|
||||
|
||||
cv2.imwrite(path, final_img)
|
||||
print(f"Processed: {path}")
|
||||
|
||||
# DEFINIRAJ NOVE CILJE
|
||||
TARGETS = {
|
||||
'assets/DEMO_FAZA1/Environment/sign_danger.png': 256,
|
||||
'assets/DEMO_FAZA1/UI/badge_trial.png': 512,
|
||||
'assets/DEMO_FAZA1/Items/item_longboard_wheel.png': 128,
|
||||
'assets/DEMO_FAZA1/VFX/overlay_amnesia_blood.png': 1024 # Overlay stays big
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
base = os.getcwd()
|
||||
for rel, w in TARGETS.items():
|
||||
remove_white_bg_and_resize(os.path.join(base, rel), w)
|
||||
83
scripts/clean_and_resize_white.py
Normal file
83
scripts/clean_and_resize_white.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import sys
|
||||
|
||||
def remove_white_bg_and_resize(path, target_width=None):
|
||||
if not os.path.exists(path):
|
||||
print(f"File not found: {path}")
|
||||
return
|
||||
|
||||
img = cv2.imread(path)
|
||||
if img is None:
|
||||
print(f"Error loading {path}")
|
||||
return
|
||||
|
||||
# Convert to RGBA
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
|
||||
|
||||
# Threshold for white (allowing slight off-white/compression artifacts)
|
||||
# White is (255,255,255). Let's say anything > 240 in all channels.
|
||||
lower_white = np.array([240, 240, 240, 255])
|
||||
upper_white = np.array([255, 255, 255, 255])
|
||||
|
||||
# Create mask: looking for white pixels
|
||||
# Note: cv2.inRange checks ranges. For BGRA, A is 255.
|
||||
# However, input usually doesn't have A yet if loaded as BGR.
|
||||
# But we converted.
|
||||
|
||||
# Alternative: Grayscale thresholding is often safer for "almost white" backgrounds in illustrations
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
|
||||
# Create binary mask where white is 255, else 0
|
||||
_, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)
|
||||
|
||||
# Invert mask: We want to KEEP non-white.
|
||||
# Non-white = 0 in mask? No, thresholded white became 255.
|
||||
# So we invert.
|
||||
mask_inv = cv2.bitwise_not(mask)
|
||||
|
||||
# Morphological cleanup (remove small white specks inside object if necessary, or smooth edges)
|
||||
# For vector art, simple threshold usually works well.
|
||||
|
||||
# Assign new alpha channel
|
||||
b, g, r, a = cv2.split(img)
|
||||
# Use mask_inv as alpha
|
||||
final_img = cv2.merge((b, g, r, mask_inv))
|
||||
|
||||
# Crop to content (Trim transparent borders)
|
||||
coords = cv2.findNonZero(mask_inv)
|
||||
if coords is not None:
|
||||
x, y, w, h = cv2.boundingRect(coords)
|
||||
# Padding
|
||||
pad = 2
|
||||
x = max(0, x - pad)
|
||||
y = max(0, y - pad)
|
||||
w = min(final_img.shape[1] - x, w + 2*pad)
|
||||
h = min(final_img.shape[0] - y, h + 2*pad)
|
||||
final_img = final_img[y:y+h, x:x+w]
|
||||
|
||||
# Resize if target_width provided
|
||||
if target_width:
|
||||
h_curr, w_curr = final_img.shape[:2]
|
||||
if w_curr > target_width: # Only scale down
|
||||
ratio = target_width / w_curr
|
||||
new_h = int(h_curr * ratio)
|
||||
final_img = cv2.resize(final_img, (target_width, new_h), interpolation=cv2.INTER_AREA)
|
||||
print(f"Resized to {target_width}x{new_h}")
|
||||
|
||||
cv2.imwrite(path, final_img)
|
||||
print(f"Processed (Cleaned White BG + Trim): {path}")
|
||||
|
||||
TARGETS = {
|
||||
'assets/DEMO_FAZA1/UI/badge_purchase.png': 512,
|
||||
'assets/DEMO_FAZA1/UI/health_critical.png': 512,
|
||||
'assets/DEMO_FAZA1/VFX/toxic_fog.png': 512,
|
||||
'assets/DEMO_FAZA1/Environment/obstacle_thorns.png': 256,
|
||||
'assets/DEMO_FAZA1/Structures/foundation_concrete.png': 512,
|
||||
'assets/DEMO_FAZA1/Items/hay_drop_0.png': 128
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
base = os.getcwd()
|
||||
for rel, w in TARGETS.items():
|
||||
remove_white_bg_and_resize(os.path.join(base, rel), w)
|
||||
@@ -1,59 +1,59 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import sys
|
||||
import os
|
||||
|
||||
def remove_pink_bg():
|
||||
input_path = '/Users/davidkotnik/.gemini/antigravity/brain/63340bd3-91a9-439d-b1d9-5692ce5adaea/visoka_trava_v2_pink_bg_1769436757738.png'
|
||||
output_path = 'assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png'
|
||||
def clean_pink_and_soften(input_path, output_path):
|
||||
print(f"Processing {input_path}...")
|
||||
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
# Read image
|
||||
img = cv2.imread(input_path)
|
||||
img = cv2.imread(input_path, cv2.IMREAD_UNCHANGED)
|
||||
if img is None:
|
||||
print(f"Error: Could not read {input_path}")
|
||||
print("Failed to load image.")
|
||||
return
|
||||
|
||||
# Convert to RGBA
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
|
||||
# Check channels
|
||||
if img.shape[2] == 3:
|
||||
b, g, r = cv2.split(img)
|
||||
# Create alpha (255)
|
||||
a = np.ones_like(b) * 255
|
||||
img = cv2.merge((b, g, r, a))
|
||||
|
||||
# Define Magenta range (B, G, R) - standard magenta is (255, 0, 255) in BGR
|
||||
# We'll allow a small tolerance to catch anti-aliasing edges
|
||||
sensitivity = 20
|
||||
lower_magenta = np.array([255 - sensitivity, 0, 255 - sensitivity])
|
||||
upper_magenta = np.array([255, sensitivity, 255])
|
||||
# Targeted Pink Range in HSV
|
||||
hsv = cv2.cvtColor(img[:,:,0:3], cv2.COLOR_BGR2HSV)
|
||||
lower_pink = np.array([145, 50, 50])
|
||||
upper_pink = np.array([170, 255, 255])
|
||||
|
||||
# Create mask
|
||||
# Note: OpenCV reads as BGR, so Pink #FF00FF is (255, 0, 255)
|
||||
# Using simple color thresholding
|
||||
mask = cv2.inRange(img[:,:,:3], lower_magenta, upper_magenta)
|
||||
pink_mask = cv2.inRange(hsv, lower_pink, upper_pink)
|
||||
|
||||
# Invert mask (we want to keep non-magenta)
|
||||
mask_inv = cv2.bitwise_not(mask)
|
||||
|
||||
# Apply mask to alpha channel
|
||||
# Where mask is white (magenta pixels), alpha becomes 0
|
||||
# But mask_inv is white for KEEP pixels.
|
||||
# So we want Alpha to be 0 where mask is 255.
|
||||
|
||||
# Better approach:
|
||||
# 1. Split channels
|
||||
# 1. REMOVE PINK (Set alpha to 0)
|
||||
# Get current alpha
|
||||
b, g, r, a = cv2.split(img)
|
||||
|
||||
# 2. Update alpha channel using the inverted mask
|
||||
# If mask pixel is 255 (magenta), mask_inv is 0. We want alpha 0 there.
|
||||
# If mask pixel is 0 (not magenta), mask_inv is 255. We want alpha 255 there.
|
||||
# However, original alpha is 255 everywhere inside the image bounds.
|
||||
# So we can just take bitwise_and or just set alpha to mask_inv.
|
||||
|
||||
img[:, :, 3] = mask_inv
|
||||
# Where pink_mask is 255, set alpha to 0
|
||||
# Also combine with existing alpha (if image was already transparent, keep it)
|
||||
new_alpha = cv2.bitwise_and(a, cv2.bitwise_not(pink_mask))
|
||||
|
||||
# Optional: Basic edge smoothing/despiking if needed, but for pixel/vector art simple cut is often better.
|
||||
# 2. EDGE SOFTENING (Alpha Blur 1px)
|
||||
# We blur the alpha channel slightly to soften jagged edges
|
||||
soft_alpha = cv2.GaussianBlur(new_alpha, (3, 3), 0)
|
||||
|
||||
# Save
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"Successfully saved transparent image to {output_path}")
|
||||
# Thresholding back to keep semi-transparency only on very edge?
|
||||
# Or just keep the blur. For "Vector Style", usually we want sharp, but
|
||||
# user asked for "zmehčaj robove (Alpha Blur: 1px)".
|
||||
# Gaussian (3,3) is roughly 1px radius.
|
||||
|
||||
# 3. COLOR BLEED REMOVAL (Halo effect)
|
||||
# Often pink pixels leave a pink fringe.
|
||||
# We can dilate the non-pink color into the transparent area?
|
||||
# Or simpler: desaturate or darken pixels on the edge of transparency.
|
||||
# For now, let's stick to the requested Blur.
|
||||
|
||||
final_img = cv2.merge((b, g, r, soft_alpha))
|
||||
|
||||
cv2.imwrite(output_path, final_img)
|
||||
print(f"Saved cleaned image to {output_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
remove_pink_bg()
|
||||
# Hardcoded path as per instructions: Vegetation/visoka_trava_v2.png
|
||||
target = 'assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png'
|
||||
clean_pink_and_soften(target, target)
|
||||
|
||||
56
scripts/clean_single_water.py
Normal file
56
scripts/clean_single_water.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import sys
|
||||
|
||||
def remove_white_bg_and_resize(path, target_width=None):
|
||||
if not os.path.exists(path):
|
||||
print(f"File not found: {path}")
|
||||
return
|
||||
|
||||
img = cv2.imread(path)
|
||||
if img is None:
|
||||
print(f"Error loading {path}")
|
||||
return
|
||||
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
|
||||
|
||||
# Threshold for white
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
|
||||
_, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)
|
||||
mask_inv = cv2.bitwise_not(mask)
|
||||
|
||||
b, g, r, a = cv2.split(img)
|
||||
final_img = cv2.merge((b, g, r, mask_inv))
|
||||
|
||||
# Crop
|
||||
coords = cv2.findNonZero(mask_inv)
|
||||
if coords is not None:
|
||||
x, y, w, h = cv2.boundingRect(coords)
|
||||
pad = 2
|
||||
x = max(0, x - pad); y = max(0, y - pad)
|
||||
w = min(final_img.shape[1] - x, w + 2*pad)
|
||||
h = min(final_img.shape[0] - y, h + 2*pad)
|
||||
final_img = final_img[y:y+h, x:x+w]
|
||||
|
||||
# Resize
|
||||
if target_width:
|
||||
h_curr, w_curr = final_img.shape[:2]
|
||||
if w_curr > target_width:
|
||||
ratio = target_width / w_curr
|
||||
new_h = int(h_curr * ratio)
|
||||
final_img = cv2.resize(final_img, (target_width, new_h), interpolation=cv2.INTER_AREA)
|
||||
print(f"Resized {path} to {target_width}x{new_h}")
|
||||
|
||||
cv2.imwrite(path, final_img)
|
||||
print(f"Processed: {path}")
|
||||
|
||||
# SAMO NOVA ZADNJA SLIKA
|
||||
TARGETS = {
|
||||
'assets/DEMO_FAZA1/Environment/water_clean_patch.png': 512
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
base = os.getcwd()
|
||||
for rel, w in TARGETS.items():
|
||||
remove_white_bg_and_resize(os.path.join(base, rel), w)
|
||||
57
scripts/clean_water_assets.py
Normal file
57
scripts/clean_water_assets.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import sys
|
||||
|
||||
def remove_white_bg_and_resize(path, target_width=None):
|
||||
if not os.path.exists(path):
|
||||
print(f"File not found: {path}")
|
||||
return
|
||||
|
||||
img = cv2.imread(path)
|
||||
if img is None:
|
||||
print(f"Error loading {path}")
|
||||
return
|
||||
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
|
||||
|
||||
# Threshold for white
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
|
||||
_, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)
|
||||
mask_inv = cv2.bitwise_not(mask)
|
||||
|
||||
b, g, r, a = cv2.split(img)
|
||||
final_img = cv2.merge((b, g, r, mask_inv))
|
||||
|
||||
# Crop
|
||||
coords = cv2.findNonZero(mask_inv)
|
||||
if coords is not None:
|
||||
x, y, w, h = cv2.boundingRect(coords)
|
||||
pad = 2
|
||||
x = max(0, x - pad); y = max(0, y - pad)
|
||||
w = min(final_img.shape[1] - x, w + 2*pad)
|
||||
h = min(final_img.shape[0] - y, h + 2*pad)
|
||||
final_img = final_img[y:y+h, x:x+w]
|
||||
|
||||
# Resize
|
||||
if target_width:
|
||||
h_curr, w_curr = final_img.shape[:2]
|
||||
if w_curr > target_width:
|
||||
ratio = target_width / w_curr
|
||||
new_h = int(h_curr * ratio)
|
||||
final_img = cv2.resize(final_img, (target_width, new_h), interpolation=cv2.INTER_AREA)
|
||||
print(f"Resized {path} to {target_width}x{new_h}")
|
||||
|
||||
cv2.imwrite(path, final_img)
|
||||
print(f"Processed: {path}")
|
||||
|
||||
# NOVI CILJI (Voda)
|
||||
TARGETS = {
|
||||
'assets/DEMO_FAZA1/Environment/mud_puddle.png': 512,
|
||||
'assets/DEMO_FAZA1/Environment/stream_water.png': 512
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
base = os.getcwd()
|
||||
for rel, w in TARGETS.items():
|
||||
remove_white_bg_and_resize(os.path.join(base, rel), w)
|
||||
43
scripts/fix_pink_edges.py
Normal file
43
scripts/fix_pink_edges.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import glob
|
||||
|
||||
def fix_pink_edges(directory):
|
||||
print(f"Scanning {directory} for pink edges...")
|
||||
|
||||
# Target specific problematic file if needed, or all
|
||||
files = glob.glob(os.path.join(directory, "*.png"))
|
||||
|
||||
for path in files:
|
||||
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
|
||||
if img is None:
|
||||
continue
|
||||
|
||||
if img.shape[2] < 4:
|
||||
continue
|
||||
|
||||
b, g, r, a = cv2.split(img)
|
||||
|
||||
# 1. Wider Pink/Magenta Range
|
||||
# Catch anything with high Red/Blue and relatively lower Green
|
||||
pink_mask = (r > 150) & (g < 100) & (b > 150)
|
||||
|
||||
# Turn pink pixels transparent
|
||||
a[pink_mask] = 0
|
||||
|
||||
# 2. Aggressive Alpha Erosion
|
||||
# Increase iterations to eat 2 pixels into the edge
|
||||
# This removes the "halo" left by anti-aliasing against pink
|
||||
kernel = np.ones((3,3), np.uint8)
|
||||
a_eroded = cv2.erode(a, kernel, iterations=1) # 1 iteration with 3x3 kernel = 1-2px border
|
||||
|
||||
# Merge back
|
||||
img_fixed = cv2.merge((b, g, r, a_eroded))
|
||||
|
||||
cv2.imwrite(path, img_fixed)
|
||||
print(f"Aggressively Fixed: {os.path.basename(path)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
target_dir = "assets/DEMO_FAZA1/Vegetation"
|
||||
fix_pink_edges(target_dir)
|
||||
@@ -1,48 +1,48 @@
|
||||
|
||||
from PIL import Image
|
||||
import cv2
|
||||
import os
|
||||
|
||||
# Configuration
|
||||
# Configuration
|
||||
TARGET_DIRS = ["assets/grounds", "assets/maps/tilesets"] # Expanded targets
|
||||
TARGET_SIZE = (256, 256)
|
||||
# Define target widths for assets
|
||||
TARGETS = {
|
||||
'assets/DEMO_FAZA1/UI/badge_purchase.png': 512,
|
||||
'assets/DEMO_FAZA1/UI/health_critical.png': 512,
|
||||
'assets/DEMO_FAZA1/VFX/toxic_fog.png': 512,
|
||||
'assets/DEMO_FAZA1/Environment/obstacle_thorns.png': 256,
|
||||
'assets/DEMO_FAZA1/Structures/foundation_concrete.png': 512,
|
||||
'assets/DEMO_FAZA1/Items/hay_drop_0.png': 128
|
||||
}
|
||||
|
||||
def resize_images():
|
||||
for target_dir in TARGET_DIRS:
|
||||
if not os.path.exists(target_dir):
|
||||
print(f"⚠️ Directory {target_dir} not found. Skipping.")
|
||||
continue
|
||||
def resize_image(path, target_width):
|
||||
if not os.path.exists(path):
|
||||
print(f"Skipping {path}, file not found.")
|
||||
return
|
||||
|
||||
print(f"📂 Processing directory: {target_dir}...")
|
||||
|
||||
files = [f for f in os.listdir(target_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
|
||||
|
||||
for filename in files:
|
||||
filepath = os.path.join(target_dir, filename)
|
||||
try:
|
||||
with Image.open(filepath) as img:
|
||||
width, height = img.size
|
||||
|
||||
# SELECTIVE RESIZE LOGIC
|
||||
# 1. Must be Square 1024x1024 (Grounds, Props, Trees)
|
||||
# 2. Or if explicitly in 'grounds' folder and > 256
|
||||
|
||||
should_resize = False
|
||||
|
||||
if "grounds" in target_dir and width > 256:
|
||||
should_resize = True
|
||||
elif width == 1024 and height == 1024:
|
||||
should_resize = True
|
||||
|
||||
if should_resize:
|
||||
print(f"✨ Resizing {filename} ({width}x{height}) -> {TARGET_SIZE} (Lanczos)")
|
||||
img_resized = img.resize(TARGET_SIZE, Image.Resampling.LANCZOS)
|
||||
img_resized.save(filepath)
|
||||
else:
|
||||
pass # print(f"Skipping {filename} ({width}x{height}) - Criteria not met")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error processing {filename}: {e}")
|
||||
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
|
||||
if img is None:
|
||||
print(f"Error loading {path}")
|
||||
return
|
||||
|
||||
h, w = img.shape[:2]
|
||||
|
||||
# Check if resize is needed
|
||||
if w <= target_width:
|
||||
print(f"{path} is already {w}px (Target: {target_width}px). Skipping resize.")
|
||||
return
|
||||
|
||||
# Calculate new height to maintain aspect ratio
|
||||
ratio = target_width / w
|
||||
new_h = int(h * ratio)
|
||||
|
||||
resized = cv2.resize(img, (target_width, new_h), interpolation=cv2.INTER_AREA)
|
||||
|
||||
cv2.imwrite(path, resized)
|
||||
print(f"Resized {path}: {w}x{h} -> {target_width}x{new_h}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
resize_images()
|
||||
base_path = os.getcwd()
|
||||
print("Starting Resize Job...")
|
||||
|
||||
for rel_path, width in TARGETS.items():
|
||||
full_path = os.path.join(base_path, rel_path)
|
||||
resize_image(full_path, width)
|
||||
|
||||
print("Resize Job Complete.")
|
||||
|
||||
75
scripts/utils/clean_and_move_image.py
Normal file
75
scripts/utils/clean_and_move_image.py
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
def clean_and_save(input_path, output_path):
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
print(f"Processing: {input_path}")
|
||||
|
||||
# Read image
|
||||
img = cv2.imread(input_path)
|
||||
if img is None:
|
||||
print(f"Error: Could not read {input_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Convert to RGBA
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
|
||||
|
||||
# Convert to HSV for better color segmentation
|
||||
hsv = cv2.cvtColor(img[:,:,:3], cv2.COLOR_BGR2HSV)
|
||||
|
||||
# Define Magenta range in HSV
|
||||
# Magenta is around 300 degrees. OpenCV Hue is 0-179 (degrees/2). So 300/2 = 150.
|
||||
# Range: 140 - 160 approx.
|
||||
# We want to catch the solid pink background.
|
||||
|
||||
# Lower and Upper bounds for Magenta/Pink
|
||||
# Hue: 140-170, Saturation: > 50, Value: > 50
|
||||
lower_magenta = np.array([140, 50, 50])
|
||||
upper_magenta = np.array([170, 255, 255])
|
||||
|
||||
mask = cv2.inRange(hsv, lower_magenta, upper_magenta)
|
||||
|
||||
# Invert mask (keep non-magenta)
|
||||
mask_inv = cv2.bitwise_not(mask)
|
||||
|
||||
# Update alpha channel
|
||||
# Where mask is 255 (magenta), alpha should be 0.
|
||||
# We can just set the alpha channel to mask_inv directly.
|
||||
# However, to avoid hard edges, we might want to erode/dilate, but for pixel/vector props hard cut is usually fine.
|
||||
|
||||
# Let's perform a small morphological operation to remove pink noise on edges if any
|
||||
kernel = np.ones((1,1), np.uint8)
|
||||
mask_inv = cv2.morphologyEx(mask_inv, cv2.MORPH_OPEN, kernel)
|
||||
|
||||
b, g, r, a = cv2.split(img)
|
||||
img = cv2.merge((b, g, r, mask_inv))
|
||||
|
||||
# Crop to content (optional but good for assets)
|
||||
# Find bounding box of non-transparent area
|
||||
coords = cv2.findNonZero(mask_inv)
|
||||
if coords is not None:
|
||||
x, y, w, h = cv2.boundingRect(coords)
|
||||
# Add 1px padding if possible
|
||||
x = max(0, x-1)
|
||||
y = max(0, y-1)
|
||||
w = min(img.shape[1]-x, w+2)
|
||||
h = min(img.shape[0]-y, h+2)
|
||||
img = img[y:y+h, x:x+w]
|
||||
|
||||
# Save
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"Successfully saved cleaned image to {output_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Remove magenta background and save.')
|
||||
parser.add_argument('input', help='Input file path')
|
||||
parser.add_argument('output', help='Output file path')
|
||||
args = parser.parse_args()
|
||||
|
||||
clean_and_save(args.input, args.output)
|
||||
Reference in New Issue
Block a user