diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml index c996f88e6..eb08c54d7 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -29,11 +29,6 @@ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:supportsPictureInPicture="true" android:theme="@style/Theme.Nuvio.Splash"> - - - - - @@ -74,6 +69,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + option.platformName to "$launcherPackage.${option.platformName ?: "AppIconDefault"}" + } + + private var context: Context? = null + + fun initialize(context: Context) { + val appContext = context.applicationContext + this.context = appContext + restoreDefaultIfNeeded(appContext) + } + + actual fun currentIconName(): String? { + val appContext = context ?: return null + return currentIconName(appContext) + } + + fun currentLauncherIconResource(context: Context): Int { + val option = AppIconOption.fromPlatformName(currentIconName(context)) + val resourceName = if (option == AppIconOption.ORIGINAL) { + "ic_launcher" + } else { + "ic_launcher_${option.key}" + } + return context.resources + .getIdentifier(resourceName, "mipmap", context.packageName) + .takeIf { it != 0 } + ?: com.nuvio.app.R.mipmap.ic_launcher + } + + fun currentLauncherComponent(context: Context): ComponentName { + val currentName = currentIconName(context) + val className = launcherComponents.first { it.first == currentName }.second + return component(context, className) + } + + private fun currentIconName(context: Context): String? { + val packageManager = context.packageManager + val explicitlyEnabled = launcherComponents.firstOrNull { (_, className) -> + packageManager.getComponentEnabledSetting(component(context, className)) == + PackageManager.COMPONENT_ENABLED_STATE_ENABLED + } + return explicitlyEnabled?.first + } + + private fun restoreDefaultIfNeeded(context: Context) { + val packageManager = context.packageManager + val hasEnabledComponent = launcherComponents.any { (_, className) -> + packageManager.getComponentEnabledSetting(component(context, className)) == + PackageManager.COMPONENT_ENABLED_STATE_ENABLED + } + if (hasEnabledComponent) return + + val defaultClass = launcherComponents.first { it.first == null }.second + if ( + packageManager.getComponentEnabledSetting(component(context, defaultClass)) != + PackageManager.COMPONENT_ENABLED_STATE_DISABLED + ) { + return + } + packageManager.setComponentEnabledSetting( + component(context, defaultClass), + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP, + ) + } + + actual suspend fun activateIcon(name: String?): Boolean { + val appContext = context ?: return false + val selectedClass = launcherComponents.firstOrNull { it.first == name }?.second ?: return false + val packageManager = appContext.packageManager + + return runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageManager.setComponentEnabledSettings( + launcherComponents.map { (_, className) -> + PackageManager.ComponentEnabledSetting( + component(appContext, className), + if (className == selectedClass) { + PackageManager.COMPONENT_ENABLED_STATE_ENABLED + } else { + PackageManager.COMPONENT_ENABLED_STATE_DISABLED + }, + PackageManager.DONT_KILL_APP, + ) + }, + ) + } else { + launcherComponents.forEach { (_, className) -> + packageManager.setComponentEnabledSetting( + component(appContext, className), + if (className == selectedClass) { + PackageManager.COMPONENT_ENABLED_STATE_ENABLED + } else { + PackageManager.COMPONENT_ENABLED_STATE_DISABLED + }, + PackageManager.DONT_KILL_APP, + ) + } + } + }.isSuccess + } + + private fun component(context: Context, className: String): ComponentName = + ComponentName(context.packageName, className) +} diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/launcher/AppIconActivities.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/launcher/AppIconActivities.kt new file mode 100644 index 000000000..8838d222d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/launcher/AppIconActivities.kt @@ -0,0 +1,15 @@ +package com.nuvio.app.launcher + +import com.nuvio.app.MainActivity + +class AppIconDefault : MainActivity() + +class AppIconArcticBlue : MainActivity() + +class AppIconEmerald : MainActivity() + +class AppIconRoseGold : MainActivity() + +class AppIconCopper : MainActivity() + +class AppIconGraphite : MainActivity() diff --git a/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo.webp b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo.webp index 7db97d76c..b5230128f 100644 Binary files a/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo.webp and b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo.webp differ diff --git a/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_arctic_blue.webp b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_arctic_blue.webp new file mode 100644 index 000000000..9d47c880f Binary files /dev/null and b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_arctic_blue.webp differ diff --git a/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_copper.webp b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_copper.webp new file mode 100644 index 000000000..770e58117 Binary files /dev/null and b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_copper.webp differ diff --git a/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_emerald.webp b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_emerald.webp new file mode 100644 index 000000000..42b900198 Binary files /dev/null and b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_emerald.webp differ diff --git a/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_graphite.webp b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_graphite.webp new file mode 100644 index 000000000..2c3fb6d1b Binary files /dev/null and b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_graphite.webp differ diff --git a/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_rose_gold.webp b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_rose_gold.webp new file mode 100644 index 000000000..34cd65693 Binary files /dev/null and b/composeApp/src/androidMain/res/drawable-nodpi/ic_splash_logo_rose_gold.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml index 9d92593cd..425b4f12d 100644 --- a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,6 @@ - + - \ No newline at end of file + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_arctic_blue.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_arctic_blue.xml new file mode 100644 index 000000000..304c4dfa4 --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_arctic_blue.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_copper.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_copper.xml new file mode 100644 index 000000000..8bb03d676 --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_copper.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_emerald.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_emerald.xml new file mode 100644 index 000000000..ca3919090 --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_emerald.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_graphite.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_graphite.xml new file mode 100644 index 000000000..644caea77 --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_graphite.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_rose_gold.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_rose_gold.xml new file mode 100644 index 000000000..f8cc96e10 --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_rose_gold.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml index 9d92593cd..425b4f12d 100644 --- a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,6 @@ - + - \ No newline at end of file + + diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp index fbc06277a..fc0a5c726 100644 Binary files a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_arctic_blue.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_arctic_blue.webp new file mode 100644 index 000000000..aea17c429 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_arctic_blue.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_arctic_blue_foreground.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_arctic_blue_foreground.webp new file mode 100644 index 000000000..608805d10 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_arctic_blue_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_copper.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_copper.webp new file mode 100644 index 000000000..f211a5bc6 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_copper.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_copper_foreground.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_copper_foreground.webp new file mode 100644 index 000000000..a9d6fdc9d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_copper_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_emerald.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_emerald.webp new file mode 100644 index 000000000..1330a42c0 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_emerald.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_emerald_foreground.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_emerald_foreground.webp new file mode 100644 index 000000000..2a88efec4 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_emerald_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp index fbc06277a..00a611123 100644 Binary files a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_graphite.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_graphite.webp new file mode 100644 index 000000000..423d915c2 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_graphite.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_graphite_foreground.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_graphite_foreground.webp new file mode 100644 index 000000000..7e35ca6d3 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_graphite_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_monochrome.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_monochrome.webp new file mode 100644 index 000000000..021f7b562 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_monochrome.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_rose_gold.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_rose_gold.webp new file mode 100644 index 000000000..97ae4d27e Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_rose_gold.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_rose_gold_foreground.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_rose_gold_foreground.webp new file mode 100644 index 000000000..309ab045e Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_rose_gold_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp index fbc06277a..e53a8e1bf 100644 Binary files a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp index c28a18014..a77ba395a 100644 Binary files a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_arctic_blue.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_arctic_blue.webp new file mode 100644 index 000000000..db8b0b702 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_arctic_blue.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_arctic_blue_foreground.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_arctic_blue_foreground.webp new file mode 100644 index 000000000..d0d441a71 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_arctic_blue_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_copper.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_copper.webp new file mode 100644 index 000000000..3b128b231 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_copper.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_copper_foreground.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_copper_foreground.webp new file mode 100644 index 000000000..07c9d6de8 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_copper_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_emerald.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_emerald.webp new file mode 100644 index 000000000..974850237 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_emerald.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_emerald_foreground.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_emerald_foreground.webp new file mode 100644 index 000000000..9fb8f1381 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_emerald_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp index c28a18014..92f7966ad 100644 Binary files a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_graphite.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_graphite.webp new file mode 100644 index 000000000..e5acaa773 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_graphite.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_graphite_foreground.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_graphite_foreground.webp new file mode 100644 index 000000000..a08d10e07 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_graphite_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_monochrome.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_monochrome.webp new file mode 100644 index 000000000..054555dd9 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_monochrome.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_rose_gold.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_rose_gold.webp new file mode 100644 index 000000000..5ddbf07b3 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_rose_gold.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_rose_gold_foreground.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_rose_gold_foreground.webp new file mode 100644 index 000000000..08519ccf9 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_rose_gold_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp index c28a18014..7ff5c43fb 100644 Binary files a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp index 16206c58c..539a9f04a 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_arctic_blue.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_arctic_blue.webp new file mode 100644 index 000000000..5b5a40d61 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_arctic_blue.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_arctic_blue_foreground.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_arctic_blue_foreground.webp new file mode 100644 index 000000000..fcf3af857 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_arctic_blue_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_copper.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_copper.webp new file mode 100644 index 000000000..3d2fbbcfb Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_copper.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_copper_foreground.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_copper_foreground.webp new file mode 100644 index 000000000..b4f692fd4 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_copper_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_emerald.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_emerald.webp new file mode 100644 index 000000000..a31be3777 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_emerald.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_emerald_foreground.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_emerald_foreground.webp new file mode 100644 index 000000000..718fc2562 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_emerald_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp index 16206c58c..6b7048f5a 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_graphite.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_graphite.webp new file mode 100644 index 000000000..624839902 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_graphite.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_graphite_foreground.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_graphite_foreground.webp new file mode 100644 index 000000000..a61e11f3f Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_graphite_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_monochrome.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_monochrome.webp new file mode 100644 index 000000000..b523662d6 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_monochrome.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_rose_gold.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_rose_gold.webp new file mode 100644 index 000000000..339b01b1b Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_rose_gold.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_rose_gold_foreground.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_rose_gold_foreground.webp new file mode 100644 index 000000000..4ed478b40 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_rose_gold_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp index 16206c58c..c5611b3c1 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp index 7795c137a..db8266226 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_arctic_blue.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_arctic_blue.webp new file mode 100644 index 000000000..238fb1c63 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_arctic_blue.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_arctic_blue_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_arctic_blue_foreground.webp new file mode 100644 index 000000000..a9b70c3db Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_arctic_blue_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_copper.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_copper.webp new file mode 100644 index 000000000..02dc14d65 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_copper.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_copper_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_copper_foreground.webp new file mode 100644 index 000000000..f3f81d9e5 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_copper_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_emerald.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_emerald.webp new file mode 100644 index 000000000..2a6d066d7 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_emerald.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_emerald_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_emerald_foreground.webp new file mode 100644 index 000000000..2761136fa Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_emerald_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp index 7795c137a..e47b08f50 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_graphite.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_graphite.webp new file mode 100644 index 000000000..797ed1489 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_graphite.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_graphite_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_graphite_foreground.webp new file mode 100644 index 000000000..41c40e113 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_graphite_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_monochrome.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_monochrome.webp new file mode 100644 index 000000000..db9673199 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_monochrome.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_rose_gold.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_rose_gold.webp new file mode 100644 index 000000000..b5f3be314 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_rose_gold.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_rose_gold_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_rose_gold_foreground.webp new file mode 100644 index 000000000..2872475e7 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_rose_gold_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp index 7795c137a..d902fe55f 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp index 43104830e..403d01a49 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_arctic_blue.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_arctic_blue.webp new file mode 100644 index 000000000..18ce00f45 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_arctic_blue.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_arctic_blue_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_arctic_blue_foreground.webp new file mode 100644 index 000000000..c35eae5da Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_arctic_blue_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_copper.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_copper.webp new file mode 100644 index 000000000..c052bb6a4 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_copper.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_copper_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_copper_foreground.webp new file mode 100644 index 000000000..f8188adbd Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_copper_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_emerald.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_emerald.webp new file mode 100644 index 000000000..f65c5990e Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_emerald.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_emerald_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_emerald_foreground.webp new file mode 100644 index 000000000..3f75b498e Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_emerald_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp index 43104830e..5dac649e6 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_graphite.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_graphite.webp new file mode 100644 index 000000000..fb558aa5f Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_graphite.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_graphite_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_graphite_foreground.webp new file mode 100644 index 000000000..2422b037d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_graphite_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp new file mode 100644 index 000000000..d9e359628 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_rose_gold.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_rose_gold.webp new file mode 100644 index 000000000..26dc9fb4e Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_rose_gold.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_rose_gold_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_rose_gold_foreground.webp new file mode 100644 index 000000000..aa0fb8545 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_rose_gold_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp index 43104830e..47ec6939e 100644 Binary files a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/values-v31/themes.xml b/composeApp/src/androidMain/res/values-v31/themes.xml index 97bab9895..3a996359b 100644 --- a/composeApp/src/androidMain/res/values-v31/themes.xml +++ b/composeApp/src/androidMain/res/values-v31/themes.xml @@ -9,4 +9,24 @@ @drawable/ic_splash_logo @style/Theme.Nuvio + + + + + + + + + + diff --git a/composeApp/src/androidMain/res/values/colors.xml b/composeApp/src/androidMain/res/values/colors.xml index da3edf19a..ed834aeea 100644 --- a/composeApp/src/androidMain/res/values/colors.xml +++ b/composeApp/src/androidMain/res/values/colors.xml @@ -1,4 +1,5 @@ + #000000 #0D0D0D diff --git a/composeApp/src/androidMain/res/values/themes.xml b/composeApp/src/androidMain/res/values/themes.xml index 91536bd1b..342c5db31 100644 --- a/composeApp/src/androidMain/res/values/themes.xml +++ b/composeApp/src/androidMain/res/values/themes.xml @@ -5,4 +5,9 @@ + + """ + for key, _, ios_name, _ in VARIANTS[1:] + ) + return f""" + + + + + +{alternate_themes} + +""" + + +def remove_retired_assets() -> None: + for key, _, ios_name, _ in ALL_VARIANTS: + if key in SHORTLIST_KEYS: + continue + (COMPOSE_DRAWABLE / f"app_icon_{key}.png").unlink(missing_ok=True) + (COMPOSE_DRAWABLE / f"app_logo_wordmark_{key}.png").unlink(missing_ok=True) + (ANDROID_SPLASH / f"ic_splash_logo_{key}.webp").unlink(missing_ok=True) + (ANDROID_RES / "mipmap-anydpi-v26" / f"ic_launcher_{key}.xml").unlink( + missing_ok=True, + ) + for density in DENSITIES: + directory = ANDROID_RES / f"mipmap-{density}" + (directory / f"ic_launcher_{key}.webp").unlink(missing_ok=True) + (directory / f"ic_launcher_{key}_foreground.webp").unlink(missing_ok=True) + shutil.rmtree(IOS_ASSETS / f"{ios_name}.appiconset", ignore_errors=True) + + +def generate_preview_board(icons: list[tuple[str, Image.Image]]) -> None: + columns = 6 + tile_width = 220 + tile_height = 244 + margin = 32 + header_height = 104 + rows = (len(icons) + columns - 1) // columns + board = Image.new( + "RGB", + (margin * 2 + columns * tile_width, header_height + margin + rows * tile_height), + (8, 8, 10), + ) + draw = ImageDraw.Draw(board) + title_font = ImageFont.truetype(FONT, 34) + label_font = ImageFont.truetype(FONT, 18) + draw.text((margin, 28), "NUVIO / APP ICON CATALOGUE", fill=(245, 245, 247), font=title_font) + draw.text((margin, 70), "6 shortlisted colorways · pure black field", fill=(150, 150, 158), font=label_font) + + for index, (label, icon) in enumerate(icons): + row = index // columns + column = index % columns + x = margin + column * tile_width + y = header_height + row * tile_height + draw.rounded_rectangle( + (x + 6, y + 6, x + tile_width - 6, y + tile_height - 8), + radius=24, + fill=(17, 17, 20), + outline=(42, 42, 47), + width=1, + ) + preview = resized(icon, 168) + board.paste(preview, (x + 26, y + 22)) + text_bounds = draw.textbbox((0, 0), label, font=label_font) + text_width = text_bounds[2] - text_bounds[0] + draw.text( + (x + (tile_width - text_width) / 2, y + 204), + label, + fill=(232, 232, 237), + font=label_font, + ) + + PREVIEW_BOARD.parent.mkdir(parents=True, exist_ok=True) + board.save(PREVIEW_BOARD, "PNG", optimize=True) + + +def generate() -> None: + COMPOSE_DRAWABLE.mkdir(parents=True, exist_ok=True) + remove_retired_assets() + previews = [] + + for key, filename, ios_name, label in VARIANTS: + transparent = Image.open(COLORWAYS / "transparent" / filename).convert("RGBA") + expanded = expanded_logo(transparent, max_width_ratio=0.70, max_height_ratio=0.74) + adaptive = expanded_logo(transparent, max_width_ratio=0.52, max_height_ratio=0.55) + black = on_black(expanded) + launcher_name = "ic_launcher" if key == "original" else f"ic_launcher_{key}" + foreground_name = f"{launcher_name}_foreground" + splash_name = "ic_splash_logo" if key == "original" else f"ic_splash_logo_{key}" + previews.append((label, black)) + + save_webp(transparent, ANDROID_SPLASH / f"{splash_name}.webp") + + adaptive_directory = ANDROID_RES / "mipmap-anydpi-v26" + adaptive_directory.mkdir(parents=True, exist_ok=True) + (adaptive_directory / f"{launcher_name}.xml").write_text( + adaptive_icon_contents(foreground_name), + encoding="utf-8", + ) + + resized(black, 256).save( + COMPOSE_DRAWABLE / f"app_icon_{key}.png", + "PNG", + optimize=True, + ) + wordmark_for(transparent).save( + COMPOSE_DRAWABLE / f"app_logo_wordmark_{key}.png", + "PNG", + optimize=True, + ) + + icon_set = IOS_ASSETS / f"{ios_name}.appiconset" + icon_set.mkdir(parents=True, exist_ok=True) + resized(black, 1024).save(icon_set / "app-icon-1024.png", "PNG", optimize=True) + (icon_set / "Contents.json").write_text( + json.dumps(ios_contents(), indent=2) + "\n", + encoding="utf-8", + ) + + for density, scale in DENSITIES.items(): + directory = ANDROID_RES / f"mipmap-{density}" + legacy_size = round(48 * scale) + foreground_size = round(108 * scale) + save_webp(resized(black, legacy_size), directory / f"{launcher_name}.webp") + save_webp( + resized(adaptive, foreground_size), + directory / f"{foreground_name}.webp", + ) + + if key == "original": + save_webp( + round_icon(resized(black, legacy_size)), + directory / "ic_launcher_round.webp", + ) + save_webp( + resized(monochrome_icon(adaptive), foreground_size), + directory / "ic_launcher_monochrome.webp", + ) + + generate_preview_board(previews) + ANDROID_THEMES.write_text(base_theme_contents(), encoding="utf-8") + ANDROID_THEMES_V31.write_text(v31_theme_contents(), encoding="utf-8") + + +if __name__ == "__main__": + generate() diff --git a/scripts/generate_logo_colorways.py b/scripts/generate_logo_colorways.py new file mode 100644 index 000000000..566a59202 --- /dev/null +++ b/scripts/generate_logo_colorways.py @@ -0,0 +1,486 @@ +from pathlib import Path +import colorsys + +import cv2 +import numpy as np +from PIL import Image, ImageDraw, ImageFont + + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "asset/Deliverables 2/Logo Only/Logo_1080x1080.png" +OUTPUT = ROOT / "asset/Logo Colorways - Black" +FONT_REGULAR = Path("/System/Library/Fonts/SFNS.ttf") +BLACK = "#000000" + + +FAMILIES = [ + { + "slug": "core-cool", + "name": "Core Cool", + "label": "CORE\nCOOL", + "description": "Brand-first\ntechnology palette", + }, + { + "slug": "aqua-green", + "name": "Aqua + Green", + "label": "AQUA +\nGREEN", + "description": "Fresh product and\ngrowth palette", + }, + { + "slug": "warm-spectrum", + "name": "Warm Spectrum", + "label": "WARM\nSPECTRUM", + "description": "Entertainment and\ncampaign palette", + }, + { + "slug": "luxe-neutral", + "name": "Luxe + Neutral", + "label": "LUXE +\nNEUTRAL", + "description": "Premium and\nsystem palette", + }, +] + + +VARIANTS = [ + { + "family": "core-cool", + "slug": "original-on-black", + "name": "Original on Black", + "swatches": ["#45D6EB", "#3965D4", "#B250EF"], + "hues": [190, 225, 286], + "usage": "Current identity", + "preserve": True, + }, + { + "family": "core-cool", + "slug": "glacier", + "name": "Glacier", + "swatches": ["#7BE7FF", "#35B9FF", "#506BFF"], + "hues": [191, 202, 231], + "usage": "Bright and approachable", + }, + { + "family": "core-cool", + "slug": "arctic-blue", + "name": "Arctic Blue", + "swatches": ["#4DE3FF", "#3185F5", "#4D55E8"], + "hues": [188, 214, 237], + "usage": "Best master-brand option", + "recommended": True, + }, + { + "family": "core-cool", + "slug": "cobalt-pulse", + "name": "Cobalt Pulse", + "swatches": ["#59A8FF", "#315BFF", "#613DFF"], + "hues": [211, 228, 254], + "usage": "High-energy technology", + }, + { + "family": "core-cool", + "slug": "sapphire", + "name": "Sapphire", + "swatches": ["#38C5FF", "#2265E5", "#3B2FD1"], + "hues": [196, 219, 245], + "usage": "Confident and premium", + }, + { + "family": "core-cool", + "slug": "electric-violet", + "name": "Electric Violet", + "swatches": ["#4985FF", "#704DFF", "#EA4DFF"], + "hues": [218, 253, 293], + "usage": "Creator-led media", + }, + { + "family": "aqua-green", + "slug": "polar-mint", + "name": "Polar Mint", + "swatches": ["#80FFE3", "#31E6C5", "#21B8E5"], + "hues": [167, 169, 194], + "usage": "Light and contemporary", + }, + { + "family": "aqua-green", + "slug": "tidal", + "name": "Tidal", + "swatches": ["#42E8B4", "#14C8C8", "#2384E8"], + "hues": [160, 180, 210], + "usage": "Best fresh-brand option", + "recommended": True, + }, + { + "family": "aqua-green", + "slug": "turquoise", + "name": "Turquoise", + "swatches": ["#38F2D0", "#12D6B3", "#19A7D8"], + "hues": [169, 168, 195], + "usage": "Clean and global", + }, + { + "family": "aqua-green", + "slug": "emerald", + "name": "Emerald", + "swatches": ["#7BF08D", "#22D37C", "#0BBF9A"], + "hues": [127, 148, 168], + "usage": "Mature and assured", + }, + { + "family": "aqua-green", + "slug": "jade", + "name": "Jade", + "swatches": ["#B9F44A", "#2DDB83", "#12B8A6"], + "hues": [79, 150, 174], + "usage": "Energetic product growth", + }, + { + "family": "aqua-green", + "slug": "lime-pulse", + "name": "Lime Pulse", + "swatches": ["#D5FF4F", "#78ED3B", "#16C995"], + "hues": [72, 104, 157], + "usage": "Youth and activation", + }, + { + "family": "warm-spectrum", + "slug": "sunset", + "name": "Sunset", + "swatches": ["#8D55FF", "#F044A7", "#FF7048"], + "hues": [260, 324, 374], + "usage": "Best campaign option", + "recommended": True, + }, + { + "family": "warm-spectrum", + "slug": "magenta-rush", + "name": "Magenta Rush", + "swatches": ["#6B5CFF", "#E640D5", "#FF4E87"], + "hues": [248, 306, 339], + "usage": "Creator campaigns", + }, + { + "family": "warm-spectrum", + "slug": "cherry-pop", + "name": "Cherry Pop", + "swatches": ["#FF4AB8", "#FF3D6E", "#FF633D"], + "hues": [326, 344, 373], + "usage": "Bold social presence", + }, + { + "family": "warm-spectrum", + "slug": "crimson", + "name": "Crimson", + "swatches": ["#FF5B91", "#F52E62", "#E42E3D"], + "hues": [340, 346, 355], + "usage": "Dramatic editorial use", + }, + { + "family": "warm-spectrum", + "slug": "ember-gold", + "name": "Ember Gold", + "swatches": ["#FF476F", "#FF7738", "#FFD34E"], + "hues": [347, 379, 407], + "usage": "Events and launches", + }, + { + "family": "warm-spectrum", + "slug": "solar", + "name": "Solar", + "swatches": ["#FF6B3D", "#FFAA2D", "#FFE35C"], + "hues": [14, 36, 50], + "usage": "Optimistic promotion", + }, + { + "family": "luxe-neutral", + "slug": "rose-gold", + "name": "Rose Gold", + "swatches": ["#B75AFF", "#EC70A9", "#FFB37A"], + "hues": [279, 334, 385], + "usage": "Lifestyle premium", + }, + { + "family": "luxe-neutral", + "slug": "amethyst", + "name": "Amethyst", + "swatches": ["#6E5BFF", "#A85CFF", "#E475FF"], + "hues": [247, 273, 291], + "usage": "Best premium option", + "recommended": True, + }, + { + "family": "luxe-neutral", + "slug": "burgundy", + "name": "Burgundy", + "swatches": ["#B63D82", "#D94D70", "#ED765C"], + "hues": [326, 345, 369], + "usage": "Cinematic and editorial", + }, + { + "family": "luxe-neutral", + "slug": "copper", + "name": "Copper", + "swatches": ["#C95945", "#DE7B45", "#ECAE62"], + "hues": [9, 24, 38], + "usage": "Warm heritage premium", + }, + { + "family": "luxe-neutral", + "slug": "champagne", + "name": "Champagne", + "swatches": ["#D6A95F", "#E8C979", "#F7E6B0"], + "hues": [38, 44, 46], + "usage": "Luxury partnerships", + }, + { + "family": "luxe-neutral", + "slug": "graphite", + "name": "Graphite", + "swatches": ["#F3F5F7", "#AAB2BE", "#687381"], + "hues": [210, 215, 220], + "usage": "Monochrome system mark", + "monochrome": True, + }, +] + + +def hex_rgb(value): + value = value.lstrip("#") + return tuple(int(value[index:index + 2], 16) for index in (0, 2, 4)) + + +def mix_hex(first, second, amount): + a = np.array(hex_rgb(first), dtype=np.float32) + b = np.array(hex_rgb(second), dtype=np.float32) + return tuple(np.rint(a * (1 - amount) + b * amount).astype(np.uint8)) + + +def swatch_hsv(swatches): + converted = [] + for value in swatches: + red, green, blue = (channel / 255 for channel in hex_rgb(value)) + converted.append(colorsys.rgb_to_hsv(red, green, blue)) + return np.array(converted, dtype=np.float32) + + +def recolor(source, variant): + pixels = np.array(source.convert("RGBA"), dtype=np.uint8) + rgb = pixels[..., :3] + alpha = pixels[..., 3] + hsv = cv2.cvtColor(rgb.astype(np.float32) / 255, cv2.COLOR_RGB2HSV) + hue = hsv[..., 0] + saturation = hsv[..., 1] + value = hsv[..., 2] + mapped_rgb = rgb.copy() + if not variant.get("preserve"): + color_mask = (alpha > 0) & (saturation > 0.12) & (value > 0.22) + position = np.clip((hue - 184) / 110, 0, 1) + stops = np.array([0, 0.5, 1], dtype=np.float32) + target_hue = np.interp(position, stops, variant["hues"]) % 360 + targets = swatch_hsv(variant["swatches"]) + target_saturation = np.interp(position, stops, targets[:, 1]) + target_value = np.interp(position, stops, targets[:, 2]) + saturation_factor = np.clip((saturation / 0.69) ** 0.35, 0.72, 1.16) + value_factor = np.clip((value / 0.88) ** 0.65, 0.74, 1.1) + mapped_saturation = np.clip(target_saturation * saturation_factor, 0, 1) + if variant.get("monochrome"): + mapped_saturation = np.zeros_like(mapped_saturation) + mapped_value = np.clip(target_value * value_factor, 0, 1) + adjusted = hsv.copy() + adjusted[..., 0][color_mask] = target_hue[color_mask] + adjusted[..., 1][color_mask] = mapped_saturation[color_mask] + adjusted[..., 2][color_mask] = mapped_value[color_mask] + mapped_rgb = np.rint(cv2.cvtColor(adjusted, cv2.COLOR_HSV2RGB) * 255).astype(np.uint8) + dark_mask = (alpha > 0) & (value <= 0.22) + dark_level = np.clip(value / 0.118, 0.58, 1.35) + inner = np.array(hex_rgb(BLACK), dtype=np.float32) + mapped_rgb[dark_mask] = np.clip(inner * dark_level[dark_mask, None], 0, 255).astype(np.uint8) + pixels[..., :3] = mapped_rgb + return Image.fromarray(pixels, mode="RGBA") + + +def composite_black(logo): + background = Image.new("RGBA", logo.size, (0, 0, 0, 255)) + background.alpha_composite(logo) + return background.convert("RGB") + + +def fit_logo(logo, width, height): + bounds = logo.getchannel("A").getbbox() + cropped = logo.crop(bounds) + scale = min(width / cropped.width, height / cropped.height) + size = (round(cropped.width * scale), round(cropped.height * scale)) + return cropped.resize(size, Image.Resampling.LANCZOS) + + +def font(size): + return ImageFont.truetype(str(FONT_REGULAR), size) + + +def family_variants(slug): + return [variant for variant in VARIANTS if variant["family"] == slug] + + +def draw_swatch_row(draw, swatches, left, top, diameter, gap): + x = left + for swatch in swatches: + draw.ellipse( + (x, top, x + diameter, top + diameter), + fill=swatch, + outline=mix_hex(swatch, "#FFFFFF", 0.38), + width=2, + ) + x += diameter + gap + + +def draw_master(rendered): + canvas_width = 3080 + margin = 60 + rail_width = 250 + header_height = 220 + card_width = 430 + card_height = 420 + column_gap = 20 + row_gap = 30 + canvas_height = header_height + card_height * 4 + row_gap * 3 + margin + canvas = Image.new("RGB", (canvas_width, canvas_height), BLACK) + draw = ImageDraw.Draw(canvas) + draw.text((margin, 42), "NUVIO / COLOR SYSTEM", font=font(58), fill="#F7F7F7") + draw.text( + (margin, 120), + "24 curated logo colorways · pure black preview field · original geometry preserved", + font=font(26), + fill="#909090", + ) + rendered_by_slug = {variant["slug"]: logo for variant, logo in zip(VARIANTS, rendered)} + for family_index, family in enumerate(FAMILIES): + top = header_height + family_index * (card_height + row_gap) + draw.multiline_text( + (margin, top + 92), + family["label"], + font=font(34), + fill="#F7F7F7", + spacing=4, + ) + draw.multiline_text( + (margin, top + 188), + family["description"], + font=font(19), + fill="#777777", + spacing=4, + ) + for column, variant in enumerate(family_variants(family["slug"])): + left = margin + rail_width + column * (card_width + column_gap) + right = left + card_width + bottom = top + card_height + border = "#323232" if variant.get("recommended") else "#222222" + draw.rounded_rectangle((left, top, right, bottom), radius=24, fill=BLACK, outline=border, width=2) + logo = fit_logo(rendered_by_slug[variant["slug"]], 285, 285) + logo_x = left + (card_width - logo.width) // 2 + canvas.paste(logo, (logo_x, top + 18), logo) + draw.text((left + 24, top + 314), variant["name"], font=font(25), fill="#F4F4F4") + draw.text((left + 24, top + 350), variant["usage"], font=font(16), fill="#777777") + draw_swatch_row(draw, variant["swatches"], left + 24, top + 382, 22, 8) + if variant.get("recommended"): + badge_width = 116 + badge_left = right - badge_width - 18 + draw.rounded_rectangle( + (badge_left, top + 374, right - 18, top + 402), + radius=14, + fill="#F4F4F4", + ) + draw.text((badge_left + 12, top + 379), "DESIGNER PICK", font=font(12), fill=BLACK) + canvas.save(OUTPUT / "Nuvio-Color-System-Master.png", optimize=True) + + +def draw_family_board(family, rendered_by_slug): + canvas_width = 2280 + margin = 60 + gap = 30 + header_height = 220 + card_width = 700 + card_height = 560 + canvas_height = header_height + card_height * 2 + gap + margin + canvas = Image.new("RGB", (canvas_width, canvas_height), BLACK) + draw = ImageDraw.Draw(canvas) + draw.text((margin, 44), f"NUVIO / {family['name'].upper()}", font=font(58), fill="#F7F7F7") + draw.text( + (margin, 124), + "Six curated logo shades shown on pure black #000000", + font=font(26), + fill="#909090", + ) + for index, variant in enumerate(family_variants(family["slug"])): + row, column = divmod(index, 3) + left = margin + column * (card_width + gap) + top = header_height + row * (card_height + gap) + right = left + card_width + bottom = top + card_height + border = "#3A3A3A" if variant.get("recommended") else "#242424" + draw.rounded_rectangle((left, top, right, bottom), radius=32, fill=BLACK, outline=border, width=2) + logo = fit_logo(rendered_by_slug[variant["slug"]], 390, 390) + logo_x = left + (card_width - logo.width) // 2 + canvas.paste(logo, (logo_x, top + 20), logo) + draw.text((left + 34, top + 420), variant["name"], font=font(32), fill="#F7F7F7") + draw.text((left + 34, top + 464), variant["usage"], font=font(19), fill="#838383") + draw_swatch_row(draw, variant["swatches"], right - 34 - 138, top + 458, 38, 12) + if variant.get("recommended"): + draw.rounded_rectangle((left + 34, top + 504, left + 174, top + 536), radius=16, fill="#F2F2F2") + draw.text((left + 48, top + 510), "DESIGNER PICK", font=font(14), fill=BLACK) + canvas.save(OUTPUT / "previews" / f"{family['slug']}.png", optimize=True) + + +def write_guide(): + lines = [ + "# Nuvio logo color system", + "", + "This set contains 24 professionally curated colorways across four usage families. Every preview uses pure black `#000000`. Logo geometry, transparency, highlights, overlap shading, and the white play glyph are preserved.", + "", + "## Designer recommendation", + "", + "- Use **Arctic Blue** as the strongest evolution of the master brand.", + "- Use **Tidal** when the product needs a fresher, more distinctive identity.", + "- Use **Amethyst** for premium positioning and **Graphite** for monochrome system contexts.", + "- Reserve **Sunset**, **Ember Gold**, and the brighter warm variants for campaigns, launches, and seasonal moments.", + "", + "| Family | Colorway | Gradient anchors | Recommended use |", + "| --- | --- | --- | --- |", + ] + family_names = {family["slug"]: family["name"] for family in FAMILIES} + for variant in VARIANTS: + anchors = " → ".join(variant["swatches"]) + lines.append(f"| {family_names[variant['family']]} | {variant['name']} | {anchors} | {variant['usage']} |") + lines.extend( + [ + "", + "Each colorway is supplied as a transparent 1080×1080 PNG and a matching 1080×1080 PNG on pure black.", + "", + "The source is `asset/Deliverables 2/Logo Only/Logo_1080x1080.png`.", + ] + ) + (OUTPUT / "README.md").write_text("\n".join(lines) + "\n", encoding="utf-8") + + +def main(): + transparent_dir = OUTPUT / "transparent" + black_dir = OUTPUT / "black-background" + preview_dir = OUTPUT / "previews" + transparent_dir.mkdir(parents=True, exist_ok=True) + black_dir.mkdir(parents=True, exist_ok=True) + preview_dir.mkdir(parents=True, exist_ok=True) + source = Image.open(SOURCE).convert("RGBA") + rendered = [] + for index, variant in enumerate(VARIANTS, start=1): + logo = recolor(source, variant) + filename = f"{index:02d}-{variant['slug']}.png" + logo.save(transparent_dir / filename, optimize=True) + composite_black(logo).save(black_dir / filename, optimize=True) + rendered.append(logo) + rendered_by_slug = {variant["slug"]: logo for variant, logo in zip(VARIANTS, rendered)} + draw_master(rendered) + for family in FAMILIES: + draw_family_board(family, rendered_by_slug) + write_guide() + + +if __name__ == "__main__": + main()