feat: Integrated Stream asset and Kai animation system

This commit is contained in:
2026-01-29 00:09:00 +01:00
parent 94565adffc
commit afa0e3c662
59 changed files with 1477 additions and 19 deletions

View File

@@ -0,0 +1,85 @@
import cv2
import numpy as np
import os
def bury_pipe_stream():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img = cv2.imread(src_path)
if img is None: return
# 1. GrabCut to isolate object from BG
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
h, w = img.shape[:2]
cv2.grabCut(img, mask, (10, 10, w-20, h-20), bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
mask_final = np.where((mask==2)|(mask==0),0,1).astype('uint8')
# 2. REMOVE OUTER WALLS (The "Burying" Step)
# The walls are the darkest parts of the ground structure.
# The "Top Rim" (ground level) is lighter brown.
# The water is murky brown/gray.
# Convert to HSV to separate by brightness/color
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Define "Wall" Color range (Dark Brown / Black shadow)
# H: Any (Browns are 0-20 or 160-180)
# S: Low to Medium
# V: VERY LOW (Shadows)
# Let's visualize: brightness < 40?
v_channel = hsv[:,:,2]
# Create a mask of "Very Dark" pixels
wall_mask = (v_channel < 60)
# But we want to keep outlines (which are thin dark lines).
# Walls are LARGE blocks of dark.
# We can use morphological opening to select only LARGE dark areas (walls) and ignore thin lines (outlines).
wall_mask_uint8 = wall_mask.astype(np.uint8)
kernel = np.ones((5,5), np.uint8)
# Erode then Dilate -> Removes small noise (lines), keeps big blobs (walls)
walls_only = cv2.morphologyEx(wall_mask_uint8, cv2.MORPH_OPEN, kernel, iterations=2)
# Now, subtract 'walls_only' from our main mask.
# We want mask_final to be 0 where walls_only is 1.
mask_buried = mask_final.copy()
mask_buried[walls_only == 1] = 0
# 3. FIX: This might delete the interior of the pipe (shadow) or the drain hole!
# We must PROTECT the pipe/drain area.
# The Drain is at the top right. Let's define a Region of Interest (ROI) that we DO NOT touch.
# Pipe is roughly in top right quadrant.
# Heuristic: Only apply wall removal to the bottom half/rim?
# Or better: The walls are on the OUTSIDE boundary.
# Let's perform the subtraction.
# 4. FIX PIPE HOLE (Make opaque again if GrabCut lost it)
# (Re-using logic from previous step if needed, but GrabCut usually keeps it).
# Combine
b, g, r = cv2.split(img)
alpha = mask_buried * 255
img_rgba = cv2.merge([b, g, r, alpha])
# Crop
coords = cv2.findNonZero(alpha)
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
img_rgba = img_rgba[y:y+ch, x:x+cw]
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets/stream_pipe.png',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment/stream_pipe.png'
]
for t in targets:
cv2.imwrite(t, img_rgba)
print(f"Saved {t}")
if __name__ == "__main__":
bury_pipe_stream()

View File

