diff --git a/.gitignore b/.gitignore index da83111..ac3e1da 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ keystore.properties # OS .DS_Store + +# Fonts fetched for icon generation (see brand/README.md) +brand/fonts/ diff --git a/README.md b/README.md index e967f93..2ec27cb 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,22 @@ The queue is crash-safe: `PageScribeApp.onCreate` reclaims photos orphaned in `work/` older than two minutes and re-arms the worker, and failed photos are retried up to `MAX_JOB_ATTEMPTS` before being parked in `failed/`. +## Icon and branding + +PageScribe belongs to the **UmbraTools** suite, so its launcher icon follows the +UmbraTools UX brand: a dark, luminous "umbra" identity built from interwoven, +glowing forms. The mark is an open **book** bound along its gutter by a glowing +**weave**, held inside a broken **reticle** ring — a midnight field +(`#0A0F1F`), Electric Cyan (`#00BFFF`) glow, a bronze (`#A67C52`) book, and Ice +Blue (`#CFE9FF`) ruled lines. + +The icon ships as a full adaptive icon (background + foreground + themed +`monochrome` layer) sized to the 66 dp safe circle, so circle, squircle and +rounded-square launcher masks all crop it cleanly, plus legacy bitmaps for older +launchers. The 1024 master lockup and Play Store icon live in `brand/`, together +with the deterministic generator and the design notes — see +[`brand/README.md`](brand/README.md). + ## Building ```bash diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 712651b..3b14342 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,8 @@ diff --git a/app/src/main/res/drawable/ic_launcher.xml b/app/src/main/res/drawable/ic_launcher.xml deleted file mode 100644 index f0af549..0000000 --- a/app/src/main/res/drawable/ic_launcher.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..cf79c3d --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..cf79c3d --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..91951d3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..47f6987 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..1cf78fc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..16e5fdd Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..33bd504 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..7c76d0b Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 0000000..8d9482d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4933a15 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..54169d7 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..6b5e39e Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..9874c3f Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000..4e43104 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..06a221c Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..3a65424 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..1e00715 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..06295c7 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..6a040e7 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2998874 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..b1739a1 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..67994c5 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..35c11d7 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..c69e5a1 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..b3e4fe3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..721720d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e201324 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/brand/README.md b/brand/README.md new file mode 100644 index 0000000..39f5fd7 --- /dev/null +++ b/brand/README.md @@ -0,0 +1,66 @@ +# PageScribe icon & brand + +PageScribe is part of the **UmbraTools** suite, so its icon follows the +UmbraTools UX brand: a dark, luminous "umbra" identity built from interwoven, +glowing forms. + +## The mark + +An open **book** whose gutter is bound by an interwoven, glowing **weave**, +held inside a broken **reticle** ring. + +| Element | Meaning | Colour | +| --- | --- | --- | +| Midnight field | the "umbra" depth the suite is named for | Midnight Blue `#0A0F1F` | +| Reticle ring (4 arcs) | capture / framing — PageScribe photographs spreads | Electric Cyan `#00BFFF` → Deep Teal `#007A8C` | +| Open book | the object being read | Bronze `#A67C52` | +| Ruled lines | the text a scribe transcribes (OCR) | Ice Blue `#CFE9FF` | +| Weave through the gutter | the suite's interwoven-knot motif; the energy that turns pages into text | Electric Cyan `#00BFFF` with a near-white filament core | + +The ring is drawn as four arcs with cut-outs on the diagonals, so the mark reads +as a scanner target rather than a plain medallion — that is PageScribe's +contextual twist on the shared UmbraTools emblem. + +Everything is **vertically symmetric**, and every gradient is purely vertical, +so the left and right halves of the book and weave are pixel-identical. The +emblem is sized to fit the **66 dp adaptive-icon safe circle**, so no launcher +mask (circle, squircle, rounded square) ever clips it. + +## Files + +``` +brand/ + pagescribe-icon-1024.png master lockup: mark + "PageScribe" wordmark + pagescribe-playstore-512.png Play Store listing icon (512) + pagescribe-mark-1024.png the mark on its rounded-square tile, no text + generate_icons.py produces every asset below, deterministically +``` + +The Android resources it writes: + +``` +app/src/main/res/ + mipmap-anydpi-v26/ic_launcher.xml adaptive icon (bg + fg + monochrome) + mipmap-anydpi-v26/ic_launcher_round.xml same, for round masks + mipmap-{m,h,xh,xxh,xxxh}dpi/ + ic_launcher.png legacy rounded-square bitmap + ic_launcher_round.png legacy circular bitmap + ic_launcher_foreground.png adaptive foreground layer + ic_launcher_background.png adaptive background layer + ic_launcher_monochrome.png themed-icon layer (weave cut as a groove) +``` + +## Regenerating + +```bash +pip install cairosvg pillow +python3 brand/generate_icons.py +``` + +Montserrat (SIL Open Font License) is needed for the wordmark. Put +`Montserrat-SemiBold.ttf` and `Montserrat-Light.ttf` in `brand/fonts/`, or point +`FONT_DIR` at a folder that has them, or install Montserrat system-wide. + +The generator is deterministic: re-running it reproduces every PNG byte for +byte, so a regeneration only shows up in `git diff` when the art actually +changes. diff --git a/brand/generate_icons.py b/brand/generate_icons.py new file mode 100644 index 0000000..67983e3 --- /dev/null +++ b/brand/generate_icons.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +"""Generate the PageScribe icon set in the UmbraTools brand style. + +Brand rules taken from the UmbraTools UX Brand Document: + * midnight blue #0A0F1F ground, electric cyan #00BFFF accents, + deep teal #007A8C gradients, bronze #A67C52 for the book, + ice blue #CFE9FF for text + * core motif: an interwoven knot/weave + * composition: a central emblem with a contextual element (here the book, + plus the ruled lines a scribe reads) + * rounded corners, symmetrical geometry, soft glow, gradients into darkness + * legible from 1024px down to launcher sizes + +Every gradient is **purely vertical** so the mirrored left and right halves of +the book and the weave come out identical. + +The art is authored as SVG (real gradients, gaussian bloom) and rasterised with +cairosvg; the wordmark is drawn with PIL so it always uses Montserrat. + +Requirements +------------ + pip install cairosvg pillow + Montserrat SemiBold + Light TTFs (SIL Open Font License), found via + $FONT_DIR, ./fonts, or a system install. + +Regenerate everything with: + + python3 brand/generate_icons.py +""" +from __future__ import annotations + +import math +import os +import sys +from pathlib import Path + +try: + import cairosvg +except ImportError: # pragma: no cover - developer convenience + sys.exit("cairosvg is required: pip install cairosvg") + +from PIL import Image, ImageChops, ImageDraw, ImageFont, ImageFilter + +# ---------------------------------------------------------------- brand ---- +BG = "#0A0F1F" +CYAN = "#00BFFF" +TEAL = "#007A8C" +BRONZE = "#A67C52" +ICE = "#CFE9FF" + +HERE = Path(__file__).resolve().parent + + +def find_font(name: str) -> Path: + """Locate a Montserrat TTF: explicit env, vendored, or system install.""" + candidates = [HERE / "fonts" / name, Path("/tmp/opencode/fonts") / name] + env = os.environ.get("FONT_DIR") + if env: + candidates.insert(0, Path(env) / name) + for c in candidates: + if c.is_file(): + return c + for root in ("/usr/share/fonts", "/usr/local/share/fonts", Path.home() / ".fonts"): + for hit in Path(root).rglob(name): + return hit + sys.exit(f"cannot find {name}; set FONT_DIR to the folder holding Montserrat TTFs") + + +FONT_SEMIBOLD = find_font("Montserrat-SemiBold.ttf") +FONT_LIGHT = find_font("Montserrat-Light.ttf") + +D = 1024.0 # design space is always 1024x1024 + +# ------------------------------------------------------------- geometry ---- +RING_R = 340.0 # ring centreline radius +RING_W = 17.0 # ring stroke width +RING_GAP = 22.0 # degrees cut away at each diagonal (reticle) + +BRAID_AMP = 76.0 # weave amplitude +BRAID_W = 64.0 # weave stroke width +BRAID_CORE = 20.0 # bright filament inside the weave +BRAID_Y0 = -318.0 # tips land on the ring's top/bottom, inside the +BRAID_Y1 = 318.0 # adaptive safe circle even with the round caps +BRAID_PERIOD = 318.0 # three crossings, symmetric about the centre + +SAFE_DP = 64.0 # keep the mark inside the 66dp adaptive safe circle +CANVAS_DP = 108.0 + + +def ring_scale(ring_px: float) -> float: + return (ring_px / 2.0) / RING_R + + +# --------------------------------------------------------------- shapes ---- +def leaf(sign: int) -> str: + """One page of the open book; sign = -1 (left) or +1 (right).""" + s = sign + return ( + f"M 0,-150 " + f"C {s * 60},-181 {s * 162},-183 {s * 233},-159 " + f"L {s * 241},-153 C {s * 249},-148 {s * 252},-140 {s * 252},-131 " + f"L {s * 252},110 C {s * 252},120 {s * 246},130 {s * 235},133 " + f"C {s * 166},153 {s * 62},151 0,116 Z" + ) + + +# (y, inner x, outer x) for the ruled lines on each page. +RULES = [(-96, 122, 226), (-34, 122, 226), (28, 122, 226), (86, 122, 182)] + + +def braid_points(sign: int, n: int = 360) -> list[tuple[float, float]]: + pts = [] + for i in range(n + 1): + y = BRAID_Y0 + (BRAID_Y1 - BRAID_Y0) * i / n + x = sign * BRAID_AMP * math.sin(2 * math.pi * (y - BRAID_Y0) / BRAID_PERIOD) + pts.append((x, y)) + return pts + + +def polyline(pts: list[tuple[float, float]]) -> str: + return (f"M {pts[0][0]:.2f},{pts[0][1]:.2f}" + + "".join(f" L {x:.2f},{y:.2f}" for x, y in pts[1:])) + + +def ring_arcs(r: float, gap_deg: float, n: int = 72) -> list[str]: + """Four arcs of a circle, the cut-outs centred on the diagonals.""" + arcs = [] + for centre in (0.0, 90.0, 180.0, 270.0): + a0 = math.radians(centre - (90.0 - gap_deg) / 2.0) + a1 = math.radians(centre + (90.0 - gap_deg) / 2.0) + pts = [(r * math.cos(a0 + (a1 - a0) * i / n), + r * math.sin(a0 + (a1 - a0) * i / n)) for i in range(n + 1)] + arcs.append(polyline(pts)) + return arcs + + +# ------------------------------------------------------------- emblem ------ +def emblem_defs(mono: bool = False) -> str: + if mono: + return "" + + return """ + + + + + + + + + + + + + + + + + + + + + + + +""" + + +def braid_pass(stroke: str, width: float, extra: str = "") -> str: + """Both strands with the middle crossing on top, so the weave reads over/under.""" + a = polyline(braid_points(1)) + b = polyline(braid_points(-1)) + common = f'fill="none" stroke="{stroke}" stroke-width="{width}" stroke-linecap="round" {extra}' + return (f'' + f'' + f'') + + +def emblem(cx: float, cy: float, ring_px: float) -> str: + """The mark: reticle, open book, and the weave running through its gutter.""" + s = ring_scale(ring_px) + arcs = "".join(f'' for d in ring_arcs(RING_R, RING_GAP)) + rules = [] + for y, xi, xo in RULES: + for sign in (-1, 1): + x = sign * xo if sign < 0 else xi + rules.append(f'') + + return f""" + + + + {arcs} + {braid_pass('#00BFFF', BRAID_W)} + + + + + + + + + + {''.join(rules)} + + {braid_pass('#00BFFF', BRAID_W)} + {braid_pass('url(#weave)', BRAID_W)} + {braid_pass('#EAFDFF', BRAID_CORE, 'opacity="0.9"')} + + {arcs} +""" + + +def background(rounded: float = 0.0, halo: bool = True) -> str: + r = f' rx="{rounded:.0f}"' if rounded else "" + glow = "" + if halo: + glow = ( + '' + '' + '' + '' + '' + '' + '' + '' + '' + f'' + f'' + ) + return f'{glow}' + + +def svg(body: str, mono: bool = False) -> str: + return (f'{emblem_defs(mono)}{body}') + + +def rasterise(markup: str, px: int, supersample: int = 4) -> Image.Image: + import io + big = px * supersample + png = cairosvg.svg2png(bytestring=markup.encode(), output_width=big, output_height=big) + return Image.open(io.BytesIO(png)).convert("RGBA").resize((px, px), Image.LANCZOS) + + +# --------------------------------------------------------------- compose ---- +def fit_font(path: Path, text: str, target_w: float) -> ImageFont.FreeTypeFont: + """Largest size at which `text` measures `target_w`.""" + probe = ImageFont.truetype(str(path), 100) + box = probe.getbbox(text) + return ImageFont.truetype(str(path), int(100 * target_w / (box[2] - box[0]))) + + +def draw_wordmark(img: Image.Image, scale: float) -> Image.Image: + """'PageScribe' in Montserrat SemiBold, 'UmbraTools.com' in Light below.""" + draw = ImageDraw.Draw(img) + mid = img.width / 2 + title = fit_font(FONT_SEMIBOLD, "PageScribe", 660 * scale) + sub = fit_font(FONT_LIGHT, "UmbraTools.com", 330 * scale) + + glow = Image.new("RGBA", img.size, (0, 0, 0, 0)) + ImageDraw.Draw(glow).text((mid, 872 * scale), "PageScribe", font=title, + fill=(0, 191, 255, 150), anchor="ms") + img.alpha_composite(glow.filter(ImageFilter.GaussianBlur(13 * scale))) + + draw.text((mid, 872 * scale), "PageScribe", font=title, fill=ICE, anchor="ms") + draw.text((mid, 948 * scale), "UmbraTools.com", font=sub, + fill=(0, 191, 255, 205), anchor="ms") + return img + + +def icon_png(px: int, rounded_frac: float = 0.0, ring_px: float = 690.0, + wordmark: bool = False) -> Image.Image: + radius = rounded_frac * D + body = background(radius) + body += emblem(D / 2, 420 if wordmark else D / 2, ring_px) + img = rasterise(svg(body), px) + if wordmark and px >= 512: + img = draw_wordmark(img, scale=px / 1024.0) + return img + + +def adaptive_layer(px: int, layer: str) -> Image.Image: + ring = SAFE_DP / CANVAS_DP * D + if layer == "background": + return rasterise(svg(background()), px) + if layer == "monochrome": + return monochrome_layer(px) + return rasterise(svg(emblem(D / 2, D / 2, ring)), px) + + +def monochrome_layer(px: int) -> Image.Image: + """Single-colour themed icon: the mark with the weave cut out as a groove. + + cairosvg's handling of SVG is unreliable, so the groove is cut with + PIL alpha arithmetic instead: draw the solid shapes, draw the braid, and + subtract the braid's coverage. + """ + s = ring_scale(SAFE_DP / CANVAS_DP * D) + arcs = "".join(f'' for d in ring_arcs(RING_R, RING_GAP)) + wrap = (f'{{}}') + + shapes = wrap.format( + f'' + f'{arcs}' + ) + braid = "".join( + f'' for sign in (1, -1) + ) + shape_img = rasterise(svg(shapes), px) + groove_img = rasterise(svg(wrap.format(braid)), px) + + r, g, b, alpha = shape_img.split() + alpha = ImageChops.subtract(alpha, groove_img.split()[3]) + return Image.merge("RGBA", (r, g, b, alpha)) + + +# ------------------------------------------------------------------ main ---- +RES = HERE.parent / "app/src/main/res" +BRANDBOOK = HERE +DENSITIES = {"mdpi": 1.0, "hdpi": 1.5, "xhdpi": 2.0, "xxhdpi": 3.0, "xxxhdpi": 4.0} + + +def main() -> None: + BRANDBOOK.mkdir(parents=True, exist_ok=True) + + icon_png(1024, ring_px=580, wordmark=True).save(BRANDBOOK / "pagescribe-icon-1024.png") + icon_png(512, ring_px=580, wordmark=True).save(BRANDBOOK / "pagescribe-playstore-512.png") + icon_png(1024, rounded_frac=0.229, ring_px=690).save(BRANDBOOK / "pagescribe-mark-1024.png") + + for name, frac in (("ic_launcher", 0.229), ("ic_launcher_round", 0.5)): + for dens, f in DENSITIES.items(): + out = RES / f"mipmap-{dens}" + out.mkdir(parents=True, exist_ok=True) + icon_png(int(round(48 * f)), rounded_frac=frac, ring_px=820).save(out / f"{name}.png") + + for dens, f in DENSITIES.items(): + out = RES / f"mipmap-{dens}" + for layer in ("background", "foreground", "monochrome"): + adaptive_layer(int(round(108 * f)), layer).save(out / f"ic_launcher_{layer}.png") + print("wrote icon assets") + + +if __name__ == "__main__": + main() diff --git a/brand/pagescribe-icon-1024.png b/brand/pagescribe-icon-1024.png new file mode 100644 index 0000000..e0a85a0 Binary files /dev/null and b/brand/pagescribe-icon-1024.png differ diff --git a/brand/pagescribe-mark-1024.png b/brand/pagescribe-mark-1024.png new file mode 100644 index 0000000..d7cf63e Binary files /dev/null and b/brand/pagescribe-mark-1024.png differ diff --git a/brand/pagescribe-playstore-512.png b/brand/pagescribe-playstore-512.png new file mode 100644 index 0000000..d35c2bc Binary files /dev/null and b/brand/pagescribe-playstore-512.png differ