🥇 export from upstream (ce9e2c1)
This commit is contained in:
+3
-1
@@ -148,7 +148,9 @@ def run_export(*, repo_root: Path, cfg: ExportConfig,
|
||||
diff = _run(["git", "diff", "--cached", "--quiet"],
|
||||
cwd=worktree, check=False)
|
||||
if diff.returncode != 0:
|
||||
_run(["git", "-c", "user.email=export@sandcage", "-c", "user.name=sandcage-export",
|
||||
git_name = _run(["git", "config", "user.name"], cwd=repo_root).stdout.strip()
|
||||
git_email = _run(["git", "config", "user.email"], cwd=repo_root).stdout.strip()
|
||||
_run(["git", "-c", f"user.email={git_email}", "-c", f"user.name={git_name}",
|
||||
"commit", "-m",
|
||||
f"\U0001f947 export from upstream ({source_sha})"],
|
||||
cwd=worktree)
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user