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>
13
CLAUDE.md
|
|
@ -58,11 +58,14 @@ ui/player/ MiniPlayer, NowPlayingScreen, QueueScreen, GlassPlayButton, Fi
|
|||
ui/common/ shared pieces: LargeTitleScaffold, SongRow, AlbumCard, Loadable, ArtworkColors,
|
||||
SharedElements, FastScrollBar, AppLocals (cover URLs, playback hooks)
|
||||
ui/theme/ SyncWaveTheme, ArtworkTheme, colors, type
|
||||
res/mipmap-*/ic_launcher_* app icon from syncwave-icon-1024.png (project root), whole and untrimmed as
|
||||
the foreground + a brightness mask as monochrome; background #080812.
|
||||
res/drawable-*/splash_icon.png launch screen (Theme.SyncWave.Starting): same image with the grey
|
||||
spotlight faded to #080812 so only the wave and its glow show (no "plate").
|
||||
Regenerate all these PNGs if the source changes.
|
||||
res/mipmap-*/ic_launcher_* app icon from "SyncWave Sv2.png" (project root, glossy purple S): whole
|
||||
image at 94% as the foreground (S inside the 33dp safe zone), edge blended into
|
||||
background #0E0B14; monochrome = solid S silhouette.
|
||||
res/drawable-*/splash_icon.png launch screen (Theme.SyncWave.Starting): only the S on #0E0B14 (no
|
||||
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:
|
||||
|
|
|
|||
BIN
SyncWave Sv2.png
Normal file
|
After Width: | Height: | Size: 3 MiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 63 KiB |
|
|
@ -1,9 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SyncWave icon: syncwave-icon-1024.png (project root), used whole and untrimmed as the
|
||||
108dp foreground (mipmap-*/ic_launcher_foreground.png). Its wave reaches 32.5dp from the
|
||||
centre, inside the 33dp safe zone. Solid #080812 background, matching the image's edges.
|
||||
Monochrome layer (wave as a mask) for Android themed icons.
|
||||
SyncWave icon: "SyncWave Sv2.png" (project root), the whole image scaled to 94% as the
|
||||
108dp foreground (mipmap-*/ic_launcher_foreground.png), its edge blended into the
|
||||
background. The S then reaches 32.5dp from the centre, inside the 33dp safe zone. Solid
|
||||
#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">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SyncWave icon: syncwave-icon-1024.png (project root), used whole and untrimmed as the
|
||||
108dp foreground (mipmap-*/ic_launcher_foreground.png). Its wave reaches 32.5dp from the
|
||||
centre, inside the 33dp safe zone. Solid #080812 background, matching the image's edges.
|
||||
Monochrome layer (wave as a mask) for Android themed icons.
|
||||
SyncWave icon: "SyncWave Sv2.png" (project root), the whole image scaled to 94% as the
|
||||
108dp foreground (mipmap-*/ic_launcher_foreground.png), its edge blended into the
|
||||
background. The S then reaches 32.5dp from the centre, inside the 33dp safe zone. Solid
|
||||
#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">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 11 KiB |
|
|
@ -6,6 +6,6 @@
|
|||
<color name="brand_purple_light">#FFA693FF</color>
|
||||
<color name="window_background_light">#FFFFFFFF</color>
|
||||
<color name="window_background_dark">#FF000000</color>
|
||||
<!-- App icon and launch screen background (matches the edges of syncwave-icon-1024.png). -->
|
||||
<color name="ic_launcher_background">#FF080812</color>
|
||||
<!-- App icon and launch screen background (matches the edges of "SyncWave Sv2.png"). -->
|
||||
<color name="ic_launcher_background">#FF0E0B14</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@
|
|||
-->
|
||||
<style name="Theme.SyncWave.Starting" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_launcher_background</item>
|
||||
<!-- The icon image with its spotlight faded into the background, so the round crop
|
||||
doesn't show an edge (drawable-*/splash_icon.png, made from syncwave-icon-1024.png). -->
|
||||
<!-- Just the glossy S from the logo on the plain background, so the round crop doesn't
|
||||
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="postSplashScreenTheme">@style/Theme.SyncWave</item>
|
||||
</style>
|
||||
|
|
|
|||
87
tools/make_icons.py
Normal 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")
|
||||