fancycrop
Modified: February 14, 2026 7:58 PM Category: Coding Notes List: Projects Created: November 18, 2025 8:36 AM Master Type: Notes Hide: No Starred: No Status: Unassigned
#!/usr/bin/env python3
# FancyCrop.py â True minimal, background-agnostic, lossless cropper
# Fully PSD/TIFF-safe. Preserves EXIF/ICC/IPTC. Supports multicore.
#
# Logic:
# - flatten (lossless)
# - trim with fuzz sequence
# - measure area reduction only
# - choose the trial with MAX area removed
# - classify confidence purely by area removed
#
# Confidence rules:
# area_removed âĨ 5% â high (overwrite original)
# area_removed âĨ 1% â mid (overwrite original)
# else â low (move to Cropped/)
#
# Now patched with:
# â secondary tighter trim
# â tunable extra-fuzz + extra-shave
# â museum_safe / full_crop toggle
# â boundary-protector (only for full_crop)
# â no other logic changes
import os
import sys
import subprocess
import tempfile
from datetime import datetime
import shutil
from multiprocessing import Pool, cpu_count
from PIL import Image
# ============================================================
# CONFIGURATION
# ============================================================
MODE = "full_crop" # "museum_safe" or "full_crop"
FUZZ_SEQUENCE = ["20%", "30%", "35%", "40%"]
EXTENT_PCT = "120%"
SHAVE_PCT = "12%"
HIGH_CONF_AREA = 0.05
MID_CONF_AREA = 0.01
CROPPED_DIRNAME = "Cropped"
CACHE_DIR = os.path.join(tempfile.gettempdir(), "FancyCrop")
LOG_FILE = os.path.join(tempfile.gettempdir(), "FancyCrop.log")
VALID_EXTS = {".psd", ".tif", ".tiff"}
# Tighter trim pass
EXTRA_FUZZ = "6%"
EXTRA_SHAVE = "2%"
# Full-crop boundary protector (raw pixel test)
BOUNDARY_PROTECT_MIN_LUMA = 20
BOUNDARY_PROTECT_SCAN_PX = 40 # only checks first 40px in (L/R/T/B)
# ============================================================
# LOGGING
# ============================================================
def log(msg):
stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{stamp}] {msg}"
print(line)
with open(LOG_FILE, "a") as f:
f.write(line + "\n")
# ============================================================
# IM HELPERS
# ============================================================
def run(cmd):
return subprocess.run(
cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=True
).stdout.decode("utf-8","replace")
def have(cmd):
return subprocess.call([ "/usr/bin/which", cmd ],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL ) == 0
def dims(path):
try:
out = run(["identify","-format","%[fx:w] %[fx:h]",f"{path}[0]"])
w,h = out.strip().split()
return int(float(w)), int(float(h))
except:
return 0,0
# ============================================================
# FLATTEN + TRIM
# ============================================================
def flatten_to_single_layer(src, dst):
log(f"đĒ Flattening {src}")
run([
"magick", src,
"-auto-orient",
"-flatten",
"-alpha","off",
"-define","tiff:preserve-tags=exif,icc,iptc",
dst
])
def trim_once(src, dst, fuzz):
log(f"âī¸ fuzz={fuzz}")
run([
"magick", src,
"-gravity","center","-extent",EXTENT_PCT,
"-shave",SHAVE_PCT,
"-fuzz",fuzz,
"-trim","+repage",
"-define","tiff:preserve-tags=exif,icc,iptc",
dst
])
# ============================================================
# BOUNDARY PROTECTOR â ONLY FOR full_crop
# ============================================================
def has_edge_information(path):
"""
Detects *non-white / non-empty* pixels in the first ~40px
of each side. This protects acetate text, notch codes,
edge numbering, etc.
"""
try:
im = Image.open(path).convert("RGB")
w,h = im.size
px = im.load()
# luminance helper
def L(p): return 0.299*p[0] + 0.587*p[1] + 0.114*p[2]
# scan strips along L/R/T/B
for x in range(0, min(BOUNDARY_PROTECT_SCAN_PX, w)):
for y in range(h):
if L(px[x,y]) < BOUNDARY_PROTECT_MIN_LUMA:
return True
for x in range(w - min(BOUNDARY_PROTECT_SCAN_PX,w), w):
for y in range(h):
if L(px[x,y]) < BOUNDARY_PROTECT_MIN_LUMA:
return True
for y in range(0, min(BOUNDARY_PROTECT_SCAN_PX, h)):
for x in range(w):
if L(px[x,y]) < BOUNDARY_PROTECT_MIN_LUMA:
return True
for y in range(h - min(BOUNDARY_PROTECT_SCAN_PX,h), h):
for x in range(w):
if L(px[x,y]) < BOUNDARY_PROTECT_MIN_LUMA:
return True
return False
except:
return False
# ============================================================
# MAIN CROPPING LOGIC
# ============================================================
def process_file(path):
ext = os.path.splitext(path)[1].lower()
if ext not in VALID_EXTS: return
log(f"đ Processing: {path}")
flat = path + ".flat" + ext
trial = path + ".trim" + ext
try:
# ---------------- FLATTEN ----------------
flatten_to_single_layer(path, flat)
w0,h0 = dims(flat)
if not w0 or not h0:
log("đĢ unreadable dims")
return
orig_area = float(w0*h0)
best_trial = None
best_area_reduction = 0.0
# ---------------- PRIMARY TRIM LOOP ----------------
for fuzz in FUZZ_SEQUENCE:
trim_once(flat, trial, fuzz)
w1,h1 = dims(trial)
if not w1 or not h1: continue
ar = 1.0 - (float(w1*h1)/orig_area)
log(f" fuzz={fuzz} area_reduction={ar:.4f}")
if ar > best_area_reduction:
if best_trial and os.path.exists(best_trial):
os.remove(best_trial)
best_area_reduction = ar
best_trial = path + f".best_{fuzz.replace('%','p')}" + ext
if os.path.exists(best_trial):
os.remove(best_trial)
os.rename(trial, best_trial)
else:
if os.path.exists(trial):
os.remove(trial)
if not best_trial:
log("đĢ No successful trim.")
return
# ---------------- CONFIDENCE ----------------
ar = best_area_reduction
log(f" Final area reduction = {ar:.4f}")
if ar >= HIGH_CONF_AREA:
conf, action = "HIGH", "overwrite"
elif ar >= MID_CONF_AREA:
conf, action = "MID", "overwrite"
else:
conf, action = "LOW", "cropped_dir"
log(f" â final confidence = {conf}")
# ---------------- SECONDARY TIGHT TRIM ----------------
tighter = path + ".tighter" + ext
if os.path.exists(tighter): os.remove(tighter)
run([
"magick", best_trial,
"-fuzz",EXTRA_FUZZ,
"-shave",EXTRA_SHAVE,
"-trim","+repage",
"-define","tiff:preserve-tags=exif,icc,iptc",
tighter
])
os.remove(best_trial)
best_trial = tighter
# ---------------- FULL_CROP MODE SAFETY ----------------
if MODE == "full_crop":
if has_edge_information(best_trial):
log("â ī¸ full_crop blocked: edge information detected â reverting to museum_safe")
MODE = "museum_safe"
# ---------------- APPLY FINAL DECISION ----------------
if action == "overwrite":
tmp = path + ".final" + ext
if os.path.exists(tmp): os.remove(tmp)
os.rename(best_trial, tmp)
os.replace(tmp, path)
log(f"đž OVERWROTE original ({conf})")
else:
out_dir = os.path.join(os.path.dirname(path), CROPPED_DIRNAME)
os.makedirs(out_dir, exist_ok=True)
out_path = os.path.join(out_dir, os.path.basename(path))
if os.path.exists(out_path): os.remove(out_path)
os.rename(best_trial, out_path)
log(f"â ī¸ LOW CONF â moved to {out_path}")
except Exception as e:
log(f"â Error: {e}")
finally:
for p in (flat, trial):
if os.path.exists(p):
try: os.remove(p)
except: pass
# ============================================================
# WALK + MULTICORE
# ============================================================
def walk_files(root):
for dp, _, files in os.walk(root):
for name in files:
if os.path.splitext(name.lower())[1] in VALID_EXTS:
yield os.path.join(dp, name)
def main():
if not have("magick"):
print("â ImageMagick not found", file=sys.stderr)
sys.exit(1)
if os.path.exists(CACHE_DIR):
shutil.rmtree(CACHE_DIR)
os.makedirs(CACHE_DIR, exist_ok=True)
root = sys.argv[1] if len(sys.argv)>1 else "."
files = list(walk_files(root))
if not files:
log("âšī¸ No PSD/TIFF files found.")
return
workers = min(max(cpu_count()//2,1),4)
log(f"âšī¸ Using {workers} workers")
try:
if workers==1:
for f in files: process_file(f)
else:
with Pool(workers) as pool:
pool.map(process_file,files)
finally:
shutil.rmtree(CACHE_DIR, ignore_errors=True)
if __name__=="__main__":
main()