@@ -0,0 +1,72 @@
import cv2
import numpy as np
import os
import shutil
def clean_pipe_stream():
# Source is the uploaded JPG
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img = cv2.imread(src_path)
if img is None:
print("Failed to load image")
return
# Add Alpha Channel
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# Advanced Background Removal using FloodFill from corners
# The background is likely uniform-ish but with JPG noise.
# We create a mask initialized with zeros (2 pixels larger than image)
h, w = img.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
# Floodfill from (0,0) with a tolerance
# LoDiff and UpDiff allow for noise (e.g. +/- 10 in color value)
flood_flags = 4 | (255 << 8) | cv2.FLOODFILL_MASK_ONLY | cv2.FLOODFILL_FIXED_RANGE
# We need to know the 'seed' color to guess tolerance.
seed_color = img[0,0][:3].tolist()
print(f"Seed Color at 0,0: {seed_color}")
# Run floodFill from top-left
# Using a generous tolerance because the background looks like a solid color render but might have gradients/noise
cv2.floodFill(img, mask, (0,0), (0,0,0,0), (10,10,10), (10,10,10), flood_flags)
# Also try top-right, bottom-left, bottom-right just in case
cv2.floodFill(img, mask, (w-1, 0), (0,0,0,0), (10,10,10), (10,10,10), flood_flags)
cv2.floodFill(img, mask, (0, h-1), (0,0,0,0), (10,10,10), (10,10,10), flood_flags)
cv2.floodFill(img, mask, (w-1, h-1), (0,0,0,0), (10,10,10), (10,10,10), flood_flags)
# The 'mask' now contains 255 where the background was found.
# Note: floodFill mask is 2 pixels bigger. Crop it back.
mask = mask[1:-1, 1:-1]
# Set alpha to 0 where mask is set
img[mask > 0, 3] = 0
# Crop the image to the actual content (non-transparent parts)
# Find bounding box of alpha > 0
coords = cv2.findNonZero(img[:,:,3]) # Points where alpha is NOT 0
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
print(f"Cropping to: {x},{y} {cw}x{ch}")
img = img[y:y+ch, x:x+cw]
# Destinations
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment'
]
dest_name = 'stream_pipe.png'
for t in targets:
if not os.path.exists(t): os.makedirs(t, exist_ok=True)
final_path = os.path.join(t, dest_name)
cv2.imwrite(final_path, img)
print(f"Saved cleaned asset to {final_path}")
if __name__ == "__main__":
clean_pipe_stream()

View File

@@ -0,0 +1,73 @@
import cv2
import numpy as np
import os
def clean_pipe_stream_final():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img = cv2.imread(src_path)
if img is None: return
# 1. GRABCUT for Clean Foreground (Pipe + Stream + Earth)
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
h, w = img.shape[:2]
# Rect: slight margin
cv2.grabCut(img, mask, (10, 10, w-20, h-20), bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
mask_final = np.where((mask==2)|(mask==0),0,1).astype('uint8') # 0 = BG, 1 = FG
# 2. FILL THE DRAIN HOLE (Make it opaque)
# The drain is typically at the top-right or top area.
# We can detect the "grating" (dark criss-cross lines).
# Simple heuristic: The pipe opening is a dark area.
# To prevent it from becoming transparent, we force the mask to be 1 in that region?
# GrabCut usually keeps the hole opaque if it's distinct from BG.
# But if there are "holes" in the GrabCut mask (transparency inside the object), we should fill them.
# Close small holes in the mask (Morphological Closing)
kernel = np.ones((5,5),np.uint8)
mask_closed = cv2.morphologyEx(mask_final, cv2.MORPH_CLOSE, kernel)
# 3. "BURYING" (Removing outer walls)
# The outer walls are usually at the bottom-left and bottom-right edges of the mask.
# We can try to "shave" the bottom of the mask to reduce the "block height".
# Let's shift the mask up? No.
# Let's erode the mask from the bottom?
# We can assume the "floor" is higher.
# This is risky, but let's try a mild erosion just on the edges to smooth the blend.
# Actually, if we just ensure the alpha channel is clean, it might be enough.
# Convert to RGBA
b, g, r = cv2.split(img)
alpha = mask_closed * 255
# 4. COLOR FIX: If the drain hole inside is gray/white (from original image),
# the user might want it BLACK (so it looks deep).
# Let's darken the "dark" areas inside the foreground.
# Convert to HSV, find dark areas in FG, make them blacker.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Dark areas: Value < 50?
dark_mask = (hsv[:,:,2] < 80) & (mask_closed == 1)
img[dark_mask] = [20, 20, 20] # Very dark gray/black
img_rgba = cv2.merge([img[:,:,0], img[:,:,1], img[:,:,2], alpha])
# Crop
coords = cv2.findNonZero(alpha)
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
img_rgba = img_rgba[y:y+ch, x:x+cw]
# Save
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets/stream_pipe.png',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment/stream_pipe.png'
]
for t in targets:
cv2.imwrite(t, img_rgba)
print(f"Saved {t}")
if __name__ == "__main__":
clean_pipe_stream_final()

View File

