New app icon and launch screen from "SyncWave Sv2.png"

- Adaptive icon: the glossy purple S logo, whole image at 94% so the S sits
  inside the 33dp safe zone (it reached 34.6dp untouched), edge blended into
  the new #0E0B14 background that matches the image's border.
- Themed (monochrome) icon: the S as a solid silhouette.
- Launch screen: only the S on #0E0B14, so the round splash crop shows no
  edge of the textured square.
- tools/make_icons.py regenerates all of these from the source PNG.

Verified on the emulator: icon in the app drawer and the launch screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
TySmi 2026-10-14 08:58:03 -04:00
parent 133af7b78f
commit 8919596ac2
22 changed files with 110 additions and 17 deletions

View file

@ -58,11 +58,14 @@ ui/player/ MiniPlayer, NowPlayingScreen, QueueScreen, GlassPlayButton, Fi
ui/common/ shared pieces: LargeTitleScaffold, SongRow, AlbumCard, Loadable, ArtworkColors, ui/common/ shared pieces: LargeTitleScaffold, SongRow, AlbumCard, Loadable, ArtworkColors,
SharedElements, FastScrollBar, AppLocals (cover URLs, playback hooks) SharedElements, FastScrollBar, AppLocals (cover URLs, playback hooks)
ui/theme/ SyncWaveTheme, ArtworkTheme, colors, type ui/theme/ SyncWaveTheme, ArtworkTheme, colors, type
res/mipmap-*/ic_launcher_* app icon from syncwave-icon-1024.png (project root), whole and untrimmed as res/mipmap-*/ic_launcher_* app icon from "SyncWave Sv2.png" (project root, glossy purple S): whole
the foreground + a brightness mask as monochrome; background #080812. image at 94% as the foreground (S inside the 33dp safe zone), edge blended into
res/drawable-*/splash_icon.png launch screen (Theme.SyncWave.Starting): same image with the grey background #0E0B14; monochrome = solid S silhouette.
spotlight faded to #080812 so only the wave and its glow show (no "plate"). res/drawable-*/splash_icon.png launch screen (Theme.SyncWave.Starting): only the S on #0E0B14 (no
Regenerate all these PNGs if the source changes. textured square, so the round crop shows no edge).
Regenerate all these PNGs with tools/make_icons.py (needs Pillow + numpy):
py tools/make_icons.py "SyncWave Sv2.png" app/src/main/res <preview dir>
syncwave-icon-1024.png is the previous logo (no longer used).
``` ```
Key rules: Key rules:

BIN
SyncWave Sv2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
SyncWave icon: syncwave-icon-1024.png (project root), used whole and untrimmed as the SyncWave icon: "SyncWave Sv2.png" (project root), the whole image scaled to 94% as the
108dp foreground (mipmap-*/ic_launcher_foreground.png). Its wave reaches 32.5dp from the 108dp foreground (mipmap-*/ic_launcher_foreground.png), its edge blended into the
centre, inside the 33dp safe zone. Solid #080812 background, matching the image's edges. background. The S then reaches 32.5dp from the centre, inside the 33dp safe zone. Solid
Monochrome layer (wave as a mask) for Android themed icons. #0E0B14 background, matching the image's edges. Monochrome layer (the S as a solid
silhouette) for Android themed icons.
--> -->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" /> <background android:drawable="@color/ic_launcher_background" />

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
SyncWave icon: syncwave-icon-1024.png (project root), used whole and untrimmed as the SyncWave icon: "SyncWave Sv2.png" (project root), the whole image scaled to 94% as the
108dp foreground (mipmap-*/ic_launcher_foreground.png). Its wave reaches 32.5dp from the 108dp foreground (mipmap-*/ic_launcher_foreground.png), its edge blended into the
centre, inside the 33dp safe zone. Solid #080812 background, matching the image's edges. background. The S then reaches 32.5dp from the centre, inside the 33dp safe zone. Solid
Monochrome layer (wave as a mask) for Android themed icons. #0E0B14 background, matching the image's edges. Monochrome layer (the S as a solid
silhouette) for Android themed icons.
--> -->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" /> <background android:drawable="@color/ic_launcher_background" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -6,6 +6,6 @@
<color name="brand_purple_light">#FFA693FF</color> <color name="brand_purple_light">#FFA693FF</color>
<color name="window_background_light">#FFFFFFFF</color> <color name="window_background_light">#FFFFFFFF</color>
<color name="window_background_dark">#FF000000</color> <color name="window_background_dark">#FF000000</color>
<!-- App icon and launch screen background (matches the edges of syncwave-icon-1024.png). --> <!-- App icon and launch screen background (matches the edges of "SyncWave Sv2.png"). -->
<color name="ic_launcher_background">#FF080812</color> <color name="ic_launcher_background">#FF0E0B14</color>
</resources> </resources>

View file

@ -18,8 +18,9 @@
--> -->
<style name="Theme.SyncWave.Starting" parent="Theme.SplashScreen"> <style name="Theme.SyncWave.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/ic_launcher_background</item> <item name="windowSplashScreenBackground">@color/ic_launcher_background</item>
<!-- The icon image with its spotlight faded into the background, so the round crop <!-- Just the glossy S from the logo on the plain background, so the round crop doesn't
doesn't show an edge (drawable-*/splash_icon.png, made from syncwave-icon-1024.png). --> show the edge of the textured square (drawable-*/splash_icon.png, made from
"SyncWave Sv2.png"). -->
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item> <item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<item name="postSplashScreenTheme">@style/Theme.SyncWave</item> <item name="postSplashScreenTheme">@style/Theme.SyncWave</item>
</style> </style>

