"""Build Datewise brand assets (Shopify sizes) from the generated source images. usage: python -I build_brand.py """ import os import shutil import sys import numpy as np from PIL import Image, ImageDraw, ImageFilter, ImageFont SRC, SHOT, OUT, PUB = sys.argv[1:5] TEAL = (13, 107, 93) # #0D6B5D TEAL_DEEP = (4, 90, 81) # background of the reversed logo SAGE = (232, 242, 237) # #E8F2ED OFF_WHITE = (250, 251, 249) # #FAFBF9 FONT = os.path.expanduser(r"~/AppData/Local/Microsoft/Windows/Fonts/Inter-VariableFont_slnt,wght.ttf") for d in ("source", "logo", "shopify", "web"): os.makedirs(os.path.join(OUT, d), exist_ok=True) os.makedirs(PUB, exist_ok=True) src = lambda n: Image.open(os.path.join(SRC, f"{n}.png")) out = lambda *p: os.path.join(OUT, *p) # ---- originals, renamed ------------------------------------------------------------- for n, name in {1: "brand-board", 2: "app-icon-rounded-preview", 3: "logo-reversed-on-teal", 4: "logo-mono", 5: "symbol", 6: "logo"}.items(): shutil.copy(os.path.join(SRC, f"{n}.png"), out("source", f"{name}.png")) def trim(im, pad=0): box = im.getchannel("A").point(lambda v: 255 if v > 8 else 0).getbbox() im = im.crop(box) if pad: c = Image.new("RGBA", (im.width + 2 * pad, im.height + 2 * pad), (0, 0, 0, 0)) c.paste(im, (pad, pad)) im = c return im # ---- transparent logos ---------------------------------------------------------------- logo = trim(src(6).convert("RGBA"), 24) logo.save(out("logo", "datewise-logo.png")) trim(src(4).convert("RGBA"), 24).save(out("logo", "datewise-logo-mono.png")) symbol = trim(src(5).convert("RGBA")) side = max(symbol.size) + 48 sq = Image.new("RGBA", (side, side), (0, 0, 0, 0)) sq.paste(symbol, ((side - symbol.width) // 2, (side - symbol.height) // 2)) sq.save(out("logo", "datewise-symbol.png")) # White logo for dark backgrounds: alpha from distance to the teal background, colour from the source. rev = np.asarray(src(3).convert("RGB")).astype(np.float32) bg = np.array(TEAL_DEEP, np.float32) dist = np.sqrt(((rev - bg) ** 2).sum(-1)) alpha = np.clip(dist / np.sqrt(((np.array([255, 255, 255]) - bg) ** 2).sum()) * 255 * 1.15, 0, 255).astype(np.uint8) white = np.dstack([np.full(alpha.shape, 255, np.uint8)] * 3 + [alpha]) trim(Image.fromarray(white, "RGBA"), 24).save(out("logo", "datewise-logo-white.png")) # ---- full-bleed app icon (1200x1200, no rounded corners, no padding) ------------------ icon = src(2).convert("RGB") a = np.asarray(icon).astype(np.float32) x0, y0, x1, y1 = 185, 185, 1070, 1070 # the rounded square crop = a[y0:y1, x0:x1] h, w, _ = crop.shape yy, xx = np.mgrid[0:h, 0:w] # Fit the teal gradient (quadratic in x, y) on pixels that are clearly teal background. teal_mask = (crop[..., 1] > 60) & (crop[..., 1] < 125) & (crop[..., 0] < 25) & (abs(crop[..., 1] - crop[..., 2]) < 20) content = np.zeros((h, w), bool) content[340 - y0:885 - y0, 330 - x0:1005 - x0] = True fit = teal_mask & ~content X = np.stack([np.ones_like(xx), xx, yy, xx * xx, yy * yy, xx * yy], -1).astype(np.float64) / np.array([1, w, h, w * w, h * h, w * h]) coef = [np.linalg.lstsq(X[fit], crop[..., c][fit], rcond=None)[0] for c in range(3)] model = np.stack([X @ coef[c] for c in range(3)], -1) # Keep the original artwork only inside the content box; everything else is the smooth gradient. dev = np.sqrt(((crop - model) ** 2).sum(-1)) m = np.where(content, np.clip((dev - 6) / 18, 0, 1), 0).astype(np.float32) m = np.asarray(Image.fromarray((m * 255).astype(np.uint8)).filter(ImageFilter.MaxFilter(3)).filter(ImageFilter.GaussianBlur(1))).astype(np.float32) / 255 full = model * (1 - m[..., None]) + crop * m[..., None] app_icon = Image.fromarray(np.clip(full, 0, 255).astype(np.uint8), "RGB").resize((1200, 1200), Image.LANCZOS) app_icon.save(out("shopify", "app-icon-1200x1200.png"), optimize=True) # ---- web icons --------------------------------------------------------------------------- app_icon.resize((180, 180), Image.LANCZOS).save(out("web", "apple-touch-icon-180.png")) app_icon.resize((512, 512), Image.LANCZOS).save(out("web", "icon-512.png")) fav = app_icon.resize((256, 256), Image.LANCZOS) fav.save(out("web", "favicon.ico"), sizes=[(16, 16), (32, 32), (48, 48)]) app_icon.resize((32, 32), Image.LANCZOS).save(out("web", "favicon-32.png")) for f in ("favicon.ico", "apple-touch-icon-180.png"): shutil.copy(out("web", f), os.path.join(PUB, f)) logo.save(os.path.join(PUB, "datewise-logo.png")) # ---- feature image (1600x900): logo + one line + the real cart widget ------------------ def font(size, weight): f = ImageFont.truetype(FONT, size) try: f.set_variation_by_axes([weight, 0] if len(f.get_variation_axes()) == 2 else [weight]) except Exception: pass return f W, H = 1600, 900 feat = Image.new("RGB", (W, H), TEAL_DEEP) d = ImageDraw.Draw(feat) # Soft lighter wash top-left, echoing the icon's gradient. wash = Image.new("L", (W, H), 0) ImageDraw.Draw(wash).ellipse((-500, -600, 1000, 700), fill=70) wash = wash.filter(ImageFilter.GaussianBlur(160)) feat = Image.composite(Image.new("RGB", (W, H), (20, 125, 110)), feat, wash) d = ImageDraw.Draw(feat) white_logo = Image.open(out("logo", "datewise-logo-white.png")) lw = 560 white_logo = white_logo.resize((lw, round(white_logo.height * lw / white_logo.width)), Image.LANCZOS) feat.paste(white_logo, (96, 300), white_logo) d.text((120, 300 + white_logo.height + 28), "Customers choose their delivery\nor pickup time in the cart.", font=font(46, 600), fill=(255, 255, 255), spacing=14) d.text((120, 300 + white_logo.height + 172), "Store pickup · Local delivery · Shipping", font=font(26, 450), fill=SAGE) shot = Image.open(SHOT).convert("RGB") panel = shot.crop((16, 228, 504, 1112)) # the scheduler panel in the e2e cart screenshot ph = 780 panel = panel.resize((round(panel.width * ph / panel.height), ph), Image.LANCZOS) card = Image.new("RGB", (panel.width + 48, panel.height + 48), (255, 255, 255)) card.paste(panel, (24, 24)) mask = Image.new("L", card.size, 0) ImageDraw.Draw(mask).rounded_rectangle((0, 0, card.width - 1, card.height - 1), 28, fill=255) cx, cy = W - card.width - 110, (H - card.height) // 2 shadow = Image.new("L", (W, H), 0) ImageDraw.Draw(shadow).rounded_rectangle((cx + 8, cy + 24, cx + card.width + 8, cy + card.height + 24), 28, fill=110) shadow = shadow.filter(ImageFilter.GaussianBlur(28)) feat = Image.composite(Image.new("RGB", (W, H), (2, 40, 36)), feat, shadow) feat.paste(card, (cx, cy), mask) feat.save(out("shopify", "feature-image-1600x900.png"), optimize=True) for root, _, files in os.walk(OUT): for f in sorted(files): p = os.path.join(root, f) try: print(os.path.relpath(p, OUT), Image.open(p).size, f"{os.path.getsize(p) // 1024} KB") except Exception: print(os.path.relpath(p, OUT))