@@ -0,0 +1,52 @@
import cv2
import numpy as np
import os
def clean_pipe_stream_gentle():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img = cv2.imread(src_path)
if img is None: return
# 1. Standard Separation (GrabCut) - keeps everything (Pipe, Stream, Walls, Banks)
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
h, w = img.shape[:2]
cv2.grabCut(img, mask, (10, 10, w-20, h-20), bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
mask_final = np.where((mask==2)|(mask==0),0,1).astype('uint8')
# Not doing any aggressive "Wall Removal" logic now, because it ate the banks.
# Instead, we rely on the fact that GrabCut should just remove the GRAY background.
# The walls will remain. To fit them better, we can manually crop the BOTTOM few pixels if needed.
# 2. Fix Drain Hole (Make Black if it's transparent)
# Actually, let's just make the hole Area Opaque regardless.
# Assuming drain is separate dark region? No, it's connected.
# Let's clean up small holes in the mask so the drain grate isn't see-through.
kernel = np.ones((5,5),np.uint8)
mask_final = cv2.morphologyEx(mask_final, cv2.MORPH_CLOSE, kernel)
# 3. Create Alpha
b, g, r = cv2.split(img)
alpha = mask_final * 255
img_rgba = cv2.merge([b, g, r, alpha])
# Crop
coords = cv2.findNonZero(alpha)
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
img_rgba = img_rgba[y:y+ch, x:x+cw]
# Save
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets/stream_pipe.png',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment/stream_pipe.png'
]
for t in targets:
cv2.imwrite(t, img_rgba)
print(f"Saved {t}")
if __name__ == "__main__":
clean_pipe_stream_gentle()

View File

@@ -0,0 +1,71 @@
import cv2
import numpy as np
import os
def clean_pipe_stream_aggressive():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img_bgr = cv2.imread(src_path)
if img_bgr is None:
print("Failed to load")
return
# Convert to HSV for better segmentation
hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
# The background is a "neutral gray".
# Gray means LOW Saturation.
# Light Gray means HIGH Value.
# Let's define "Background" as:
# Saturation < 20 (very gray)
# Value > 200 (very bright)
# But wait, the pipe is also gray! We risk deleting the pipe.
# Plan B: GrabCut.
# We initialize a mask where the center is "Probable Foreground" and edges are "Background".
mask = np.zeros(img_bgr.shape[:2], np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
h, w = img_bgr.shape[:2]
# Define rectangle: Cut off 10px from edges as "Definite Background"
# Everything else is "Probable Foreground"
rect = (10, 10, w-20, h-20)
print("Running GrabCut (this might take a second)...")
cv2.grabCut(img_bgr, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
# Mask values: 0=BG, 1=FG, 2=Prob BG, 3=Prob FG
# We take 1 and 3 as mask
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img_bgr = img_bgr * mask2[:,:,np.newaxis]
# Now create Alpha channel
b, g, r = cv2.split(img_bgr)
alpha = mask2 * 255
img_rgba = cv2.merge([b, g, r, alpha])
# Crop to content
coords = cv2.findNonZero(alpha)
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
print(f"Cropping to: {x},{y} {cw}x{ch}")
img_rgba = img_rgba[y:y+ch, x:x+cw]
# Save
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets/stream_pipe.png',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment/stream_pipe.png'
]
for t in targets:
cv2.imwrite(t, img_rgba)
print(f"Saved to {t}")
if __name__ == "__main__":
clean_pipe_stream_aggressive()

View File

@@ -0,0 +1,62 @@
import cv2
import numpy as np
import os
def clean_pipe_stream_v2():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img_bgr = cv2.imread(src_path) # Load as BGR (3 channels) first for floodFill
if img_bgr is None:
print("Failed")
return
# Create mask for floodFill (h+2, w+2)
h, w = img_bgr.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
# Flags: Fixed range, result in mask, fill with something (doesn't matter for mask-only)
# The mask will be updated with 1s where filled.
flood_flags = 4 | (255 << 8) | cv2.FLOODFILL_MASK_ONLY | cv2.FLOODFILL_FIXED_RANGE
# Seed points (Corners)
# Tolerance: +/- 15
lo_diff = (15, 15, 15)
up_diff = (15, 15, 15)
cv2.floodFill(img_bgr, mask, (0,0), 0, lo_diff, up_diff, flood_flags)
cv2.floodFill(img_bgr, mask, (w-1,0), 0, lo_diff, up_diff, flood_flags)
cv2.floodFill(img_bgr, mask, (0,h-1), 0, lo_diff, up_diff, flood_flags)
# Extract the mask corresponding to the image (remove border)
# The mask has 255 where BG was found.
alpha_mask = mask[1:-1, 1:-1]
# Invert mask: We want 255 (Opaque) where it is NOT background (0 in mask)
# Background (255 in mask) -> Should be 0 (Transparent)
# Foreground (0 in mask) -> Should be 255 (Opaque)
final_alpha = cv2.bitwise_not(alpha_mask)
# Create valid RGBA image
b, g, r = cv2.split(img_bgr)
img_rgba = cv2.merge([b, g, r, final_alpha])
# Crop
coords = cv2.findNonZero(final_alpha)
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
print(f"Cropping to: {x},{y} {cw}x{ch}")
img_rgba = img_rgba[y:y+ch, x:x+cw]
# Save
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets/stream_pipe.png',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment/stream_pipe.png'
]
for t in targets:
cv2.imwrite(t, img_rgba)
print(f"Saved to {t}")
if __name__ == "__main__":
clean_pipe_stream_v2()

