"""Compose the rendered mosswart head into an app-icon badge.""" import math, os import numpy as np from PIL import Image, ImageDraw, ImageFilter, ImageChops HERE = os.path.dirname(os.path.abspath(__file__)) def rounded_mask(size, radius_frac=0.225, ss=4): W = size * ss m = Image.new("L", (W, W), 0) d = ImageDraw.Draw(m) d.rounded_rectangle([0, 0, W - 1, W - 1], radius=int(W * radius_frac), fill=255) return m.resize((size, size), Image.LANCZOS) def circle_mask(size, ss=4): W = size * ss m = Image.new("L", (W, W), 0) ImageDraw.Draw(m).ellipse([0, 0, W - 1, W - 1], fill=255) return m.resize((size, size), Image.LANCZOS) def field(size, inner="#2C3A1A", outer="#0B1006", cx=0.5, cy=0.36): """Radial swamp-light gradient behind the head.""" y, x = np.mgrid[0:size, 0:size].astype(np.float32) / max(size - 1, 1) r = np.sqrt(((x - cx) * 1.05) ** 2 + ((y - cy) * 1.05) ** 2) / 0.78 r = np.clip(r, 0, 1) ** 1.15 ci = np.array([int(inner[i:i + 2], 16) for i in (1, 3, 5)], dtype=np.float32) co = np.array([int(outer[i:i + 2], 16) for i in (1, 3, 5)], dtype=np.float32) img = ci[None, None, :] * (1 - r[..., None]) + co[None, None, :] * r[..., None] return Image.fromarray(img.astype(np.uint8), "RGB").convert("RGBA") def drop_shadow(subject, blur=26, dy=18, opacity=0.55, spread=1.03): a = subject.split()[3] w, h = a.size s = a.resize((int(w * spread), int(h * spread)), Image.LANCZOS) canvas = Image.new("L", (w, h), 0) canvas.paste(s, ((w - s.width) // 2, (h - s.height) // 2 + dy)) canvas = canvas.filter(ImageFilter.GaussianBlur(blur)) canvas = canvas.point(lambda v: int(v * opacity)) sh = Image.new("RGBA", (w, h), (0, 0, 0, 0)) sh.putalpha(canvas) return sh def fit_subject(sub, size, occupancy=0.86, offset_y=0.0): """Trim to content, scale so the longest side hits `occupancy`, centre it.""" bbox = sub.split()[3].getbbox() sub = sub.crop(bbox) scale = (size * occupancy) / max(sub.width, sub.height) sub = sub.resize((max(1, int(sub.width * scale)), max(1, int(sub.height * scale))), Image.LANCZOS) out = Image.new("RGBA", (size, size), (0, 0, 0, 0)) out.paste(sub, ((size - sub.width) // 2, int((size - sub.height) / 2 + size * offset_y)), sub) return out def badge(subject, size=1024, shape="rounded", occupancy=0.84, offset_y=0.02, inner="#2C3A1A", outer="#0B1006", shadow=True, rim=True): bg = field(size, inner, outer) if rim: # faint inner rim so the badge has an edge in dark UI ring = Image.new("RGBA", (size, size), (0, 0, 0, 0)) d = ImageDraw.Draw(ring) d.rounded_rectangle([2, 2, size - 3, size - 3], radius=int(size * 0.225) if shape == "rounded" else size // 2, outline=(150, 172, 92, 46), width=max(2, size // 220)) bg = Image.alpha_composite(bg, ring) sub = fit_subject(subject, size, occupancy, offset_y) if shadow: bg = Image.alpha_composite(bg, drop_shadow(sub, blur=int(size * 0.028), dy=int(size * 0.018))) out = Image.alpha_composite(bg, sub) mask = rounded_mask(size) if shape == "rounded" else circle_mask(size) out.putalpha(ImageChops.multiply(out.split()[3], mask)) return out def free(subject, size=1024, occupancy=0.94): """No field -- the head alone on transparency.""" return fit_subject(subject, size, occupancy) def contact(images, labels, cell=280, pad=14, bg=(22, 24, 20)): sheet = Image.new("RGB", (len(images) * cell, cell + 24), bg) d = ImageDraw.Draw(sheet) for i, (im, lb) in enumerate(zip(images, labels)): t = im.copy().resize((cell - 2 * pad, cell - 2 * pad), Image.LANCZOS) base = Image.new("RGBA", t.size, bg + (255,)) sheet.paste(Image.alpha_composite(base, t).convert("RGB"), (i * cell + pad, pad)) d.text((i * cell + pad, cell + 4), lb, fill=(200, 210, 170)) return sheet def size_strip(icon, sizes=(128, 64, 48, 32, 24, 16), bg=(22, 24, 20)): W = sum(s + 18 for s in sizes) + 20 H = max(sizes) + 34 sheet = Image.new("RGB", (W, H), bg) d = ImageDraw.Draw(sheet) x = 12 for s in sizes: t = icon.resize((s, s), Image.LANCZOS) base = Image.new("RGBA", t.size, bg + (255,)) sheet.paste(Image.alpha_composite(base, t).convert("RGB"), (x, 10 + (max(sizes) - s))) d.text((x, H - 18), "%d" % s, fill=(200, 210, 170)) x += s + 18 return sheet