"""Generate icon variants from the sandcage source image.""" from pathlib import Path from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parent.parent SRC = ROOT / ".workpad" / "assets" / "sandcage.png" OUT = ROOT / "local" def square_crop(src: Path, size: int = 1024) -> Image.Image: img = Image.open(src).convert("RGBA") w, h = img.size side = min(w, h) left = (w - side) // 2 top = (h - side) // 2 img = img.crop((left, top, left + side, top + side)) return img.resize((size, size), Image.LANCZOS) def invert_rgb(img: Image.Image) -> Image.Image: from PIL import ImageChops r, g, b, a = img.split() return Image.merge("RGBA", (ImageChops.invert(r), ImageChops.invert(g), ImageChops.invert(b), a)) def lighten_dune_interior(img: Image.Image, grey: int = 80) -> Image.Image: img = img.copy() draw = ImageDraw.Draw(img) fill = (grey, grey, grey, 255) # tips found at (56,567) and (973,567), dune bottom at y=652 # draw a filled arc (pieslice) from tip to tip curving below to seal the interior # pieslice bbox: center the ellipse so it passes through both tips cx = (56 + 973) // 2 half_w = (973 - 56) // 2 arc_depth = 120 bbox = (cx - half_w, 567 - arc_depth, cx + half_w, 567 + arc_depth) # draw just the bottom arc in grey to create the barrier draw.arc(bbox, 0, 180, fill=fill, width=4) # flood fill the enclosed black region ImageDraw.floodfill(img, (300, 620), fill, thresh=60) return img def apply_vertical_bars(img: Image.Image, bar_width: int = 48, gap_width: int = 48, opacity: float = 0.5) -> Image.Image: img = img.copy() r, g, b, a = img.split() bar_mask = Image.new("L", img.size, 255) draw = ImageDraw.Draw(bar_mask) bar_alpha = int(255 * opacity) x = 0 while x < img.size[0]: draw.rectangle((x, 0, x + bar_width - 1, img.size[1]), fill=bar_alpha) x += bar_width + gap_width from PIL import ImageChops a = ImageChops.multiply(a, bar_mask) return Image.merge("RGBA", (r, g, b, a)) def apply_circle_mask(img: Image.Image, size: int = 1024) -> Image.Image: # circle passes through dune tips (56,567) and (973,567) # center at midpoint, radius = half the tip distance cx = (56 + 973) // 2 # 514 cy = 567 r = (973 - 56) // 2 # 458 # crop to bounding box of this circle, then resize to output size left = max(cx - r, 0) top = max(cy - r, 0) right = min(cx + r, img.size[0]) bottom = min(cy + r, img.size[1]) cropped = img.crop((left, top, right, bottom)) cropped = cropped.resize((size, size), Image.LANCZOS) # apply circular mask mask = Image.new("L", (size, size), 0) draw = ImageDraw.Draw(mask) draw.ellipse((0, 0, size, size), fill=255) result = Image.new("RGBA", (size, size), (0, 0, 0, 0)) result.paste(cropped, (0, 0), mask) return result def save(img: Image.Image, path: Path) -> Path: img.save(path) print(f"Wrote {path}") return path if __name__ == "__main__": OUT.mkdir(parents=True, exist_ok=True) base = square_crop(SRC) inverted = invert_rgb(base) save(apply_circle_mask(base), OUT / "sandcage-round.png") save(apply_circle_mask(inverted), OUT / "sandcage-round-inverted.png") inverted_grey = lighten_dune_interior(inverted) save(apply_circle_mask(inverted_grey), OUT / "sandcage-round-inverted-grey.png") save(apply_circle_mask(apply_vertical_bars(base)), OUT / "sandcage-round-bars.png") save(apply_circle_mask(apply_vertical_bars(inverted)), OUT / "sandcage-round-inverted-bars.png") save(apply_circle_mask(apply_vertical_bars(inverted_grey)), OUT / "sandcage-round-inverted-grey-bars.png")