View File

@@ -0,0 +1,80 @@
import cv2
import numpy as np
import os
import shutil
def deploy_hud_extras():
artifacts_dir = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b'
mapping = {
'ui_minimap_frame': 'minimap_frame.png',
'ui_weather_icons': 'weather_icons_sheet.png',
'ui_weather_widget_retry': 'weather_widget.png'
}
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/UI',
'/Users/davidkotnik/nova farma/main/assets'
]
for t in targets:
os.makedirs(t, exist_ok=True)
for key_pattern, dest_name in mapping.items():
# Find latest file
candidates = []
for f in os.listdir(artifacts_dir):
if key_pattern in f and f.endswith('.png'):
candidates.append(os.path.join(artifacts_dir, f))
if not candidates:
print(f"Skipping {key_pattern}")
continue
candidates.sort(key=os.path.getmtime, reverse=True)
found_path = candidates[0]
print(f"Processing {found_path}")
img = cv2.imread(found_path)
if img is None: continue
# BG Removal
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
target = np.array([255, 0, 255])
tol = 60
lower = np.clip(target - tol, 0, 255)
upper = np.clip(target + tol, 0, 255)
mask = cv2.inRange(img[:,:,:3], lower, upper)
img[mask > 0, 3] = 0
# Crop Content
coords = cv2.findNonZero(img[:,:,3])
if coords is not None:
x, y, w, h = cv2.boundingRect(coords)
img = img[y:y+h, x:x+w]
# Resize Logic
h, w = img.shape[:2]
new_w, new_h = w, h
if 'minimap' in dest_name and w > 256:
scale = 256 / w
new_w, new_h = int(w*scale), int(h*scale)
elif 'widget' in dest_name and w > 256:
scale = 256 / w
new_w, new_h = int(w*scale), int(h*scale)
elif 'icons' in dest_name and w > 512:
scale = 512 / w
new_w, new_h = int(w*scale), int(h*scale)
if new_w != w:
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
# Distribute
for t in targets:
final_path = os.path.join(t, dest_name)
cv2.imwrite(final_path, img)
print(f"Saved to {final_path}")
if __name__ == "__main__":
deploy_hud_extras()

View File

@@ -0,0 +1,81 @@
import cv2
import numpy as np
import os
import shutil
def deploy_inventory_ui():
artifacts_dir = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b'
mapping = {
'ui_hotbar_background': 'hotbar_background.png',
'ui_big_inventory_panel': 'inventory_panel.png'
}
# Destination in projects
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/UI',
'/Users/davidkotnik/nova farma/main/assets'
]
for t in targets:
os.makedirs(t, exist_ok=True)
for key_pattern, dest_name in mapping.items():
found_path = None
# Find latest file
candidates = []
for f in os.listdir(artifacts_dir):
if key_pattern in f and f.endswith('.png'):
candidates.append(os.path.join(artifacts_dir, f))
if not candidates:
print(f"Skipping {key_pattern}")
continue
candidates.sort(key=os.path.getmtime, reverse=True)
found_path = candidates[0]
print(f"Processing {found_path}")
img = cv2.imread(found_path)
if img is None: continue
# BG Removal
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
target = np.array([255, 0, 255])
tol = 60
lower = np.clip(target - tol, 0, 255)
upper = np.clip(target + tol, 0, 255)
mask = cv2.inRange(img[:,:,:3], lower, upper)
img[mask > 0, 3] = 0
# Crop
coords = cv2.findNonZero(img[:,:,3])
if coords is not None:
x, y, w, h = cv2.boundingRect(coords)
img = img[y:y+h, x:x+w]
# Resize logic
# Hotbar: Needs to be roughly wide enough for 9 slots. Maybe 600-800px wide.
# Panel: Maybe 500-600px square.
h, w = img.shape[:2]
new_w, new_h = w, h
if 'hotbar' in dest_name and w > 800:
scale = 800 / w
new_w, new_h = int(w*scale), int(h*scale)
elif 'panel' in dest_name and w > 600:
scale = 600 / w
new_w, new_h = int(w*scale), int(h*scale)
if new_w != w:
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
# Distribute
for t in targets:
final_path = os.path.join(t, dest_name)
cv2.imwrite(final_path, img)
print(f"Saved to {final_path}")
if __name__ == "__main__":
deploy_inventory_ui()

