diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a3e1fb7..724dbcf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -42,42 +42,50 @@ Переключается в рантайме (LauncherIconManager) при смене темы. --> + android:icon="@mipmap/ic_launcher_forest" android:roundIcon="@mipmap/ic_launcher_forest_round" + android:theme="@style/Theme.Radiola.Splash.Forest"> + android:icon="@mipmap/ic_launcher_ocean" android:roundIcon="@mipmap/ic_launcher_ocean_round" + android:theme="@style/Theme.Radiola.Splash.Ocean"> + android:icon="@mipmap/ic_launcher_sunset" android:roundIcon="@mipmap/ic_launcher_sunset_round" + android:theme="@style/Theme.Radiola.Splash.Sunset"> + android:icon="@mipmap/ic_launcher_amethyst" android:roundIcon="@mipmap/ic_launcher_amethyst_round" + android:theme="@style/Theme.Radiola.Splash.Amethyst"> + android:icon="@mipmap/ic_launcher_neon" android:roundIcon="@mipmap/ic_launcher_neon_round" + android:theme="@style/Theme.Radiola.Splash.Neon"> + android:icon="@mipmap/ic_launcher_amber" android:roundIcon="@mipmap/ic_launcher_amber_round" + android:theme="@style/Theme.Radiola.Splash.Amber"> + android:icon="@mipmap/ic_launcher_ice" android:roundIcon="@mipmap/ic_launcher_ice_round" + android:theme="@style/Theme.Radiola.Splash.Ice"> + android:icon="@mipmap/ic_launcher_rose" android:roundIcon="@mipmap/ic_launcher_rose_round" + android:theme="@style/Theme.Radiola.Splash.Rose"> diff --git a/app/src/main/java/com/radiola/MainActivity.kt b/app/src/main/java/com/radiola/MainActivity.kt index 04791c4..de652be 100644 --- a/app/src/main/java/com/radiola/MainActivity.kt +++ b/app/src/main/java/com/radiola/MainActivity.kt @@ -61,20 +61,39 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) - startService(Intent(this, PlayerService::class.java)) lifecycleScope.launch { tokenDataStore.preload() + // Старт плеер-сервиса уводим с критического пути запуска — ускоряет + // появление первого кадра (сплэша). + startService(Intent(this@MainActivity, PlayerService::class.java)) } ensureBackgroundPlaybackAllowed() enableEdgeToEdge() + // Тему берём из быстрого SharedPreferences (его пишет LauncherIconManager при + // смене темы) — синхронно и МГНОВЕННО, без блокировки первого кадра. Так сплэш + // и приложение сразу нужного цвета, и тёмный системный сплэш не висит лишнее. + val initialPaletteId = getSharedPreferences("radiola_prefs", MODE_PRIVATE) + .getString("icon_alias", "forest") ?: "forest" setContent { // Выбранная цветовая тема (мгновенно перекрашивает всё приложение). - val paletteId by settingsRepository.getThemePalette().collectAsState(initial = "forest") + val paletteId by settingsRepository.getThemePalette().collectAsState(initial = initialPaletteId) // Иконка лаунчера следует теме (срабатывает на старте и при смене темы). LaunchedEffect(paletteId) { launcherIconManager.applyIfNeeded(com.radiola.ui.theme.ThemePalette.fromId(paletteId)) } RadiolaTheme(palette = com.radiola.ui.theme.ThemePalette.fromId(paletteId)) { + // Сплэш рисуем на ПЕРВОМ (дешёвом) кадре; тяжёлый контент (ViewModels, + // плеер) композим следующим кадром ПОД сплэшем — так логотип появляется + // почти сразу, без долгого тёмного ожидания холодного старта. + var showSplash by remember { mutableStateOf(true) } + var contentReady by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { contentReady = true } + LaunchedEffect(Unit) { + kotlinx.coroutines.delay(1600) + showSplash = false + } + + if (contentReady) { val navController = rememberNavController() val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) var showPlayer by remember { mutableStateOf(false) } @@ -298,6 +317,20 @@ class MainActivity : ComponentActivity() { onDismiss = { if (!update.forceUpdate) pendingUpdate = null } ) } + + } // конец if (contentReady): тяжёлый контент композится под сплэшем + + // Тематический экран загрузки поверх всего (рисуем сами — системный + // сплэш Android 12+ нельзя перекрасить под выбранную тему). + androidx.compose.animation.AnimatedVisibility( + visible = showSplash, + enter = androidx.compose.animation.EnterTransition.None, + exit = androidx.compose.animation.fadeOut(androidx.compose.animation.core.tween(450)) + ) { + com.radiola.ui.components.SplashOverlay( + com.radiola.ui.theme.ThemePalette.fromId(paletteId) + ) + } } } } diff --git a/app/src/main/java/com/radiola/ui/components/SplashOverlay.kt b/app/src/main/java/com/radiola/ui/components/SplashOverlay.kt new file mode 100644 index 0000000..550b56f --- /dev/null +++ b/app/src/main/java/com/radiola/ui/components/SplashOverlay.kt @@ -0,0 +1,96 @@ +package com.radiola.ui.components + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.radiola.ui.theme.RadiolaWordmark +import com.radiola.ui.theme.ThemePalette +import kotlinx.coroutines.launch + +/** + * Свой экран загрузки: тематический 3D-логотип с акцентным свечением под цвет темы, + * мягкой тенью и плавным появлением. Рисуем сами (а не системный сплэш), потому что + * Android 12+ не даёт менять иконку системного сплэша под выбранную пользователем тему. + */ +@Composable +fun SplashOverlay(palette: ThemePalette, modifier: Modifier = Modifier) { + val colors = palette.colors + // Появление снапное: лого видно почти сразу (короткий fade), затем мягкий «вдох». + val scale = remember { Animatable(0.92f) } + val fade = remember { Animatable(0f) } + LaunchedEffect(Unit) { + launch { fade.animateTo(1f, tween(200)) } + launch { scale.animateTo(1f, tween(420, easing = FastOutSlowInEasing)) } + } + Box( + modifier = modifier.fillMaxSize().background(colors.bgBase), + contentAlignment = Alignment.Center + ) { + // Акцентное свечение под цвет темы + Box( + Modifier + .size(380.dp) + .graphicsLayer { alpha = fade.value } + .background( + Brush.radialGradient( + listOf( + colors.accent.copy(alpha = 0.32f), + colors.accent.copy(alpha = 0.10f), + Color.Transparent + ) + ) + ) + ) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Box(contentAlignment = Alignment.Center) { + // Мягкая тень логотипа + Image( + painter = painterResource(palette.logoRes), + contentDescription = null, + colorFilter = ColorFilter.tint(Color.Black.copy(alpha = 0.5f)), + modifier = Modifier + .size(176.dp) + .scale(scale.value) + .offset(y = 14.dp) + .blur(18.dp) + .graphicsLayer { alpha = fade.value } + ) + // Логотип + Image( + painter = painterResource(palette.logoRes), + contentDescription = "radiOLA", + modifier = Modifier + .size(176.dp) + .scale(scale.value) + .graphicsLayer { alpha = fade.value } + ) + } + Spacer(Modifier.height(20.dp)) + Box(Modifier.graphicsLayer { alpha = fade.value }) { + RadiolaWordmark(fontSize = 30) + } + } + } +} diff --git a/app/src/main/res/drawable-nodpi/ic_bg_amber.png b/app/src/main/res/drawable-nodpi/ic_bg_amber.png new file mode 100644 index 0000000..66d3ad4 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_amber.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_bg_amethyst.png b/app/src/main/res/drawable-nodpi/ic_bg_amethyst.png new file mode 100644 index 0000000..4b3e1a9 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_amethyst.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_bg_forest.png b/app/src/main/res/drawable-nodpi/ic_bg_forest.png new file mode 100644 index 0000000..a3d065d Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_forest.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_bg_ice.png b/app/src/main/res/drawable-nodpi/ic_bg_ice.png new file mode 100644 index 0000000..7635682 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_ice.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_bg_neon.png b/app/src/main/res/drawable-nodpi/ic_bg_neon.png new file mode 100644 index 0000000..6e1e024 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_neon.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_bg_ocean.png b/app/src/main/res/drawable-nodpi/ic_bg_ocean.png new file mode 100644 index 0000000..524c1d1 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_ocean.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_bg_rose.png b/app/src/main/res/drawable-nodpi/ic_bg_rose.png new file mode 100644 index 0000000..841d3d3 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_rose.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_bg_sunset.png b/app/src/main/res/drawable-nodpi/ic_bg_sunset.png new file mode 100644 index 0000000..b568d96 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_sunset.png differ diff --git a/app/src/main/res/drawable-nodpi/splash_logo_amber.png b/app/src/main/res/drawable-nodpi/splash_logo_amber.png new file mode 100644 index 0000000..efa4976 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_amber.png differ diff --git a/app/src/main/res/drawable-nodpi/splash_logo_amethyst.png b/app/src/main/res/drawable-nodpi/splash_logo_amethyst.png new file mode 100644 index 0000000..013a796 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_amethyst.png differ diff --git a/app/src/main/res/drawable-nodpi/splash_logo_forest.png b/app/src/main/res/drawable-nodpi/splash_logo_forest.png new file mode 100644 index 0000000..5c3b73c Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_forest.png differ diff --git a/app/src/main/res/drawable-nodpi/splash_logo_ice.png b/app/src/main/res/drawable-nodpi/splash_logo_ice.png new file mode 100644 index 0000000..8eaf931 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_ice.png differ diff --git a/app/src/main/res/drawable-nodpi/splash_logo_neon.png b/app/src/main/res/drawable-nodpi/splash_logo_neon.png new file mode 100644 index 0000000..3383692 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_neon.png differ diff --git a/app/src/main/res/drawable-nodpi/splash_logo_ocean.png b/app/src/main/res/drawable-nodpi/splash_logo_ocean.png new file mode 100644 index 0000000..376f1d6 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_ocean.png differ diff --git a/app/src/main/res/drawable-nodpi/splash_logo_rose.png b/app/src/main/res/drawable-nodpi/splash_logo_rose.png new file mode 100644 index 0000000..81bb0aa Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_rose.png differ diff --git a/app/src/main/res/drawable-nodpi/splash_logo_sunset.png b/app/src/main/res/drawable-nodpi/splash_logo_sunset.png new file mode 100644 index 0000000..7bf6f97 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_sunset.png differ diff --git a/app/src/main/res/drawable/splash_transparent.xml b/app/src/main/res/drawable/splash_transparent.xml new file mode 100644 index 0000000..057f153 --- /dev/null +++ b/app/src/main/res/drawable/splash_transparent.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 015762b..6d02a6a 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml index 279f7bc..0cd6a7f 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber_round.xml index 279f7bc..0cd6a7f 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber_round.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst.xml index 8afb6f2..9da4444 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst_round.xml index 8afb6f2..9da4444 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst_round.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml index 6840300..0747f26 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest_round.xml index 6840300..0747f26 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest_round.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice.xml index 470f0e1..6cd4f40 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice_round.xml index 470f0e1..6cd4f40 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice_round.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon.xml index ca6c953..2ce13f7 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon_round.xml index ca6c953..2ce13f7 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon_round.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean.xml index 7d788fa..baed001 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean_round.xml index 7d788fa..baed001 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean_round.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose.xml index 1e2976d..7feee71 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_round.xml index 1e2976d..7feee71 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_round.xml @@ -1,5 +1,5 @@ - + 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 index 015762b..6d02a6a 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset.xml index e145e77..acf3874 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset_round.xml index e145e77..acf3874 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset_round.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index a000b81..3fc1852 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -11,7 +11,53 @@ тёмный фон + наша иконка, затем переход в основную тему. --> + + + + + + + + + + diff --git a/design/logos/gen_bg.py b/design/logos/gen_bg.py new file mode 100644 index 0000000..fa62b67 --- /dev/null +++ b/design/logos/gen_bg.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +"""Фоны adaptive-иконки: градиент под акцент темы + радиальное свечение + тень от логотипа.""" +from PIL import Image, ImageDraw, ImageFilter +import numpy as np, os + +TH = r"C:\radiOLA\design\logos\gen\themed" +DRW = r"C:\radiOLA\app\src\main\res\drawable-nodpi" +PREV = os.environ["LOCALAPPDATA"] + r"\Temp\radiola_bg_preview.png" +S = 432 +THEMES = [ + ("forest", (0xA8,0xE0,0x5F), (0x0C,0x14,0x10)), + ("ocean", (0x4F,0xD6,0xE0), (0x0A,0x0F,0x1A)), + ("sunset", (0xFF,0x8A,0x5B), (0x1A,0x0F,0x0C)), + ("amethyst",(0xB3,0x88,0xFF), (0x12,0x0E,0x1A)), + ("neon", (0xFF,0x4D,0x9D), (0x0D,0x0A,0x12)), + ("amber", (0xFF,0xC2,0x47), (0x14,0x11,0x0A)), + ("ice", (0x7F,0xB3,0xFF), (0x0C,0x10,0x14)), + ("rose", (0xFF,0x7E,0xA8), (0x16,0x0E,0x12)), +] + +def lerp(a,b,t): return tuple(a[i]+(b[i]-a[i])*t for i in range(3)) + +yy,xx = np.mgrid[0:S,0:S] +cx,cy=S*0.5,S*0.46 +rad = np.sqrt((xx-cx)**2+(yy-cy)**2)/(S*0.62) # 0 в центре → ~1 к краю +diag = (xx+yy)/(2*S) # 0 верх-лево → 1 низ-право + +def make_bg(accent, bg): + acc=np.array(accent,float); base=np.array(bg,float) + # 1) база: лёгкий диагональный градиент (верх чуть светлее с оттенком акцента, низ темнее) + top = lerp(bg, accent, 0.10); top=np.array([min(255,c*1.15) for c in top]) + bot = np.array([c*0.7 for c in bg]) + g = diag[...,None] + img = top*(1-g) + bot*g + # 2) радиальное акцентное свечение в центре + glow = np.clip(1-rad,0,1)**1.7 + img = img + (acc-img)*(glow[...,None]*0.30) + # 3) виньетка по углам + vig = np.clip(rad-0.6,0,1) + img = img*(1-vig[...,None]*0.35) + return Image.fromarray(np.clip(img,0,255).astype("uint8"),"RGB").convert("RGBA") + +def r_shadow(theme): + """Мягкая тень от логотипа (силуэт R), запечённая в фон.""" + logo=Image.open(os.path.join(TH,f"logo_{theme}.png")).convert("RGBA") + logo=logo.crop(logo.getbbox()) + target=196; sc=target/max(logo.size) + logo=logo.resize((int(logo.width*sc),int(logo.height*sc))) + sh=Image.new("RGBA",(S,S),(0,0,0,0)) + # силуэт из альфы + sil=Image.new("RGBA",(S,S),(0,0,0,0)) + a=logo.split()[3] + blk=Image.new("RGBA",logo.size,(0,0,0,150)) + sil.paste(blk,((S-logo.width)//2+7,(S-logo.height)//2+14),a) # сдвиг вниз-вправо + sil=sil.filter(ImageFilter.GaussianBlur(16)) + return sil + +cols=4; rows=2; pad=16 +sheet=Image.new("RGB",(cols*S//2+(cols+1)*pad, rows*S//2+(rows+1)*pad),(28,28,32)) +for i,(name,acc,bg) in enumerate(THEMES): + base=make_bg(acc,bg) + base.alpha_composite(r_shadow(name)) + base.convert("RGB").save(os.path.join(DRW,f"ic_bg_{name}.png")) + # превью: фон + foreground + круглая маска + fg=Image.open(os.path.join(DRW,f"ic_fg_{name}.png")).convert("RGBA") + full=base.copy(); full.alpha_composite(fg) + mask=Image.new("L",(S,S),0); ImageDraw.Draw(mask).ellipse([0,0,S,S],fill=255) + out=Image.new("RGB",(S,S),(28,28,32)); out.paste(full.convert("RGB"),(0,0),mask) + out=out.resize((S//2,S//2)) + r=i//cols; c=i%cols; x=pad+c*(S//2+pad); y=pad+r*(S//2+pad) + sheet.paste(out,(x,y)) +sheet.save(PREV) +print("bg generated + preview", PREV)