4a3e12c85a
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Failing after 2m10s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Failing after 0s
CI / deploy (pull_request) Has been skipped
The adaptive foreground's asterisk grazed the round mask's safe zone. Scale the foreground mark 0.75 about the icon centre (25% smaller, the letter-to-star arrangement unchanged) so it sits well inside the 61% safe zone. Independently, the legacy square ic_launcher.png now renders the full-bleed master instead of the safe-zone composite, so old Android (< API 26) shows a large mark rather than a shrunk one; the legacy round icon keeps the safe-zone composite (a round mask would clip the master's corner star). Regenerate the layer set, Android res and the brandbook foreground previews; update ICON_BRANDBOOK.md / ICONS.md.
54 lines
3.3 KiB
JavaScript
54 lines
3.3 KiB
JavaScript
'use strict';
|
|
// Generate the Android launcher resources from the committed layers, at every
|
|
// density, with NO inset (our layers already fill the 108 dp canvas) and a
|
|
// monochrome (themed) layer. Run build-set.mjs first (it writes the layer PNGs).
|
|
//
|
|
// npm i opentype.js # (only build-icon/build-set need it; this reads PNGs)
|
|
// node build-set.mjs && node build-android-res.mjs
|
|
//
|
|
// Writes ui/android/app/src/main/res/mipmap-*/ic_launcher{,_round,_foreground,
|
|
// _background,_monochrome}.png. The two mipmap-anydpi-v26/*.xml are committed by hand.
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
|
|
const DIR = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = join(DIR, '..', '..', '..');
|
|
const RES = join(ROOT, 'ui', 'android', 'app', 'src', 'main', 'res');
|
|
const uri = p => 'data:image/png;base64,' + readFileSync(p).toString('base64');
|
|
const BG = uri(join(ROOT, 'ui', 'assets', 'icon-background.png'));
|
|
const FG = uri(join(ROOT, 'ui', 'assets', 'icon-foreground.png'));
|
|
const MONO = uri(join(DIR, 'android-monochrome.png'));
|
|
// The full-bleed master (square tile + master mark) — the legacy SQUARE launcher (API < 26)
|
|
// uses it so old Android shows a large mark rather than the safe-zone foreground shrunk into
|
|
// the middle. The round legacy icon keeps the safe-zone composite (a round mask would clip
|
|
// the master's corner ✻).
|
|
const MASTER = uri(join(ROOT, 'ui', 'assets', 'icon.png'));
|
|
|
|
// density: [adaptive-layer px (108 dp), legacy launcher px (48 dp)]
|
|
const D = { ldpi: [81, 36], mdpi: [108, 48], hdpi: [162, 72], xhdpi: [216, 96], xxhdpi: [324, 144], xxxhdpi: [432, 192] };
|
|
|
|
const pw = await import(join(ROOT, 'ui', 'node_modules', '@playwright', 'test', 'index.js'));
|
|
const { chromium } = pw.default ?? pw;
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage({ deviceScaleFactor: 1 });
|
|
const im = (u, s) => `<img src="${u}" width="${s}" height="${s}" style="display:block">`;
|
|
async function shot(html, s, transparent) {
|
|
await page.setViewportSize({ width: s, height: s });
|
|
await page.setContent(`<!doctype html><meta charset=utf8><style>*{margin:0;padding:0}#r{width:${s}px;height:${s}px}</style><div id=r>${html}</div>`, { waitUntil: 'networkidle' });
|
|
return page.locator('#r').screenshot({ omitBackground: !!transparent });
|
|
}
|
|
for (const [d, [fg, lg]] of Object.entries(D)) {
|
|
const dir = join(RES, `mipmap-${d}`);
|
|
writeFileSync(join(dir, 'ic_launcher_background.png'), await shot(im(BG, fg), fg, false));
|
|
writeFileSync(join(dir, 'ic_launcher_foreground.png'), await shot(im(FG, fg), fg, true));
|
|
writeFileSync(join(dir, 'ic_launcher_monochrome.png'), await shot(im(MONO, fg), fg, true));
|
|
// Square launcher: the full-bleed master (large mark). Round launcher: the safe-zone
|
|
// composite, circle-clipped (the master's corner ✻ would be clipped by the round mask).
|
|
writeFileSync(join(dir, 'ic_launcher.png'), await shot(im(MASTER, lg), lg, false));
|
|
const round = `<div style="width:${lg}px;height:${lg}px;border-radius:50%;overflow:hidden;position:relative">${im(BG, lg)}<div style="position:absolute;inset:0">${im(FG, lg)}</div></div>`;
|
|
writeFileSync(join(dir, 'ic_launcher_round.png'), await shot(round, lg, true));
|
|
console.log('mipmap-' + d, 'ok');
|
|
}
|
|
await browser.close();
|