100
scripts/fix_minimap.py Normal file
View File

@@ -0,0 +1,100 @@
import cv2
import numpy as np
import os
import shutil
def fix_minimap_center():
# Paths
artifacts_dir = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b'
# Find latest minimap artifact
minimap_src = None
candidates = []
for f in os.listdir(artifacts_dir):
if 'ui_minimap_frame' in f and f.endswith('.png'):
candidates.append(os.path.join(artifacts_dir, f))
if not candidates:
print("Minimap artifact not found in brain.")
return
candidates.sort(key=os.path.getmtime, reverse=True)
minimap_src = candidates[0]
# Load image
print(f"Fixing {minimap_src}")
img = cv2.imread(minimap_src, cv2.IMREAD_UNCHANGED)
# If no alpha, add it
if img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# 1. Remove Magenta Background (as before)
target = np.array([255, 0, 255])
tol = 60
lower = np.clip(target - tol, 0, 255)
upper = np.clip(target + tol, 0, 255)
mask_bg = cv2.inRange(img[:,:,:3], lower, upper)
img[mask_bg > 0, 3] = 0
# 2. REMOVE CHECKERBOARD CENTER
# The checkerboard is usually gray/white pixels.
# Gray: (204, 204, 204), White: (255, 255, 255) or similar.
# We can detect pixels that are essentially grayscale (R~=G~=B) and high brightness.
# Convert to HSV to check saturation?
hsv = cv2.cvtColor(img[:,:,:3], cv2.COLOR_BGR2HSV)
s = hsv[:,:,1]
v = hsv[:,:,2]
# Checkerboard is low saturation (gray/white) and high value
# Define mask for "grayish/whiteish stuff"
# S < 20 (very low color)
# V > 150 (fairly bright)
mask_center = (s < 30) & (v > 150)
# But wait, the metal rim is also gray!
# We only want to remove the CENTER.
# Let's use a circular mask for the center hole.
h, w = img.shape[:2]
center_x, center_y = w // 2, h // 2
# Radius of the inner hole?
# Based on the image, the rim is maybe 15-20% of radius.
# Let's guess inner radius is about 75% of total width/2.
radius = int((w / 2) * 0.72)
# Create circular mask
circle_mask = np.zeros((h, w), dtype=np.uint8)
cv2.circle(circle_mask, (center_x, center_y), radius, 255, -1)
# Combine masks: Must be inside the circle AND be gray/checkerboard-like
# Actually, simpler: JUST CUT THE HOLE.
# If the user wants a transparent center, we can just clear everything inside the metal ring.
# The metal ring has content. The center is just checkerboard.
# Let's force alpha=0 for the inner circle.
# Refined approach:
# Everything inside the circle_mask becomes transparent.
img[circle_mask > 0, 3] = 0
# Resizing to standard size
target_w = 256
scale = target_w / w
new_h = int(h * scale)
img = cv2.resize(img, (target_w, new_h), interpolation=cv2.INTER_AREA)
# Save and Deploy
dest_name = 'minimap_frame.png'
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/UI',
'/Users/davidkotnik/nova farma/main/assets'
]
for t in targets:
final_path = os.path.join(t, dest_name)
cv2.imwrite(final_path, img)
print(f"Saved fixed minimap to {final_path}")
if __name__ == "__main__":
fix_minimap_center()