87
tools/make_icons.py Normal file
View file

@ -0,0 +1,87 @@
"""Builds the SyncWave launcher icon, themed icon and splash image from the source logo.
Usage: py tools/make_icons.py "SyncWave Sv2.png" app/src/main/res <preview dir>
Writes mipmap-*/ic_launcher_foreground.png, mipmap-*/ic_launcher_monochrome.png,
drawable-*/splash_icon.png, and icon_preview.png in the preview dir.
"""
import sys, os
import numpy as np
from PIL import Image, ImageDraw, ImageFilter
src_path, res_dir, preview_dir = sys.argv[1], sys.argv[2], sys.argv[3]
BG = (14, 11, 20) # #0E0B14, average of the logo's outer border
SCALE = 0.94 # S reaches 34.6dp of 54 -> 32.5dp, inside the 33dp safe zone
N = 2048
src = Image.open(src_path).convert("RGB")
assert src.size == (N, N), src.size
a = np.asarray(src).astype(np.float32)
# --- Mask of the S: colourful/bright pixels; then everything not connected to the border
# background counts as S (fills darker spots inside the glass).
mx, mn = a.max(2), a.min(2)
core = ((mx - mn) > 60) & (mx > 70)
m = Image.fromarray(np.where(core, 255, 0).astype(np.uint8))
m = m.filter(ImageFilter.MaxFilter(9)).filter(ImageFilter.MinFilter(9)) # close small gaps
ImageDraw.floodfill(m, (0, 0), 128) # outside -> 128
s_mask = Image.fromarray(np.where(np.asarray(m) == 128, 0, 255).astype(np.uint8))
s_mask = s_mask.filter(ImageFilter.GaussianBlur(2.5)) # soft anti-aliased edge
# --- 1. Foreground: whole logo at 94% on BG, with its outer edge blended into BG.
def scaled_on_bg(img, alpha=None):
size = round(N * SCALE)
small = img.resize((size, size), Image.LANCZOS)
canvas = Image.new("RGB", (N, N), BG)
off = (N - size) // 2
if alpha is None:
# Feather the outer 3% of the image into the background.
ramp = np.ones((size, size), np.float32)
f = int(size * 0.03)
lin = np.linspace(0, 1, f, dtype=np.float32)
for i in range(f):
ramp[i, :] = np.minimum(ramp[i, :], lin[i]); ramp[-1 - i, :] = np.minimum(ramp[-1 - i, :], lin[i])
ramp[:, i] = np.minimum(ramp[:, i], lin[i]); ramp[:, -1 - i] = np.minimum(ramp[:, -1 - i], lin[i])
alpha = Image.fromarray((ramp * 255).astype(np.uint8))
else:
alpha = alpha.resize((size, size), Image.LANCZOS)
canvas.paste(small, (off, off), alpha)
return canvas
foreground = scaled_on_bg(src)
# --- 2. Monochrome: white S silhouette (Android tints it).
size = round(N * SCALE); off = (N - size) // 2
mono = Image.new("RGBA", (N, N), (255, 255, 255, 0))
mono_alpha = Image.new("L", (N, N), 0)
mono_alpha.paste(s_mask.resize((size, size), Image.LANCZOS), (off, off))
mono.putalpha(mono_alpha)
# --- 3. Splash: only the glossy S (no textured square) on BG.
splash = scaled_on_bg(src, alpha=s_mask)
densities = {"mdpi": 108, "hdpi": 162, "xhdpi": 216, "xxhdpi": 324, "xxxhdpi": 432}
for d, px in densities.items():
foreground.resize((px, px), Image.LANCZOS).save(os.path.join(res_dir, f"mipmap-{d}", "ic_launcher_foreground.png"), optimize=True)
mono.resize((px, px), Image.LANCZOS).save(os.path.join(res_dir, f"mipmap-{d}", "ic_launcher_monochrome.png"), optimize=True)
splash.resize((px, px), Image.LANCZOS).save(os.path.join(res_dir, f"drawable-{d}", "splash_icon.png"), optimize=True)
# --- Previews: icon in a circle mask (the 72dp visible area), themed icon, splash in its circle.
def circle_preview(img, visible_dp, bg=(255, 255, 255)):
px = 432
im = img.convert("RGBA").resize((px, px), Image.LANCZOS)
r = px * visible_dp / 108 / 2
out = Image.new("RGBA", (px, px), bg + (255,))
circ = Image.new("L", (px * 4, px * 4), 0)
ImageDraw.Draw(circ).ellipse([(px * 2 - r * 4), (px * 2 - r * 4), (px * 2 + r * 4), (px * 2 + r * 4)], fill=255)
out.paste(im, (0, 0), circ.resize((px, px), Image.LANCZOS))
return out
themed_bg = Image.new("RGB", (N, N), (40, 34, 60))
themed_fg = Image.new("RGB", (N, N), (205, 190, 255))
themed = Image.composite(themed_fg, themed_bg, mono_alpha)
row = [circle_preview(foreground, 72), circle_preview(foreground, 66), circle_preview(themed, 72), circle_preview(splash, 108 * 192 / 288, bg=BG)]
sheet = Image.new("RGB", (432 * 4 + 60, 472), (255, 255, 255))
for i, p in enumerate(row):
sheet.paste(p, (i * 452, 20), p)
sheet.save(os.path.join(preview_dir, "icon_preview.png"))
print("done")