View File

@@ -0,0 +1,61 @@
import cv2
import numpy as np
import os
import shutil
def process_character_sheet():
# Find latest generated sheet
artifacts_dir = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b'
sheet_src = None
candidates = []
for f in os.listdir(artifacts_dir):
if 'kai_walk_sheet' in f and f.endswith('.png'):
candidates.append(os.path.join(artifacts_dir, f))
if not candidates:
print("Sheet not found.")
return
candidates.sort(key=os.path.getmtime, reverse=True)
sheet_src = candidates[0]
print(f"Processing {sheet_src}")
img = cv2.imread(sheet_src)
# Remove BG
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
target = np.array([255, 0, 255])
tol = 60
lower = np.clip(target - tol, 0, 255)
upper = np.clip(target + tol, 0, 255)
mask = cv2.inRange(img[:,:,:3], lower, upper)
img[mask > 0, 3] = 0
# Save info about dimensions
h, w = img.shape[:2]
print(f"Total Sheet Dimensions: {w}x{h}")
# 4x4 grid
frame_w = w // 4
frame_h = h // 4
print(f"Calculated Frame Size: {frame_w}x{frame_h}")
# Save
dest_name = 'kai_walk_sheet.png'
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets',
'/Users/davidkotnik/repos/novafarma/assets/characters',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Characters' # New location?
]
# Also ensure dir exists
if not os.path.exists('/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Characters'):
os.makedirs('/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Characters')
for t in targets:
if not os.path.exists(t): os.makedirs(t, exist_ok=True)
final_path = os.path.join(t, dest_name)
cv2.imwrite(final_path, img)
print(f"Saved to {final_path}")
if __name__ == "__main__":
process_character_sheet()

View File

@@ -0,0 +1,42 @@
import cv2
import numpy as np
import os
import shutil
def process_pipe_stream():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img = cv2.imread(src_path)
if img is None:
print("Failed to load image")
return
# BG Removal
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# Sample top-left for BG color (looks like light gray)
bg_color = img[0,0][:3]
print(f"BG Color: {bg_color}")
tol = 30
lower = np.clip(bg_color - tol, 0, 255)
upper = np.clip(bg_color + tol, 0, 255)
mask = cv2.inRange(img[:,:,:3], lower, upper)
img[mask > 0, 3] = 0
# Save
dest_name = 'stream_pipe.png'
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment'
]
for t in targets:
if not os.path.exists(t): os.makedirs(t, exist_ok=True)
final_path = os.path.join(t, dest_name)
cv2.imwrite(final_path, img)
print(f"Saved to {final_path}")
if __name__ == "__main__":
process_pipe_stream()

View File

@@ -0,0 +1,54 @@
import cv2
import numpy as np
import os
import shutil
def process_stream_image():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769595145566.jpg'
# 1. Load Image
print(f"Loading {src_path}")
img = cv2.imread(src_path)
if img is None:
print("Failed to load image")
return
# 2. Remove Background
# Convert to BGRA
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# The background is a very uniform light gray.
# Let's sample the corner (0,0)
bg_color = img[0,0][:3]
print(f"Detected BG Color: {bg_color}")
# Create mask range
tol = 30
lower = np.clip(bg_color - tol, 0, 255)
upper = np.clip(bg_color + tol, 0, 255)
mask = cv2.inRange(img[:,:,:3], lower, upper)
# Set alpha to 0 where mask is present
img[mask > 0, 3] = 0
# 3. Save to Game Assets
dest_path = '/Users/davidkotnik/repos/novafarma/main/assets/stream_winding.png'
cv2.imwrite(dest_path, img)
print(f"Saved processed asset to {dest_path}")
# Sync to other locations just in case
shutil.copy2(dest_path, '/Users/davidkotnik/nova farma/main/assets/stream_winding.png')
# 4. Save Reference (Try to copy original)
try:
ref_dir = '/Users/davidkotnik/repos/novafarma/main/assets/references'
if not os.path.exists(ref_dir):
os.makedirs(ref_dir)
shutil.copy2(src_path, os.path.join(ref_dir, 'umazan_potok_ref.jpg'))
print("Saved reference copy.")
except Exception as e:
print(f"Failed to save reference copy: {e}")
if __name__ == "__main__":
process_stream_image()

44
scripts/run_defold.sh Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
# Build + run Defold project from terminal (Cursor-friendly)
# Downloads bob.jar into tools/ on first run.
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BOB_DIR="$ROOT_DIR/tools/bob"
BOB_JAR="$BOB_DIR/bob.jar"
BUILD_DIR="$ROOT_DIR/build"
mkdir -p "$BOB_DIR" "$BUILD_DIR"
if [[ ! -f "$BOB_JAR" ]]; then
echo "bob.jar not found -> downloading..."
curl -L --fail -o "$BOB_JAR" "https://d.defold.com/bob/bob.jar"
fi
echo "Building (macOS bundle)..."
java -jar "$BOB_JAR" --root "$ROOT_DIR" resolve
java -jar "$BOB_JAR" --root "$ROOT_DIR" build
PLATFORM="x86_64-darwin"
if [[ "$(uname -m)" == "arm64" ]]; then
PLATFORM="arm64-darwin"
fi
java -jar "$BOB_JAR" --root "$ROOT_DIR" bundle --platform "$PLATFORM" --bundle-output "$BUILD_DIR"
APP_PATH="$(find "$BUILD_DIR" -maxdepth 2 -name "*.app" -print -quit)"
if [[ -z "${APP_PATH:-}" ]]; then
echo "No .app found in $BUILD_DIR (bundle step may have failed)."
exit 1
fi
BIN_PATH="$APP_PATH/Contents/MacOS/$(basename "$APP_PATH" .app)"
if [[ ! -x "$BIN_PATH" ]]; then
# Fallback: run the first executable in Contents/MacOS
BIN_PATH="$(find "$APP_PATH/Contents/MacOS" -maxdepth 1 -type f -perm -111 -print -quit || true)"
fi
echo "Running: $BIN_PATH"
"$BIN_PATH"

108
scripts/slice_stream.py Normal file
View File

@@ -0,0 +1,108 @@
import cv2
import numpy as np
import os
def slice_stream_assets():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img = cv2.imread(src_path)
if img is None: return
# 1. CLEAN BACKGROUND (GrabCut)
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
h, w = img.shape[:2]
cv2.grabCut(img, mask, (10, 10, w-20, h-20), bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
mask_final = np.where((mask==2)|(mask==0),0,1).astype('uint8')
# Apply Alpha
b, g, r = cv2.split(img)
alpha = mask_final * 255
img_rgba = cv2.merge([b, g, r, alpha])
# 2. CREATE 'HEAD' (Pipe Only)
# The pipe is roughly the top 40%? Let's crop visually based on the image structure.
# The pipe is top-right.
# Let's say we keep the top half as the "Start".
# And we take a middle slice as the "Segment".
# Finding the pipe:
# Based on the image, the pipe drain is at the top.
# Let's crop `head` from y=0 to y=0.45*h
cut_y = int(h * 0.45)
head_img = img_rgba[0:cut_y, :]
# Crop transparent borders from head
coords = cv2.findNonZero(head_img[:,:,3]) # Alpha
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
head_img = head_img[y:y+ch, x:x+cw]
# 3. CREATE 'SEGMENT' (Water Channel)
# We take a slice from the middle-bottom.
# Crop from y=0.45*h to y=0.85*h (skip very bottom tip?)
# Actually, let's take a nice chunk that can be tiled.
# The channel is diagonal. Tiling diagonal is hard without overlap.
# Let's just crop the rest of the stream as one big piece for now.
body_img = img_rgba[cut_y:, :]
# Crop transparent borders from body
coords = cv2.findNonZero(body_img[:,:,3])
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
body_img = body_img[y:y+ch, x:x+cw]
# 4. SOFTEN EDGES (To fix floating walls)
# Applied to both Head and Body.
# We want to fade out the BOTTOM edge of the mask, so the "wall" blends into the grass.
def soften_bottom_edge(image):
h, w = image.shape[:2]
# Create a gradient mask for the bottom 20 pixels
grad_h = 30
if h < grad_h: return image # Too small
# We modify the alpha channel
alpha = image[:,:,3]
# We need to detect where the "bottom" of the object is.
# Since it's diagonal, it's tricky.
# Simple hack: Erode the alpha slightly to sharpen the cut, then blur it?
# Or just blur the edges?
# Let's try blurring the alpha channel to soften the hard cut against the grass.
# Only on the edges.
# Get edge mask
edges = cv2.Canny(alpha, 100, 200)
# Dilate edges to get a rim
rim = cv2.dilate(edges, np.ones((5,5),np.uint8))
# Blur alpha
blurred_alpha = cv2.GaussianBlur(alpha, (7,7), 0)
# Apply blurred alpha where rim is
# image[:,:,3] = np.where(rim>0, blurred_alpha, alpha)
# Actually, let's just do a global soft outline.
image[:,:,3] = blurred_alpha
return image
# head_img = soften_bottom_edge(head_img)
# body_img = soften_bottom_edge(body_img)
# (Skipping blur for now, plain cut is cleaner if geometry is right)
# Save
base_dir = '/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment'
if not os.path.exists(base_dir): os.makedirs(base_dir, exist_ok=True)
cv2.imwrite(os.path.join(base_dir, 'stream_head.png'), head_img)
cv2.imwrite(os.path.join(base_dir, 'stream_body.png'), body_img)
print("Sliced stream into head and body.")
if __name__ == "__main__":
slice_stream_assets()

83
scripts/trim_walls.py Normal file
View File

@@ -0,0 +1,83 @@
import cv2
import numpy as np
import os
def trim_walls_manual():
src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg'
print(f"Loading {src_path}")
img = cv2.imread(src_path)
if img is None: return
# 1. Basic GrabCut to remove BG
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
h, w = img.shape[:2]
cv2.grabCut(img, mask, (10, 10, w-20, h-20), bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
mask_final = np.where((mask==2)|(mask==0),0,1).astype('uint8')
# 2. MANUAL TRIM
# The "Wall" problem is mainly on the bottom edges because of the isometric height.
# We can try to erode the mask from the bottom-left direction?
# Or simpler: Detect the DARK BROWN/BLACK pixels near the boundary and make them transparent.
# Convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Define "Wall Shadow" color
# Walls are very dark. Value < 40 or so.
is_dark = (hsv[:,:,2] < 50)
# And we only care about dark pixels that are currently part of the mask.
is_part_of_object = (mask_final == 1)
# We want to remove dark pixels ONLY if they are on the "outside" or extending downwards.
# Let's try to remove ALL "Very Dark" pixels that are connected to the mask boundary?
# No, that might remove internal details.
# Let's try aggressive erosion of dark areas.
# Create a mask of "Walls to Remove" = Dark pixels.
walls_mask = (is_dark & is_part_of_object).astype(np.uint8)
# Morphological Opening to remove noise (thin lines might be outlines, we want blocks)
walls_mask = cv2.morphologyEx(walls_mask, cv2.MORPH_OPEN, np.ones((5,5), np.uint8))
# Subtract walls from main mask
mask_trimmed = mask_final.copy()
mask_trimmed[walls_mask == 1] = 0
# Verify: Did we eat too much?
# The pipes drain hole is dark! We must protect the top-right quadrant.
# Let's define a safe zone (Top 40% of image height + Right 40% of width)
safe_y = int(h * 0.4)
# Actually, simpler: just don't touch the top-right area where the pipe is.
# Let's mask out the top-right from the removal process.
# Pipe bounding box approx: x > w*0.6, y < h*0.5
walls_mask[0:int(h*0.5), int(w*0.6):w] = 0
# Now re-apply subtraction
mask_trimmed = mask_final.copy()
mask_trimmed[walls_mask == 1] = 0
# 3. Output
b, g, r = cv2.split(img)
alpha = mask_trimmed * 255
img_rgba = cv2.merge([b, g, r, alpha])
# Crop
coords = cv2.findNonZero(alpha)
if coords is not None:
x, y, cw, ch = cv2.boundingRect(coords)
img_rgba = img_rgba[y:y+ch, x:x+cw]
targets = [
'/Users/davidkotnik/repos/novafarma/main/assets/stream_pipe.png',
'/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment/stream_pipe.png'
]
for t in targets:
cv2.imwrite(t, img_rgba)
print(f"Saved {t}")
if __name__ == "__main__":
trim_walls_manual()