├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── font │ │ │ │ ├── crimson_text_bold.ttf │ │ │ │ ├── crimson_text_regular.ttf │ │ │ │ ├── crimson_text_semibold.ttf │ │ │ │ ├── source_serif_pro_bold.ttf │ │ │ │ ├── source_serif_pro_regular.ttf │ │ │ │ └── source_serif_pro_semibold.ttf │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── xml │ │ │ │ ├── provider_paths.xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── values │ │ │ │ ├── strings_no_translate.xml │ │ │ │ ├── themes.xml │ │ │ │ └── strings.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable │ │ │ │ ├── ic_arrow_back_24.xml │ │ │ │ ├── baseline_close_24.xml │ │ │ │ ├── ic_laptop_24.xml │ │ │ │ ├── outline_launch_24.xml │ │ │ │ ├── round_keyboard_arrow_down_24.xml │ │ │ │ ├── ic_patreon_24.xml │ │ │ │ ├── ic_image_24.xml │ │ │ │ ├── outline_photo_size_select_large_24.xml │ │ │ │ ├── ic_arrow_clockwise_24.xml │ │ │ │ ├── outline_auto_awesome_24.xml │ │ │ │ ├── ic_exclamation_octagon_24.xml │ │ │ │ ├── ic_github_24.xml │ │ │ │ ├── ic_sun_24.xml │ │ │ │ ├── ic_telegram_24.xml │ │ │ │ ├── ic_moon_stars_24.xml │ │ │ │ └── ic_gear_24.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ ├── values-v27 │ │ │ │ └── themes.xml │ │ │ ├── drawable-anydpi-v26 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── values-zh-rCN │ │ │ │ └── strings.xml │ │ │ ├── values-tr │ │ │ │ └── strings.xml │ │ │ ├── values-in │ │ │ │ └── strings.xml │ │ │ ├── values-el │ │ │ │ └── strings.xml │ │ │ └── values-ru │ │ │ │ └── strings.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── zhenxiang │ │ │ │ └── superimage │ │ │ │ ├── utils │ │ │ │ ├── MimeType.kt │ │ │ │ ├── ContextUtils.kt │ │ │ │ ├── StringUtils.kt │ │ │ │ ├── FileUtils.kt │ │ │ │ ├── BitmapUtils.kt │ │ │ │ ├── TimeUtils.kt │ │ │ │ └── IntentUtils.kt │ │ │ │ ├── ui │ │ │ │ ├── utils │ │ │ │ │ ├── ConstraintUtils.kt │ │ │ │ │ ├── RowUtils.kt │ │ │ │ │ └── PaddingUtils.kt │ │ │ │ ├── daynight │ │ │ │ │ ├── DayNightModule.kt │ │ │ │ │ ├── DayNightManager.kt │ │ │ │ │ └── DayNightMode.kt │ │ │ │ ├── theme │ │ │ │ │ ├── Elevation.kt │ │ │ │ │ ├── Shape.kt │ │ │ │ │ ├── Spacing.kt │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Border.kt │ │ │ │ │ ├── Theme.kt │ │ │ │ │ └── Type.kt │ │ │ │ ├── Density.kt │ │ │ │ ├── mono │ │ │ │ │ ├── Border.kt │ │ │ │ │ ├── Radio.kt │ │ │ │ │ ├── Text.kt │ │ │ │ │ ├── BlurShadowProvider.kt │ │ │ │ │ ├── Button.kt │ │ │ │ │ ├── AppBar.kt │ │ │ │ │ └── BlurShadowImage.kt │ │ │ │ └── form │ │ │ │ │ └── TextField.kt │ │ │ │ ├── work │ │ │ │ └── RealESRGANWorkerModule.kt │ │ │ │ ├── intent │ │ │ │ ├── InputImageIntentManagerModule.kt │ │ │ │ └── InputImageIntentManager.kt │ │ │ │ ├── datastore │ │ │ │ ├── DataStoreUtils.kt │ │ │ │ └── DataStoreModule.kt │ │ │ │ ├── home │ │ │ │ ├── InputImageUtils.kt │ │ │ │ └── Banner.kt │ │ │ │ ├── IntentActivity.kt │ │ │ │ ├── tracking │ │ │ │ ├── AppTrackingModule.kt │ │ │ │ ├── AppReviewTracking.kt │ │ │ │ └── AppVersionTracking.kt │ │ │ │ ├── navigation │ │ │ │ ├── RootNavigation.kt │ │ │ │ ├── ChildComponent.kt │ │ │ │ └── RootComponent.kt │ │ │ │ ├── settings │ │ │ │ ├── PreferenceState.kt │ │ │ │ └── SettingsPageComponent.kt │ │ │ │ ├── SuperImageWorkManagerInitializer.kt │ │ │ │ ├── SuperImageApplication.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── zhenxiang │ │ │ └── superimage │ │ │ └── ExampleInstrumentedTest.kt │ └── test │ │ └── java │ │ └── com │ │ └── zhenxiang │ │ └── superimage │ │ └── utils │ │ ├── FIleUtilsTest.kt │ │ └── StringUtilsTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── shared ├── .gitignore ├── src │ └── commonMain │ │ └── kotlin │ │ └── com │ │ └── zhenxiang │ │ └── superimage │ │ └── shared │ │ └── model │ │ ├── DataResult.kt │ │ ├── InputImage.kt │ │ ├── Changelog.kt │ │ ├── DataState.kt │ │ └── OutputFormat.kt └── build.gradle.kts ├── decompose ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── com │ │ └── arkivanov │ │ ├── decompose │ │ └── extensions │ │ │ └── compose │ │ │ └── CompositionLocals.kt │ │ └── essenty │ │ └── lifecycle │ │ └── ext │ │ ├── FlowExt.kt │ │ ├── LifecycleEvent.kt │ │ └── LifecycleExt.kt └── build.gradle.kts ├── common-models ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── zhenxiang │ └── superimage │ └── common │ └── Identifiable.kt ├── playstore ├── base │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── zhenxiang │ │ │ └── superimage │ │ │ └── playstore │ │ │ └── base │ │ │ ├── BaseAppTracker.kt │ │ │ └── BaseInAppReviewManager.kt │ ├── proguard-rules.pro │ └── build.gradle.kts ├── impl │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── zhenxiang │ │ │ │ └── superimage │ │ │ │ └── playstore │ │ │ │ ├── AppTracker.kt │ │ │ │ └── InAppReviewManager.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── zhenxiang │ │ │ └── superimage │ │ │ └── playstore │ │ │ └── InAppReviewManagerTest.kt │ ├── proguard-rules.pro │ └── build.gradle.kts └── no-op │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── zhenxiang │ │ └── superimage │ │ └── playstore │ │ ├── InAppReviewManager.kt │ │ └── AppTracker.kt │ ├── proguard-rules.pro │ └── build.gradle.kts ├── realesrgan ├── android │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ │ └── java │ │ │ └── com │ │ │ └── zhenxiang │ │ │ └── realesrgan │ │ │ ├── InterpreterError.kt │ │ │ ├── RealESRGAN.kt │ │ │ ├── UpscalingModel.kt │ │ │ └── JNIProgressTracker.kt │ ├── proguard-rules.pro │ └── build.gradle └── core │ ├── .gitignore │ ├── image_dimensions.h │ ├── jni_common │ ├── mnn_model.cpp │ ├── coroutine_utils.h │ ├── mnn_model.h │ ├── progress_tracker.h │ ├── progress_tracker.cpp │ └── coroutine_utils.cpp │ ├── android │ ├── bitmap_utils.h │ ├── bitmap_utils.cpp │ └── realesrgan_jni.cpp │ ├── desktop │ ├── image_utils.h │ ├── image_utils.cpp │ └── realesrgan_jni.cpp │ ├── upscaling.h │ ├── image_tile_interpreter.h │ ├── image_tile_interpreter.cpp │ └── CMakeLists.txt ├── .idea ├── .gitignore ├── artifacts │ └── shared_desktop.xml ├── misc.xml ├── compiler.xml ├── vcs.xml ├── gradle.xml └── inspectionProfiles │ └── Project_Default.xml ├── fastlane └── metadata │ └── android │ └── en-US │ ├── changelogs │ ├── 134.txt │ ├── 124.txt │ ├── 120.txt │ ├── 122.txt │ ├── 121.txt │ ├── 123.txt │ ├── 131.txt │ └── 130.txt │ ├── short_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── cover.png │ │ ├── screenshot_dark.png │ │ ├── screenshot_light.png │ │ ├── Screenshot_20230205-224043_SuperImage.png │ │ └── Screenshot_20230205-224055_SuperImage.png │ └── full_description.txt ├── assets ├── sample_1.jpg ├── sample_2.jpg └── sample_3.jpg ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .gitmodules ├── settings.gradle ├── gradle.properties ├── .github └── workflows │ └── release.yml ├── README.md └── gradlew.bat /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /shared/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /decompose/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common-models/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /playstore/base/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /playstore/base/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playstore/impl/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /playstore/impl/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playstore/no-op/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /playstore/no-op/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /realesrgan/android/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /realesrgan/android/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /realesrgan/core/.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /build 3 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/134.txt: -------------------------------------------------------------------------------- 1 | Fixed crash on 32 bit devices -------------------------------------------------------------------------------- /assets/sample_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/assets/sample_1.jpg -------------------------------------------------------------------------------- /assets/sample_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/assets/sample_2.jpg -------------------------------------------------------------------------------- /assets/sample_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/assets/sample_3.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Sharpen your low-resolution pictures with the power of AI upscaling -------------------------------------------------------------------------------- /app/src/main/res/font/crimson_text_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/font/crimson_text_bold.ttf -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/124.txt: -------------------------------------------------------------------------------- 1 | Updated Greek translations 2 | Fixed crash when required Vulkan features not supported -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/font/crimson_text_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/font/crimson_text_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/crimson_text_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/font/crimson_text_semibold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/source_serif_pro_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/font/source_serif_pro_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /app/src/main/res/font/source_serif_pro_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/font/source_serif_pro_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/font/source_serif_pro_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/font/source_serif_pro_semibold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/utils/MimeType.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | object MimeType { 4 | 5 | const val IMAGE = "image/*" 6 | } 7 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/120.txt: -------------------------------------------------------------------------------- 1 | Reduced memory consumption 2 | Support receiving input image shared from other apps 3 | Added link to Telegram group in settings -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/122.txt: -------------------------------------------------------------------------------- 1 | Added Greek translations 2 | Added Material You themed icon 3 | Fixed incorrect output filename suffix 4 | User interface fixes -------------------------------------------------------------------------------- /playstore/base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playstore/impl/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/cover.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot_dark.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot_light.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/121.txt: -------------------------------------------------------------------------------- 1 | Reduced memory consumption 2 | Fixed receiving input image shared from other apps 3 | Add suffix to output image's filename 4 | Fixed crash when device is not compatible -------------------------------------------------------------------------------- /common-models/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'org.jetbrains.kotlin.jvm' 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_7 8 | targetCompatibility = JavaVersion.VERSION_1_7 9 | } -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/Screenshot_20230205-224043_SuperImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/Screenshot_20230205-224043_SuperImage.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/Screenshot_20230205-224055_SuperImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T8RIN/SuperImage/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/Screenshot_20230205-224055_SuperImage.png -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/utils/ConstraintUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.utils 2 | 3 | import androidx.compose.ui.unit.Constraints 4 | 5 | val Constraints.isLandscape: Boolean 6 | get() = maxWidth > maxHeight 7 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/123.txt: -------------------------------------------------------------------------------- 1 | Use Source Serif Pro font for Greek language 2 | Fixed crash when opening output image below Android 10 3 | Fixed crash when Vulkan compute not supported 4 | Fixed crash when estimated time is invalid -------------------------------------------------------------------------------- /app/src/main/res/values/strings_no_translate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | crimson 4 | source 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/work/RealESRGANWorkerModule.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.work 2 | 3 | import org.koin.core.module.dsl.singleOf 4 | import org.koin.dsl.module 5 | 6 | val RealESRGANWorkerModule = module { 7 | singleOf(::RealESRGANWorkerManager) 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 17 14:03:42 CET 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/intent/InputImageIntentManagerModule.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.intent 2 | 3 | import org.koin.core.module.dsl.singleOf 4 | import org.koin.dsl.module 5 | 6 | val InputImageIntentManagerModule = module { 7 | singleOf(::InputImageIntentManager) 8 | } 9 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/zhenxiang/superimage/shared/model/DataResult.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.shared.model 2 | 3 | sealed class DataResult { 4 | 5 | data class Success(val data: T): DataResult() 6 | 7 | data class Error(val error: E) : DataResult() 8 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/zhenxiang/superimage/shared/model/InputImage.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.shared.model 2 | 3 | import java.io.File 4 | 5 | data class InputImage( 6 | val fileName: String, 7 | val tempFile: File, 8 | val width: Int, 9 | val height: Int 10 | ) 11 | -------------------------------------------------------------------------------- /realesrgan/core/image_dimensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 02/03/23. 3 | // 4 | 5 | #ifndef SUPERIMAGE_IMAGE_DIMENSIONS_H 6 | #define SUPERIMAGE_IMAGE_DIMENSIONS_H 7 | 8 | struct image_dimensions { 9 | int width; 10 | int height; 11 | }; 12 | 13 | #endif //SUPERIMAGE_IMAGE_DIMENSIONS_H 14 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/zhenxiang/superimage/shared/model/Changelog.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.shared.model 2 | 3 | sealed class Changelog { 4 | 5 | object Loading : Changelog() 6 | 7 | data class Show(val items: List) : Changelog() 8 | 9 | object Hide : Changelog() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/daynight/DayNightModule.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.daynight 2 | 3 | import com.zhenxiang.superimage.datastore.SETTINGS_DATA_STORE_QUALIFIER 4 | import org.koin.dsl.module 5 | 6 | val DayNightModule = module { 7 | single { DayNightManager(get(SETTINGS_DATA_STORE_QUALIFIER)) } 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/theme/Elevation.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.theme 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.ui.unit.dp 5 | 6 | object Elevation { 7 | 8 | val container = 5.dp 9 | } 10 | 11 | val MaterialTheme.elevation: Elevation 12 | get() = Elevation 13 | -------------------------------------------------------------------------------- /realesrgan/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/artifacts/shared_desktop.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/shared/build/libs 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | SuperImage is a neural network-based image upscaling build with the MNN deep learning framework and the Real-ESRGAN algorithm. 2 | 3 | By leveraging the power of your device's GPU, SuperImage is able to upscale and restore the details of your images without uploading them to the internet, keeping your data secure. -------------------------------------------------------------------------------- /realesrgan/core/jni_common/mnn_model.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 06/02/23. 3 | // 4 | 5 | #include "mnn_model.h" 6 | 7 | mnn_model mnn_model_from_jbytes(JNIEnv* env, jbyteArray& jarray) { 8 | return { 9 | .data = env->GetByteArrayElements(jarray, nullptr), 10 | .size = env->GetArrayLength(jarray) 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/zhenxiang/superimage/shared/model/DataState.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.shared.model 2 | 3 | sealed class DataState { 4 | 5 | data class Success(val data: T): DataState() 6 | 7 | class Loading : DataState() 8 | 9 | data class Error(val error: E) : DataState() 10 | } -------------------------------------------------------------------------------- /realesrgan/core/jni_common/coroutine_utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 08/02/23. 3 | // 4 | 5 | #ifndef SUPERIMAGE_COROUTINE_UTILS_H 6 | #define SUPERIMAGE_COROUTINE_UTILS_H 7 | 8 | #include 9 | 10 | extern "C" JNIEXPORT bool JNICALL 11 | is_coroutine_scope_active(JNIEnv* env, jobject coroutine_scope); 12 | 13 | #endif //SUPERIMAGE_COROUTINE_UTILS_H 14 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /playstore/no-op/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "realesrgan/core/MNN"] 2 | path = realesrgan/core/MNN 3 | url = https://github.com/Lucchetto/MNN 4 | [submodule "realesrgan/core/eigen"] 5 | path = realesrgan/core/eigen 6 | url = https://gitlab.com/libeigen/eigen 7 | [submodule "realesrgan/android/src/main/assets"] 8 | path = realesrgan/android/src/main/assets 9 | url = https://github.com/Lucchetto/Real-ESRGAN-models 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/utils/RowUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.utils 2 | 3 | import androidx.compose.foundation.layout.RowScope 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | 8 | @Composable 9 | fun RowScope.RowSpacer() = Spacer(modifier = Modifier.weight(1f)) 10 | -------------------------------------------------------------------------------- /playstore/base/src/main/java/com/zhenxiang/superimage/playstore/base/BaseAppTracker.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.playstore.base 2 | 3 | import android.content.Context 4 | 5 | abstract class BaseAppTracker(context: Context) { 6 | 7 | abstract fun trackInstall() 8 | 9 | abstract fun trackViewScreen(screenId: String) 10 | 11 | abstract fun trackAction(actionId: String) 12 | } 13 | -------------------------------------------------------------------------------- /realesrgan/core/android/bitmap_utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 18/02/23. 3 | // 4 | 5 | #ifndef SUPERIMAGE_BITMAP_UTILS_H 6 | #define SUPERIMAGE_BITMAP_UTILS_H 7 | 8 | #include 9 | #include "../image_dimensions.h" 10 | 11 | extern "C" JNIEXPORT image_dimensions JNICALL 12 | get_bitmap_dimensions(JNIEnv* env, jobject bitmap); 13 | 14 | #endif //SUPERIMAGE_BITMAP_UTILS_H 15 | -------------------------------------------------------------------------------- /realesrgan/core/jni_common/mnn_model.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 06/02/23. 3 | // 4 | 5 | #ifndef SUPERIMAGE_MNN_MODEL_H 6 | #define SUPERIMAGE_MNN_MODEL_H 7 | 8 | #include "jni.h" 9 | 10 | struct mnn_model { 11 | int8_t* data; 12 | long size; 13 | }; 14 | 15 | mnn_model mnn_model_from_jbytes(JNIEnv* env, jbyteArray& jarray); 16 | 17 | #endif //SUPERIMAGE_MNN_MODEL_H 18 | -------------------------------------------------------------------------------- /decompose/src/main/java/com/arkivanov/decompose/extensions/compose/CompositionLocals.kt: -------------------------------------------------------------------------------- 1 | package com.arkivanov.decompose.extensions.compose 2 | 3 | import androidx.compose.runtime.staticCompositionLocalOf 4 | import com.arkivanov.essenty.lifecycle.LifecycleOwner 5 | 6 | val LocalLifecycleOwner = staticCompositionLocalOf { 7 | error("CompositionLocal LocalLifecycleOwner not present") 8 | } 9 | -------------------------------------------------------------------------------- /common-models/src/main/java/com/zhenxiang/superimage/common/Identifiable.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.common 2 | 3 | interface Identifiable> { 4 | 5 | val id: T 6 | 7 | interface EnumCompanion where T : Enum, T: Identifiable<*> { 8 | 9 | val VALUES: Array 10 | 11 | fun fromId(id: Int?) = id?.let { VALUES.firstOrNull { item -> item.id == it } } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_close_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/131.txt: -------------------------------------------------------------------------------- 1 | The desktop version is just around the corner, stay tuned ! 2 | Added Indonesian translations 3 | Added cancel button to progress notification 4 | Fixed open output image button not working in some cases 5 | Fixed receiving input image shared from other apps 6 | Fixed progress details not visible in notification below Android 12 7 | Fixed duplicated file extension in output image on some Android versions -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/130.txt: -------------------------------------------------------------------------------- 1 | FIXED LABEL FOR 8X MODE, THE ACTUAL UPSCALING IS 16X 2 | Added Chinese and Russian translations 3 | Added warning when system memory may be insufficient 4 | Added estimated completion time to progress notification 5 | Fixed output orientation when upscaling some images 6 | Fixed receiving input image shared from other apps 7 | Fixed crash when input image has one side smaller than 84px 8 | User interface fixes -------------------------------------------------------------------------------- /playstore/base/src/main/java/com/zhenxiang/superimage/playstore/base/BaseInAppReviewManager.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.playstore.base 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | 6 | abstract class BaseInAppReviewManager(context: Context) { 7 | 8 | abstract val reviewFlowReady: Boolean 9 | 10 | abstract fun prepareReviewInfo() 11 | 12 | abstract fun launchReviewFlow(activity: Activity) 13 | } -------------------------------------------------------------------------------- /realesrgan/core/jni_common/progress_tracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 22/12/22. 3 | // 4 | 5 | #ifndef REALESRGAN_PROGRESS_TRACKER_H 6 | #define REALESRGAN_PROGRESS_TRACKER_H 7 | 8 | #include 9 | #include 10 | 11 | extern "C" JNIEXPORT void JNICALL 12 | set_progress_percentage(JNIEnv* env, jobject progress_tracker, float percentage, int64_t estimated_time = -1); 13 | 14 | #endif //REALESRGAN_PROGRESS_TRACKER_H 15 | -------------------------------------------------------------------------------- /playstore/impl/src/main/java/com/zhenxiang/superimage/playstore/AppTracker.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.playstore 2 | 3 | import android.content.Context 4 | import com.zhenxiang.superimage.playstore.base.BaseAppTracker 5 | 6 | class AppTracker(context: Context): BaseAppTracker(context) { 7 | 8 | override fun trackInstall() = Unit 9 | 10 | override fun trackViewScreen(screenId: String) = Unit 11 | 12 | override fun trackAction(actionId: String) = Unit 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/datastore/DataStoreUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.datastore 2 | 3 | import androidx.datastore.core.DataStore 4 | import androidx.datastore.preferences.core.Preferences 5 | import androidx.datastore.preferences.core.edit 6 | import com.zhenxiang.superimage.common.Identifiable 7 | 8 | suspend fun DataStore.writeIntIdentifiable(key: Preferences.Key, value: Identifiable) { 9 | edit { it[key] = value.id } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_laptop_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_launch_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | extraSmall = RoundedCornerShape(2.dp), 9 | small = RoundedCornerShape(6.dp), 10 | medium = RoundedCornerShape(8.dp), 11 | large = RoundedCornerShape(16.dp), 12 | extraLarge = RoundedCornerShape(32.dp), 13 | ) 14 | -------------------------------------------------------------------------------- /realesrgan/android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | x4 (generic) 8 | x16 (generic) 9 | x16 (drawing) 10 | -------------------------------------------------------------------------------- /realesrgan/core/android/bitmap_utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 18/02/23. 3 | // 4 | 5 | #include "bitmap_utils.h" 6 | 7 | extern "C" JNIEXPORT image_dimensions JNICALL 8 | get_bitmap_dimensions(JNIEnv* env, jobject bitmap) { 9 | jclass clazz = env->GetObjectClass(bitmap); 10 | return { 11 | .width = env->CallIntMethod(bitmap, env->GetMethodID(clazz, "getWidth", "()I")), 12 | .height = env->CallIntMethod(bitmap, env->GetMethodID(clazz, "getHeight", "()I")) 13 | }; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /realesrgan/core/desktop/image_utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 26/02/23. 3 | // 4 | 5 | #ifndef SUPERIMAGE_IMAGE_UTILS_H 6 | #define SUPERIMAGE_IMAGE_UTILS_H 7 | 8 | #include 9 | #include "../image_dimensions.h" 10 | 11 | extern "C" JNIEXPORT image_dimensions JNICALL 12 | get_image_dimensions(JNIEnv* env, jobject buffered_image); 13 | 14 | extern "C" JNIEXPORT jintArray JNICALL 15 | get_rgb(JNIEnv* env, jobject buffered_image, image_dimensions dimens); 16 | 17 | #endif //SUPERIMAGE_IMAGE_UTILS_H 18 | -------------------------------------------------------------------------------- /realesrgan/core/jni_common/progress_tracker.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 22/12/22. 3 | // 4 | 5 | #include "progress_tracker.h" 6 | 7 | extern "C" JNIEXPORT void JNICALL 8 | set_progress_percentage(JNIEnv* env, jobject progress_tracker, float percentage, int64_t estimated_time) { 9 | jclass clazz = env->GetObjectClass(progress_tracker); 10 | jmethodID method = env->GetMethodID(clazz, "setProgress", "(FJ)V"); 11 | env->CallVoidMethod(progress_tracker, method, percentage, estimated_time); 12 | } 13 | -------------------------------------------------------------------------------- /playstore/no-op/src/main/java/com/zhenxiang/superimage/playstore/InAppReviewManager.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.playstore 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import com.zhenxiang.superimage.playstore.base.BaseInAppReviewManager 6 | 7 | class InAppReviewManager(context: Context): BaseInAppReviewManager(context) { 8 | 9 | override val reviewFlowReady = false 10 | 11 | override fun prepareReviewInfo() {} 12 | 13 | override fun launchReviewFlow(activity: Activity) {} 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/Density.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.platform.LocalDensity 5 | import androidx.compose.ui.unit.Dp 6 | import kotlin.math.roundToInt 7 | 8 | /** 9 | * Convert an [Int] pixel value to [Dp]. 10 | */ 11 | @Composable 12 | fun Int.pxToDp(): Dp = with(LocalDensity.current) { this@pxToDp.toDp() } 13 | 14 | @Composable 15 | fun Dp.dpToPx(): Float = with(LocalDensity.current) { this@dpToPx.toPx() } 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_keyboard_arrow_down_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/theme/Spacing.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.theme 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.ui.unit.dp 5 | 6 | object Spacing { 7 | 8 | val level1 = 2.dp 9 | val level2 = 4.dp 10 | val level3 = 8.dp 11 | val level4 = 12.dp 12 | val level5 = 16.dp 13 | val level6 = 24.dp 14 | val level7 = 32.dp 15 | val level8 = 40.dp 16 | val level9 = 48.dp 17 | } 18 | 19 | val MaterialTheme.spacing: Spacing 20 | get() = Spacing 21 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_patreon_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /decompose/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("org.jetbrains.compose") 4 | id("org.jetbrains.kotlin.jvm") 5 | } 6 | 7 | java { 8 | sourceCompatibility = JavaVersion.VERSION_1_8 9 | targetCompatibility = JavaVersion.VERSION_1_8 10 | } 11 | 12 | dependencies { 13 | 14 | val decompose_version = "1.0.0" 15 | 16 | api("com.arkivanov.decompose:decompose:$decompose_version") 17 | api("com.arkivanov.decompose:extensions-compose-jetbrains:$decompose_version") 18 | implementation(compose.runtime) 19 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") 20 | } 21 | -------------------------------------------------------------------------------- /realesrgan/core/jni_common/coroutine_utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 08/02/23. 3 | // 4 | 5 | #include "coroutine_utils.h" 6 | 7 | extern "C" JNIEXPORT bool JNICALL 8 | is_coroutine_scope_active(JNIEnv* env, jobject coroutine_scope) { 9 | jclass coroutine_scope_kt_clazz = env->FindClass("kotlinx/coroutines/CoroutineScopeKt"); 10 | jmethodID method = env->GetStaticMethodID( 11 | coroutine_scope_kt_clazz, 12 | "isActive", 13 | "(Lkotlinx/coroutines/CoroutineScope;)Z"); 14 | return env->CallStaticBooleanMethod(coroutine_scope_kt_clazz, method, coroutine_scope); 15 | } 16 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_image_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_photo_size_select_large_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url 'https://jitpack.io' } 14 | } 15 | } 16 | rootProject.name = "SuperImage" 17 | include ':app' 18 | include ':common-models' 19 | include ':playstore:impl' 20 | include ':playstore:no-op' 21 | include ':playstore:base' 22 | include ':decompose' 23 | include ':shared' 24 | include ':realesrgan:android' 25 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values-v27/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/home/InputImageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.home 2 | 3 | import com.zhenxiang.realesrgan.UpscalingModel 4 | import com.zhenxiang.superimage.shared.model.InputImage 5 | 6 | fun InputImage.mayRunOutOfMemory(model: UpscalingModel): Boolean { 7 | // TODO: Remove hardcoded 4 channels 8 | val estimatedBytesSize = width * height * 4L 9 | val nonAllocatedMemory = Runtime.getRuntime().let { 10 | // totalMemory - freeMemory is the currently used memory in JVM 11 | it.maxMemory() - (it.totalMemory() - it.freeMemory()) 12 | } 13 | return estimatedBytesSize * (model.scale * model.scale + 1) > nonAllocatedMemory 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/datastore/DataStoreModule.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.datastore 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.preferencesDataStore 7 | import org.koin.core.qualifier.qualifier 8 | import org.koin.dsl.module 9 | 10 | val SETTINGS_DATA_STORE_QUALIFIER = qualifier("settings") 11 | 12 | private val Context.dataStore: DataStore by preferencesDataStore(name = SETTINGS_DATA_STORE_QUALIFIER.value) 13 | 14 | val DataStoreModule = module { 15 | single(SETTINGS_DATA_STORE_QUALIFIER) { get().dataStore } 16 | } 17 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/zhenxiang/superimage/shared/model/OutputFormat.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.shared.model 2 | 3 | import com.zhenxiang.superimage.common.Identifiable 4 | 5 | enum class OutputFormat( 6 | override val id: Int, 7 | val formatExtension: String, 8 | val formatName: String, 9 | val mimeType: String 10 | ): Identifiable { 11 | 12 | JPEG(0, "jpg", "JPEG", "image/jpeg"), 13 | PNG(1, "png", "PNG", "image/png"); 14 | 15 | companion object: Identifiable.EnumCompanion { 16 | 17 | override val VALUES = values() 18 | 19 | fun fromFormatName(name: String): OutputFormat? = VALUES.firstOrNull { it.formatName == name } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_clockwise_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /realesrgan/android/src/main/java/com/zhenxiang/realesrgan/InterpreterError.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.realesrgan 2 | 3 | enum class InterpreterError { 4 | 5 | UNKNOWN, 6 | 7 | /** 8 | * MNN model failed to load 9 | */ 10 | CREATE_INTERPRETER, 11 | 12 | /** 13 | * Required MNN backends not available or invalid config 14 | */ 15 | CREATE_SESSION; 16 | 17 | companion object { 18 | 19 | /** 20 | * Convert from ImageTileInterpreterException::Error value to enum 21 | */ 22 | fun fromNativeErrorEnum(value: Int?) = when(value) { 23 | 1 -> CREATE_INTERPRETER 24 | 2 -> CREATE_SESSION 25 | else -> UNKNOWN 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /realesrgan/android/src/main/java/com/zhenxiang/realesrgan/RealESRGAN.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.realesrgan 2 | 3 | import android.graphics.Bitmap 4 | import kotlinx.coroutines.CoroutineScope 5 | 6 | class RealESRGAN { 7 | 8 | external fun runUpscaling( 9 | progressTracker: JNIProgressTracker, 10 | coroutineScope: CoroutineScope, 11 | modelData: ByteArray, 12 | scale: Int, 13 | inputBitmap: Bitmap, 14 | outputBitmap: Bitmap 15 | ): Int 16 | 17 | companion object { 18 | // Used to load the 'realesrgan' library on application startup. 19 | init { 20 | System.loadLibrary("MNN_VK") 21 | System.loadLibrary("MNN_CL") 22 | System.loadLibrary("realesrgan") 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /realesrgan/android/src/main/java/com/zhenxiang/realesrgan/UpscalingModel.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.realesrgan 2 | 3 | import com.zhenxiang.superimage.common.Identifiable 4 | 5 | enum class UpscalingModel( 6 | override val id: Int, 7 | val labelRes: Int, 8 | val assetPath: String, 9 | val scale: Int, 10 | val fileNameSuffix: String, 11 | ): Identifiable { 12 | X2_PLUS(0, R.string.x2_plus_model_label, "realesrgan-x2plus.mnn", 2, "_x4"), 13 | X4_PLUS(1, R.string.x4_plus_model_label, "realesrgan-x4plus.mnn", 4, "_x16"), 14 | X4_PLUS_ANIME(2, R.string.x4_plus_anime_model_label, "realesrgan-x4plus-anime.mnn", 4, "_x16_drawing"); 15 | 16 | companion object: Identifiable.EnumCompanion { 17 | override val VALUES = values() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_auto_awesome_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/daynight/DayNightManager.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.daynight 2 | 3 | import androidx.datastore.core.DataStore 4 | import androidx.datastore.preferences.core.Preferences 5 | import androidx.datastore.preferences.core.intPreferencesKey 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.map 8 | 9 | class DayNightManager(private val dataStore: DataStore) { 10 | 11 | private val themeModeKey = intPreferencesKey("theme_mode") 12 | 13 | val themeModeFlow: Flow 14 | get() = dataStore.data.map { prefs -> 15 | DayNightMode.fromId(prefs[themeModeKey]) ?: DayNightMode.AUTO 16 | } 17 | 18 | companion object { 19 | 20 | const val THEME_MODE_KEY = "theme_mode" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/IntentActivity.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import com.zhenxiang.superimage.intent.InputImageIntentManager 7 | import org.koin.android.ext.android.inject 8 | 9 | /** 10 | * Empty activity used to receive input image intents and route to [MainActivity] 11 | */ 12 | class IntentActivity : Activity() { 13 | 14 | private val inputImageIntentManager by inject() 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | intent?.let { inputImageIntentManager.notifyNewIntent(it) } 18 | startActivity(Intent(this, MainActivity::class.java)) 19 | finish() 20 | super.onCreate(savedInstanceState) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/utils/ContextUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | 8 | val Context.writeStoragePermission: Boolean 9 | get() = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED 10 | 11 | val Context.appNeverUpdated: Boolean 12 | get() = with(packageManager) { 13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 14 | getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) 15 | } else { 16 | getPackageInfo(packageName, 0) 17 | }.let { 18 | it.lastUpdateTime == it.firstInstallTime 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /realesrgan/core/upscaling.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 04/01/23. 3 | // 4 | 5 | #ifndef SUPERIMAGE_UPSCALING_H 6 | #define SUPERIMAGE_UPSCALING_H 7 | 8 | #include 9 | #include 10 | 11 | #include "Eigen/Core" 12 | 13 | #include "jni_common/mnn_model.h" 14 | 15 | #define REALESRGAN_INPUT_TILE_SIZE 84 16 | #define REALESRGAN_INPUT_TILE_PADDING 10 17 | 18 | using PixelMatrix = Eigen::Map>; 19 | 20 | void run_inference( 21 | JNIEnv* jni_env, 22 | jobject progress_tracker, 23 | jobject coroutine_scope, 24 | const mnn_model* model, 25 | int scale, 26 | const PixelMatrix& input_image_matrix, 27 | PixelMatrix& output_image_matrix); 28 | 29 | #endif //SUPERIMAGE_UPSCALING_H 30 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zhenxiang/superimage/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.zhenxiang.superimage", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_exclamation_octagon_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/tracking/AppTrackingModule.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.tracking 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.preferencesDataStore 7 | import com.zhenxiang.superimage.playstore.AppTracker 8 | import org.koin.core.module.dsl.singleOf 9 | import org.koin.core.qualifier.qualifier 10 | import org.koin.dsl.module 11 | 12 | private val TRACKING_DATA_STORE_QUALIFIER = qualifier("tracking") 13 | 14 | private val Context.dataStore: DataStore by preferencesDataStore(name = TRACKING_DATA_STORE_QUALIFIER.value) 15 | 16 | val AppTrackingModule = module { 17 | single { AppReviewTracking(get().dataStore) } 18 | singleOf(::AppTracker) 19 | } 20 | -------------------------------------------------------------------------------- /playstore/base/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /playstore/impl/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /playstore/no-op/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | } 5 | 6 | kotlin { 7 | android() 8 | jvm("desktop") 9 | 10 | sourceSets { 11 | val commonMain by getting { 12 | dependencies { 13 | api(project(":common-models")) 14 | } 15 | } 16 | val commonTest by getting { 17 | dependencies { 18 | implementation(kotlin("test")) 19 | } 20 | } 21 | val androidMain by getting 22 | } 23 | } 24 | 25 | android { 26 | 27 | namespace = "com.zhenxiang.superimage.shared" 28 | compileSdk = 33 29 | 30 | defaultConfig { 31 | minSdk = 24 32 | targetSdk = 33 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility = JavaVersion.VERSION_1_8 37 | targetCompatibility = JavaVersion.VERSION_1_8 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/navigation/RootNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.CompositionLocalProvider 5 | import com.arkivanov.decompose.extensions.compose.LocalLifecycleOwner 6 | import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children 7 | import com.zhenxiang.superimage.home.HomePage 8 | import com.zhenxiang.superimage.settings.SettingsPage 9 | 10 | @Composable 11 | fun RootNavigation(rootComponent: RootComponent) = Children(rootComponent.childStack) { 12 | val child = it.instance 13 | CompositionLocalProvider(LocalLifecycleOwner provides child.component) { 14 | when (child) { 15 | is RootComponent.Child.Home -> HomePage(child.component) 16 | is RootComponent.Child.Settings -> SettingsPage(child.component) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/utils/PaddingUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.utils 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.layout.layout 6 | 7 | /** 8 | * Workaround to offset the horizontal padding applied from the given [PaddingValues] 9 | */ 10 | fun Modifier.ignoreHorizontalPadding(padding: PaddingValues): Modifier { 11 | return layout { measurable, constraints -> 12 | val overriddenWidth = constraints.maxWidth + 13 | padding.calculateLeftPadding(layoutDirection).roundToPx() + 14 | padding.calculateRightPadding(layoutDirection).roundToPx() 15 | val placeable = measurable.measure(constraints.copy(maxWidth = overriddenWidth)) 16 | layout(placeable.width, placeable.height) { 17 | placeable.place(0, 0) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val white = Color(0xFFFFFFFF) 6 | val black = Color(0xFF000000) 7 | 8 | val md_theme_light_error = Color(0xFFFF8400) 9 | val md_theme_light_onError = Color(0xFFFFFFFF) 10 | val md_theme_light_errorContainer = Color(0xFFF9DEDC) 11 | val md_theme_light_onErrorContainer = Color(0xFF410E0B) 12 | val md_theme_light_inversePrimary = Color(0xFFD0BCFF) 13 | val md_theme_light_outlineVariant = Color(0xFFCAC4D0) 14 | val md_theme_light_scrim = Color(0xFF000000) 15 | 16 | val md_theme_dark_error = Color(0xFFFFC800) 17 | val md_theme_dark_onError = Color(0xFF601410) 18 | val md_theme_dark_errorContainer = Color(0xFF8C1D18) 19 | val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC) 20 | val md_theme_dark_inversePrimary = Color(0xFF6750A4) 21 | val md_theme_dark_outlineVariant = Color(0xFF49454F) 22 | val md_theme_dark_scrim = Color(0xFF000000) 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/utils/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | fun String.replaceFileExtension(extension: String): String { 4 | val extStartIndex = lastIndexOf(FileUtils.FILE_EXTENSION_CHAR) 5 | return if (extStartIndex < 0) { 6 | "$this.$extension" 7 | } else { 8 | replaceRange(extStartIndex + 1, length, extension) 9 | } 10 | } 11 | 12 | fun String.addFileNameSuffix(suffix: String) = buildString { 13 | val split = this@addFileNameSuffix.split(FileUtils.FILE_EXTENSION_CHAR) 14 | var suffixAppended = false 15 | 16 | split.forEachIndexed { index, part -> 17 | if (part.isNotEmpty()) { 18 | append(part) 19 | if (!suffixAppended) { 20 | suffixAppended = true 21 | append(suffix) 22 | } 23 | } 24 | if (index < split.size - 1) { 25 | append(FileUtils.FILE_EXTENSION_CHAR) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /realesrgan/core/desktop/image_utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 26/02/23. 3 | // 4 | 5 | #include "image_utils.h" 6 | 7 | extern "C" JNIEXPORT image_dimensions JNICALL 8 | get_image_dimensions(JNIEnv* env, jobject buffered_image) { 9 | jclass clazz = env->GetObjectClass(buffered_image); 10 | return { 11 | .width = env->CallIntMethod(buffered_image, env->GetMethodID(clazz, "getWidth", "()I")), 12 | .height = env->CallIntMethod(buffered_image, env->GetMethodID(clazz, "getHeight", "()I")) 13 | }; 14 | } 15 | 16 | extern "C" JNIEXPORT jintArray JNICALL 17 | get_rgb(JNIEnv* env, jobject buffered_image, image_dimensions dimens) { 18 | jclass clazz = env->GetObjectClass(buffered_image); 19 | return (jintArray) env->CallObjectMethod( 20 | clazz, 21 | env->GetMethodID(clazz, "getRGB", "(I, I, I, I, [I, I, I)[I"), 22 | 0, 23 | 0, 24 | dimens.width, 25 | dimens.height, 26 | nullptr, 27 | 0, 28 | 0 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/tracking/AppReviewTracking.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.tracking 2 | 3 | import androidx.datastore.core.DataStore 4 | import androidx.datastore.preferences.core.Preferences 5 | import androidx.datastore.preferences.core.edit 6 | import androidx.datastore.preferences.core.intPreferencesKey 7 | import kotlinx.coroutines.flow.first 8 | 9 | class AppReviewTracking(private val dataStore: DataStore) { 10 | 11 | private val successfulUpscalingCountKey = intPreferencesKey("success_upscaling") 12 | 13 | suspend fun trackUpscalingSuccess() { 14 | dataStore.edit { 15 | it[successfulUpscalingCountKey] = (it[successfulUpscalingCountKey] ?: 0) + 1 16 | } 17 | } 18 | 19 | suspend fun shouldShowReviewFlow() = with(dataStore.data.first()) { 20 | (this[successfulUpscalingCountKey] ?: 0) >= SHOW_APP_REVIEW_THRESHOLD 21 | } 22 | 23 | companion object { 24 | 25 | private const val SHOW_APP_REVIEW_THRESHOLD = 2 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/mono/Border.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.mono 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.draw.drawWithContent 6 | import androidx.compose.ui.geometry.Offset 7 | 8 | fun Modifier.drawTopBorder(borderStroke: BorderStroke) = drawWithContent { 9 | val strokeWidth = borderStroke.width.value * density 10 | val y = strokeWidth / 2 11 | 12 | drawContent() 13 | 14 | drawLine( 15 | borderStroke.brush, 16 | Offset(0f, y), 17 | Offset(size.width, y), 18 | strokeWidth 19 | ) 20 | } 21 | 22 | fun Modifier.drawBottomBorder(borderStroke: BorderStroke) = drawWithContent { 23 | val strokeWidth = borderStroke.width.value * density 24 | val y = size.height - strokeWidth / 2 25 | 26 | drawContent() 27 | 28 | drawLine( 29 | borderStroke.brush, 30 | Offset(0f, y), 31 | Offset(size.width, y), 32 | strokeWidth 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /app/src/test/java/com/zhenxiang/superimage/utils/FIleUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Rule 5 | import org.junit.Test 6 | import org.junit.rules.TemporaryFolder 7 | 8 | 9 | class FIleUtilsTest { 10 | 11 | @get:Rule 12 | val folderRule = TemporaryFolder() 13 | 14 | @Test 15 | fun testNewFileAutoSuffix(): Unit = with(FileUtils) { 16 | val parentDir = folderRule.newFolder("parent") 17 | // Create file for first time, no suffix should be added 18 | val file = newFileAutoSuffix(parentDir, "..file.txt").apply { 19 | createNewFile() 20 | } 21 | assertEquals("..file.txt", file.name) 22 | 23 | // The following files will have a suffix 24 | (1 until 10).forEach { 25 | val suffixedFile = newFileAutoSuffix(parentDir, "..file.txt").apply { 26 | createNewFile() 27 | } 28 | assertEquals("..file ($it).txt", suffixedFile.name) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sun_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/theme/Border.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.theme 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.unit.dp 7 | 8 | object BorderThickness { 9 | 10 | val thin = 0.75.dp 11 | val regular = 1.25.dp 12 | 13 | } 14 | object Border { 15 | 16 | val Border.thickness: BorderThickness 17 | get() = BorderThickness 18 | 19 | val thin: BorderStroke 20 | @Composable 21 | get() = BorderStroke(BorderThickness.thin, MaterialTheme.colorScheme.outline) 22 | 23 | val regular: BorderStroke 24 | @Composable 25 | get() = BorderStroke(BorderThickness.regular, MaterialTheme.colorScheme.outline) 26 | 27 | @Composable 28 | fun RegularWithAlpha(alpha: Float) = BorderStroke( 29 | BorderThickness.regular, 30 | MaterialTheme.colorScheme.outline.copy(alpha) 31 | ) 32 | } 33 | 34 | val MaterialTheme.border: Border 35 | get() = Border 36 | -------------------------------------------------------------------------------- /app/src/test/java/com/zhenxiang/superimage/utils/StringUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class StringUtilsTest { 7 | 8 | @Test 9 | fun testReplaceFileExtension() { 10 | val fileName1 = "file" 11 | assertEquals("file.xxx", fileName1.replaceFileExtension("xxx")) 12 | 13 | val fileName2 = "file.txt" 14 | assertEquals("file.xxx", fileName2.replaceFileExtension("xxx")) 15 | 16 | val fileName3 = "file.old.txt" 17 | assertEquals("file.old.xxx", fileName3.replaceFileExtension("xxx")) 18 | } 19 | 20 | @Test 21 | fun testAddFileNameSuffix() { 22 | val fileName1 = "file" 23 | assertEquals("file_suffix", fileName1.addFileNameSuffix("_suffix")) 24 | 25 | val fileName2 = "file.txt" 26 | assertEquals("file_suffix.txt", fileName2.addFileNameSuffix("_suffix")) 27 | 28 | val fileName3 = "..file.txt" 29 | assertEquals("..file_suffix.txt", fileName3.addFileNameSuffix("_suffix")) 30 | } 31 | } -------------------------------------------------------------------------------- /realesrgan/android/src/main/java/com/zhenxiang/realesrgan/JNIProgressTracker.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.realesrgan 2 | 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | import kotlinx.coroutines.flow.StateFlow 5 | 6 | /** 7 | * Wrapper to help tracking progress of the execution of a native function 8 | */ 9 | class JNIProgressTracker { 10 | 11 | private val _progressFlow = MutableStateFlow(Progress(INDETERMINATE_PROGRESS, INDETERMINATE_TIME)) 12 | val progressFlow: StateFlow 13 | get() = _progressFlow 14 | 15 | fun setProgress(value: Float, estimatedTime: Long) { 16 | _progressFlow.tryEmit(Progress(value, estimatedTime)) 17 | } 18 | 19 | /** 20 | * @param value progress in range 0 to 100 or [INDETERMINATE_PROGRESS] 21 | * @param estimatedMillisLeft estimated time left in milliseconds 22 | */ 23 | data class Progress(val value: Float, val estimatedMillisLeft: Long) 24 | 25 | companion object { 26 | const val INDETERMINATE_PROGRESS = -1f 27 | const val INDETERMINATE_TIME = -1L 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_telegram_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /playstore/no-op/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "com.zhenxiang.superimage.playstore" 8 | compileSdk = 33 9 | 10 | defaultConfig { 11 | minSdk = 24 12 | targetSdk = 33 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | release { 20 | isMinifyEnabled = false 21 | proguardFiles( 22 | getDefaultProguardFile("proguard-android-optimize.txt"), 23 | "proguard-rules.pro" 24 | ) 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_1_8 29 | targetCompatibility = JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = "1.8" 33 | } 34 | } 35 | 36 | dependencies { 37 | 38 | api(project(":playstore:base")) 39 | 40 | implementation("com.github.matomo-org:matomo-sdk-android:4.1.4") 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/intent/InputImageIntentManager.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.intent 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import com.zhenxiang.superimage.utils.getParcelableExtraCompat 6 | import kotlinx.coroutines.channels.BufferOverflow 7 | import kotlinx.coroutines.channels.Channel 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.consumeAsFlow 10 | 11 | class InputImageIntentManager { 12 | 13 | private val _intentUriChannel = Channel(1, BufferOverflow.DROP_OLDEST) 14 | val imageUriFlow: Flow 15 | get() = _intentUriChannel.consumeAsFlow() 16 | 17 | /** 18 | * @return whether the [Intent] has been consumed 19 | */ 20 | fun notifyNewIntent(intent: Intent): Boolean { 21 | if (intent.action == Intent.ACTION_SEND && intent.type?.startsWith("image/") == true) { 22 | intent.getParcelableExtraCompat(Intent.EXTRA_STREAM)?.let { 23 | _intentUriChannel.trySend(it) 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/utils/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | import java.io.File 4 | 5 | object FileUtils { 6 | 7 | const val FILE_EXTENSION_CHAR = '.' 8 | 9 | /** 10 | * Create file object with given name, if file with given name already exists append 11 | * a numerical suffix. Only file object will be created, make sure to call [File.mkdir] 12 | * or [File.createNewFile] afterwards 13 | */ 14 | fun newFileAutoSuffix(parentDir: File, fileName: String): File { 15 | File(parentDir, fileName).let { 16 | // Existing file with given name not found, success 17 | if (!it.exists()) { 18 | return it 19 | } 20 | } 21 | 22 | // Look for existing files with suffix until we find a free filename 23 | var suffixNumber = 1 24 | while (true) { 25 | val file = File(parentDir, fileName.addFileNameSuffix(" ($suffixNumber)")) 26 | if (!file.exists()) { 27 | return file 28 | } 29 | suffixNumber++ 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /realesrgan/android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # Rules for native calls to Java function 24 | -keepclassmembers class com.zhenxiang.realesrgan.JNIProgressTracker { 25 | void setProgress(float, long); 26 | } 27 | -keep class kotlinx.coroutines.CoroutineScopeKt { 28 | boolean isActive(kotlinx.coroutines.CoroutineScope); 29 | } 30 | -------------------------------------------------------------------------------- /playstore/no-op/src/main/java/com/zhenxiang/superimage/playstore/AppTracker.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.playstore 2 | 3 | import android.content.Context 4 | import com.zhenxiang.superimage.playstore.base.BaseAppTracker 5 | import org.matomo.sdk.Matomo 6 | import org.matomo.sdk.TrackerBuilder 7 | import org.matomo.sdk.extra.DownloadTracker 8 | import org.matomo.sdk.extra.TrackHelper 9 | 10 | class AppTracker(private val context: Context): BaseAppTracker(context) { 11 | 12 | private val tracker = TrackerBuilder 13 | .createDefault("https://superimage.matomo.cloud/matomo.php", 1) 14 | .build(Matomo.getInstance(context)) 15 | 16 | override fun trackInstall() { 17 | TrackHelper.track().download().identifier(DownloadTracker.Extra.ApkChecksum(context)).with(tracker) 18 | tracker.dispatch() 19 | } 20 | 21 | override fun trackViewScreen(screenId: String) { 22 | TrackHelper.track().screen(screenId).with(tracker) 23 | tracker.dispatch() 24 | } 25 | 26 | override fun trackAction(actionId: String) { 27 | TrackHelper.track().interaction(actionId, "action").with(tracker) 28 | tracker.dispatch() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/settings/PreferenceState.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.settings 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.State 5 | import androidx.datastore.core.DataStore 6 | import androidx.datastore.preferences.core.Preferences 7 | import androidx.datastore.preferences.core.edit 8 | import androidx.datastore.preferences.core.intPreferencesKey 9 | import com.arkivanov.essenty.lifecycle.ext.collectAsStateWithLifecycle 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.flow.first 12 | import kotlinx.coroutines.flow.map 13 | import kotlinx.coroutines.runBlocking 14 | import kotlinx.coroutines.withContext 15 | 16 | class IntPreferenceState( 17 | private val dataStore: DataStore, 18 | key: String, 19 | defaultValue: Int 20 | ) { 21 | 22 | private val prefKey = intPreferencesKey(key) 23 | 24 | private val valueFlow = dataStore.data.map { it[prefKey] ?: defaultValue } 25 | 26 | val state: State 27 | @Composable 28 | get() = valueFlow.collectAsStateWithLifecycle(initialValue = runBlocking { valueFlow.first() }) 29 | 30 | suspend fun setValue(value: Int) = withContext(Dispatchers.IO) { 31 | dataStore.edit { it[prefKey] = value } 32 | } 33 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 28 | 29 | -------------------------------------------------------------------------------- /playstore/impl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "com.zhenxiang.superimage.playstore" 8 | compileSdk = 33 9 | 10 | defaultConfig { 11 | minSdk = 24 12 | targetSdk = 33 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | release { 20 | isMinifyEnabled = false 21 | proguardFiles( 22 | getDefaultProguardFile("proguard-android-optimize.txt"), 23 | "proguard-rules.pro" 24 | ) 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_1_8 29 | targetCompatibility = JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = "1.8" 33 | } 34 | } 35 | 36 | dependencies { 37 | 38 | api(project(":playstore:base")) 39 | 40 | implementation("com.google.android.play:review-ktx:2.0.1") 41 | 42 | testImplementation("io.mockk:mockk-android:1.13.4") 43 | testImplementation("junit:junit:4.13.2") 44 | androidTestImplementation("androidx.test.ext:junit:1.1.5") 45 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 46 | } -------------------------------------------------------------------------------- /playstore/base/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "com.zhenxiang.superimage.playstore.base" 8 | compileSdk = 33 9 | 10 | defaultConfig { 11 | minSdk = 24 12 | targetSdk = 33 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | release { 20 | isMinifyEnabled = false 21 | proguardFiles( 22 | getDefaultProguardFile("proguard-android-optimize.txt"), 23 | "proguard-rules.pro" 24 | ) 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_1_8 29 | targetCompatibility = JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = "1.8" 33 | } 34 | } 35 | 36 | dependencies { 37 | 38 | implementation("androidx.core:core-ktx:1.7.0") 39 | implementation("androidx.appcompat:appcompat:1.6.1") 40 | implementation("com.google.android.material:material:1.8.0") 41 | testImplementation("junit:junit:4.13.2") 42 | androidTestImplementation("androidx.test.ext:junit:1.1.5") 43 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 44 | } -------------------------------------------------------------------------------- /playstore/impl/src/main/java/com/zhenxiang/superimage/playstore/InAppReviewManager.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.playstore 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import androidx.annotation.VisibleForTesting 6 | import com.google.android.play.core.review.ReviewInfo 7 | import com.google.android.play.core.review.ReviewManagerFactory 8 | import com.zhenxiang.superimage.playstore.base.BaseInAppReviewManager 9 | 10 | class InAppReviewManager(context: Context): BaseInAppReviewManager(context) { 11 | 12 | private val manager = ReviewManagerFactory.create(context) 13 | 14 | private var busy = false 15 | 16 | @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 17 | var reviewInfo: ReviewInfo? = null 18 | 19 | override val reviewFlowReady: Boolean 20 | get() = reviewInfo != null 21 | 22 | override fun prepareReviewInfo() { 23 | if (busy || reviewInfo != null) { 24 | return 25 | } 26 | busy = true 27 | manager.requestReviewFlow().addOnCompleteListener { task -> 28 | reviewInfo = if (task.isSuccessful) task.result else null 29 | busy = false 30 | } 31 | } 32 | 33 | override fun launchReviewFlow(activity: Activity) { 34 | reviewInfo?.let { info -> 35 | reviewInfo = null 36 | manager.launchReviewFlow(activity, info) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/utils/BitmapUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | import android.content.ContentResolver 4 | import android.graphics.Bitmap 5 | import android.graphics.ImageDecoder 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.provider.MediaStore 9 | import com.zhenxiang.superimage.shared.model.OutputFormat 10 | import timber.log.Timber 11 | import java.io.OutputStream 12 | 13 | object BitmapUtils { 14 | 15 | /** 16 | * Copy hardware bitmap to software. 17 | * It will simply return the input bitmap if it's not a hardware bitmap 18 | */ 19 | fun copyToSoftware( 20 | hwBitmap: Bitmap, 21 | recycleHwBitmap: Boolean = false 22 | ): Bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hwBitmap.config == Bitmap.Config.HARDWARE) { 23 | val bitmapCopy = hwBitmap.copy(Bitmap.Config.ARGB_8888, false) 24 | if (recycleHwBitmap) { 25 | hwBitmap.recycle() 26 | } 27 | bitmapCopy 28 | } else { 29 | hwBitmap 30 | } 31 | } 32 | 33 | fun Bitmap.compress(outputFormat: OutputFormat, quality: Int, outputStream: OutputStream): Boolean { 34 | val bitmapCompressFormat = when (outputFormat) { 35 | OutputFormat.PNG -> Bitmap.CompressFormat.PNG 36 | OutputFormat.JPEG -> Bitmap.CompressFormat.JPEG 37 | } 38 | 39 | return compress(bitmapCompressFormat, quality, outputStream) 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/navigation/ChildComponent.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.navigation 2 | 3 | import com.arkivanov.decompose.ComponentContext 4 | import com.arkivanov.decompose.router.stack.StackNavigation 5 | import com.arkivanov.essenty.instancekeeper.InstanceKeeper 6 | import com.arkivanov.essenty.instancekeeper.getOrCreate 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.SupervisorJob 10 | import kotlinx.coroutines.cancel 11 | 12 | abstract class ChildComponent( 13 | componentContext: ComponentContext, 14 | val navigation: StackNavigation 15 | ): ComponentContext by componentContext { 16 | 17 | abstract val viewModel: T 18 | 19 | abstract class ViewModel: InstanceKeeper.Instance { 20 | 21 | private val coroutineScopeDelegate = lazy { 22 | CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) 23 | } 24 | 25 | val viewModelScope: CoroutineScope 26 | get() = coroutineScopeDelegate.value 27 | 28 | override fun onDestroy() { 29 | if (coroutineScopeDelegate.isInitialized()) { 30 | viewModelScope.cancel() 31 | } 32 | } 33 | } 34 | } 35 | 36 | inline fun ChildComponent.getViewModel( 37 | factory: () -> T 38 | ): T = instanceKeeper.getOrCreate(T::class, factory) 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/SuperImageWorkManagerInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import androidx.startup.Initializer 6 | import androidx.work.Configuration 7 | import androidx.work.Logger 8 | import androidx.work.WorkManager 9 | import com.zhenxiang.superimage.work.RealESRGANWorker 10 | import com.zhenxiang.superimage.work.RealESRGANWorkerManager 11 | 12 | /** 13 | * Initializes [androidx.work.WorkManager] using `androidx.startup`. 14 | */ 15 | class SuperImageWorkManagerInitializer : Initializer { 16 | 17 | @SuppressLint("RestrictedApi") 18 | override fun create(context: Context): WorkManager { 19 | // Initialize WorkManager with the default configuration. 20 | Logger.get().debug(TAG, "Initializing WorkManager with default configuration.") 21 | WorkManager.initialize(context, Configuration.Builder().build()) 22 | val instance = WorkManager.getInstance(context) 23 | 24 | // Don't try to restart RealESRGANWorker 25 | instance.cancelUniqueWork(RealESRGANWorkerManager.UNIQUE_WORK_ID) 26 | 27 | return instance 28 | } 29 | 30 | override fun dependencies(): List?>> { 31 | return emptyList() 32 | } 33 | 34 | companion object { 35 | @SuppressLint("RestrictedApi") 36 | private val TAG = Logger.tagWithPrefix("WrkMgrInitializer") 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/daynight/DayNightMode.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.daynight 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.appcompat.app.AppCompatDelegate 5 | import androidx.appcompat.app.AppCompatDelegate.NightMode 6 | import androidx.compose.foundation.isSystemInDarkTheme 7 | import androidx.compose.runtime.Composable 8 | import com.zhenxiang.superimage.R 9 | import com.zhenxiang.superimage.common.Identifiable 10 | 11 | enum class DayNightMode(override val id: Int, @StringRes val stringRes: Int): Identifiable { 12 | AUTO(0, R.string.auto_label), 13 | DAY(1, R.string.light_label), 14 | NIGHT(2, R.string.dark_label); 15 | 16 | @NightMode 17 | val delegateNightMode: Int 18 | get() = when (this) { 19 | AUTO -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM 20 | DAY -> AppCompatDelegate.MODE_NIGHT_NO 21 | NIGHT -> AppCompatDelegate.MODE_NIGHT_YES 22 | } 23 | 24 | val lightMode: Boolean 25 | @Composable get() = when (this) { 26 | AUTO -> !isSystemInDarkTheme() 27 | DAY -> true 28 | NIGHT -> false 29 | } 30 | 31 | companion object: Identifiable.EnumCompanion { 32 | 33 | override val VALUES = values() 34 | 35 | fun fromDelegateNightMode(@NightMode mode: Int) = when (mode) { 36 | AppCompatDelegate.MODE_NIGHT_YES -> NIGHT 37 | AppCompatDelegate.MODE_NIGHT_NO -> DAY 38 | else -> AUTO 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release APK 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | name: Release APK 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Checkout submodules 18 | run: git submodule update --init --recursive 19 | 20 | - name: Install JDK 21 | uses: actions/setup-java@v3 22 | with: 23 | distribution: temurin 24 | java-version: 11 25 | 26 | - name: Prepare signing keystore 27 | run: echo "${{ secrets.keystore }}" | base64 -d > ${{ github.workspace }}/signing-key.jks 28 | 29 | - name: Build signed APK 30 | uses: gradle/gradle-build-action@v2 31 | with: 32 | arguments: | 33 | assembleFreeRelease 34 | -Pandroid.injected.signing.store.file=${{ github.workspace }}/signing-key.jks 35 | -Pandroid.injected.signing.store.password=${{ secrets.keystore_password }} 36 | -Pandroid.injected.signing.key.alias=${{ secrets.key_alias }} 37 | -Pandroid.injected.signing.key.password=${{ secrets.key_password }} 38 | 39 | - name: Cleanup signing keystore 40 | run: rm $GITHUB_WORKSPACE/signing-key.jks 41 | 42 | - name: Release APK 43 | uses: softprops/action-gh-release@v1 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | with: 47 | draft: true 48 | files: | 49 | app/build/outputs/apk/free/release/app-free-release.apk 50 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_moon_stars_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /realesrgan/core/image_tile_interpreter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 06/02/23. 3 | // 4 | 5 | #ifndef SUPERIMAGE_IMAGE_TILE_INTERPRETER_H 6 | #define SUPERIMAGE_IMAGE_TILE_INTERPRETER_H 7 | 8 | #include "MNN/Interpreter.hpp" 9 | 10 | #include "jni_common/mnn_model.h" 11 | 12 | #include "image_dimensions.h" 13 | 14 | #define REALESRGAN_IMAGE_CHANNELS 3 15 | 16 | 17 | class ImageTileInterpreterException : public std::exception { 18 | 19 | public: 20 | 21 | enum Error { 22 | CreateInterpreterFailed = 1, 23 | CreateBackendFailed = 2 24 | }; 25 | 26 | const Error error; 27 | 28 | ImageTileInterpreterException(Error error) : error(error) {} 29 | 30 | const char* what() const noexcept override { 31 | switch (error) { 32 | case Error::CreateInterpreterFailed: 33 | return "Failed to create MNN interpreter"; 34 | case Error::CreateBackendFailed: 35 | return "Failed to create MNN backend"; 36 | } 37 | } 38 | }; 39 | 40 | class ImageTileInterpreter { 41 | 42 | public: 43 | ImageTileInterpreter(const mnn_model* model, const image_dimensions tile_dimensions); 44 | ~ImageTileInterpreter(); 45 | 46 | const image_dimensions tile_dimensions; 47 | 48 | float* input_buffer; 49 | float* output_buffer; 50 | 51 | void inference() const; 52 | 53 | private: 54 | MNN::Interpreter* interpreter; 55 | MNN::Session* session; 56 | MNN::Tensor* interpreter_input; 57 | MNN::Tensor* interpreter_output; 58 | MNN::Tensor* input_tensor; 59 | MNN::Tensor* output_tensor; 60 | }; 61 | 62 | #endif //SUPERIMAGE_IMAGE_TILE_INTERPRETER_H 63 | -------------------------------------------------------------------------------- /realesrgan/android/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.zhenxiang.realesrgan' 8 | compileSdk 33 9 | 10 | defaultConfig { 11 | minSdk 24 12 | targetSdk 33 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles "consumer-rules.pro" 16 | externalNativeBuild { 17 | cmake { 18 | arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_STL=c++_shared" 19 | abiFilters 'armeabi-v7a', 'arm64-v8a' 20 | cppFlags "" 21 | targets "realesrgan", "MNN", "MNN_VK", "MNN_CL" 22 | } 23 | } 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled true 29 | consumerProguardFiles 'proguard-rules.pro' 30 | } 31 | } 32 | externalNativeBuild { 33 | cmake { 34 | path "../core/CMakeLists.txt" 35 | version "3.22.1" 36 | } 37 | } 38 | compileOptions { 39 | sourceCompatibility JavaVersion.VERSION_1_8 40 | targetCompatibility JavaVersion.VERSION_1_8 41 | } 42 | kotlinOptions { 43 | jvmTarget = '1.8' 44 | } 45 | } 46 | 47 | dependencies { 48 | 49 | implementation project(path: ':common-models') 50 | 51 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") 52 | 53 | testImplementation 'junit:junit:4.13.2' 54 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 55 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/tracking/AppVersionTracking.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.tracking 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.core.booleanPreferencesKey 7 | import androidx.datastore.preferences.core.edit 8 | import androidx.datastore.preferences.core.intPreferencesKey 9 | import com.zhenxiang.superimage.BuildConfig 10 | import com.zhenxiang.superimage.utils.appNeverUpdated 11 | import kotlinx.coroutines.flow.first 12 | 13 | object AppVersionTracking { 14 | 15 | private val showChangelogKey: Preferences.Key 16 | get() = booleanPreferencesKey("show_changelog") 17 | 18 | suspend fun refreshAppVersion(context: Context, dataStore: DataStore) { 19 | val versionCodeKey = intPreferencesKey("version_code") 20 | 21 | val currentVersionCode = dataStore.data.first()[versionCodeKey] 22 | if ( 23 | (currentVersionCode == null && !context.appNeverUpdated) || 24 | (currentVersionCode != null && currentVersionCode < BuildConfig.VERSION_CODE) 25 | ) { 26 | dataStore.edit { 27 | it[versionCodeKey] = BuildConfig.VERSION_CODE 28 | it[showChangelogKey] = true 29 | } 30 | } 31 | } 32 | 33 | suspend fun shouldShowChangelog(dataStore: DataStore) = 34 | dataStore.data.first()[showChangelogKey] == true 35 | 36 | suspend fun clearShowChangelog(dataStore: DataStore) = dataStore.edit { 37 | it.remove(showChangelogKey) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/mono/Radio.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.mono 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.RadioButton 10 | import androidx.compose.material3.Surface 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.tooling.preview.Preview 16 | import com.zhenxiang.superimage.ui.theme.MonoTheme 17 | import com.zhenxiang.superimage.ui.theme.spacing 18 | 19 | @Composable 20 | fun MonoRadioButton( 21 | modifier: Modifier = Modifier, 22 | label: @Composable () -> Unit, 23 | selected: Boolean, 24 | onClick: () -> Unit 25 | ) = Row( 26 | modifier = modifier 27 | .clickable(onClick = onClick) 28 | .padding(start = MaterialTheme.spacing.level4), 29 | horizontalArrangement = Arrangement.SpaceBetween, 30 | verticalAlignment = Alignment.CenterVertically, 31 | ) { 32 | label() 33 | RadioButton(selected = selected, onClick = onClick) 34 | } 35 | 36 | @Composable 37 | @Preview(showBackground = true) 38 | @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) 39 | private fun MonoRadioButtonPreview() = MonoTheme { 40 | Surface { 41 | MonoRadioButton( 42 | label = { Text("Radio button") }, 43 | selected = true, 44 | ) {} 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/utils/TimeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | import android.content.Context 4 | import android.content.res.Resources 5 | import androidx.annotation.PluralsRes 6 | import com.zhenxiang.superimage.R 7 | import org.joda.time.Period 8 | import org.joda.time.format.PeriodFormatterBuilder 9 | import kotlin.math.roundToLong 10 | 11 | object TimeUtils { 12 | 13 | private const val MAX_MILLIS = 2562047788015 14 | 15 | fun periodToString(context: Context, millis: Long): String = with(context.resources) { 16 | val unitSpace = getString(R.string.time_unit_space) 17 | val wordSpace = getString(R.string.word_space) 18 | val formatter = PeriodFormatterBuilder() 19 | .appendHours() 20 | .appendPluralSuffix(this, R.plurals.hour, unitSpace) 21 | .appendSeparator(wordSpace) 22 | .appendMinutes() 23 | .appendPluralSuffix(this, R.plurals.minute, unitSpace) 24 | .appendSeparator(wordSpace) 25 | .printZeroAlways() 26 | .appendSeconds() 27 | .appendPluralSuffix(this, R.plurals.second, unitSpace) 28 | .toFormatter() 29 | 30 | // Round to nearest second 31 | formatter.print(Period((millis.coerceAtMost(MAX_MILLIS) / 1000.0).roundToLong() * 1000)) 32 | } 33 | 34 | private fun PeriodFormatterBuilder.appendPluralSuffix( 35 | resources: Resources, 36 | @PluralsRes resId: Int, 37 | spacer: String, 38 | ) = apply { 39 | appendSuffix( 40 | "$spacer${resources.getQuantityString(resId, 1)}", 41 | "$spacer${resources.getQuantityString(resId, 2)}" 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/SuperImageApplication.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage 2 | 3 | import android.app.Application 4 | import com.zhenxiang.superimage.datastore.DataStoreModule 5 | import com.zhenxiang.superimage.datastore.SETTINGS_DATA_STORE_QUALIFIER 6 | import com.zhenxiang.superimage.intent.InputImageIntentManagerModule 7 | import com.zhenxiang.superimage.playstore.AppTracker 8 | import com.zhenxiang.superimage.tracking.AppTrackingModule 9 | import com.zhenxiang.superimage.tracking.AppVersionTracking 10 | import com.zhenxiang.superimage.ui.daynight.DayNightModule 11 | import com.zhenxiang.superimage.work.RealESRGANWorkerModule 12 | import kotlinx.coroutines.runBlocking 13 | import org.koin.android.ext.koin.androidContext 14 | import org.koin.android.ext.koin.androidLogger 15 | import org.koin.core.component.KoinComponent 16 | import org.koin.core.component.get 17 | import org.koin.core.context.startKoin 18 | import timber.log.Timber 19 | 20 | 21 | class SuperImageApplication: Application(), KoinComponent { 22 | 23 | override fun onCreate() { 24 | Timber.plant(Timber.DebugTree()) 25 | super.onCreate() 26 | startKoin { 27 | // Log Koin into Android logger 28 | androidLogger() 29 | // Reference Android context 30 | androidContext(this@SuperImageApplication) 31 | 32 | modules( 33 | RealESRGANWorkerModule, 34 | DataStoreModule, 35 | DayNightModule, 36 | InputImageIntentManagerModule, 37 | AppTrackingModule 38 | ) 39 | } 40 | runBlocking { 41 | AppVersionTracking.refreshAppVersion(this@SuperImageApplication, get(SETTINGS_DATA_STORE_QUALIFIER)) 42 | } 43 | get().trackInstall() 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/navigation/RootComponent.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.navigation 2 | 3 | import com.arkivanov.decompose.ComponentContext 4 | import com.arkivanov.decompose.router.stack.ChildStack 5 | import com.arkivanov.decompose.router.stack.StackNavigation 6 | import com.arkivanov.decompose.router.stack.childStack 7 | import com.arkivanov.decompose.value.Value 8 | import com.arkivanov.essenty.parcelable.Parcelable 9 | import com.arkivanov.essenty.parcelable.Parcelize 10 | import com.zhenxiang.superimage.home.HomePageComponent 11 | import com.zhenxiang.superimage.settings.SettingsPageComponent 12 | 13 | class RootComponent(componentContext: ComponentContext): ComponentContext by componentContext { 14 | 15 | private val navigation = StackNavigation() 16 | 17 | private val _childStack = childStack( 18 | source = navigation, 19 | initialConfiguration = Config.Home, 20 | handleBackButton = true, // Pop the back stack on back button press 21 | childFactory = { config, componentContext -> 22 | when (config) { 23 | Config.Home -> Child.Home(HomePageComponent(componentContext, navigation)) 24 | Config.Settings -> Child.Settings(SettingsPageComponent(componentContext, navigation)) 25 | } 26 | }, 27 | ) 28 | 29 | val childStack: Value> = _childStack 30 | 31 | sealed interface Child { 32 | 33 | val component: ChildComponent<*> 34 | 35 | class Home(override val component: HomePageComponent) : Child 36 | 37 | class Settings(override val component: SettingsPageComponent) : Child 38 | } 39 | 40 | sealed interface Config : Parcelable { 41 | 42 | @Parcelize 43 | object Home : Config 44 | 45 | @Parcelize 46 | object Settings : Config 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/utils/IntentUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.utils 2 | 3 | import android.app.PendingIntent 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.os.Parcelable 9 | import android.provider.Settings 10 | import androidx.core.net.toUri 11 | import com.zhenxiang.superimage.MainActivity 12 | 13 | object IntentUtils { 14 | 15 | fun openStringUriIntent(string: String) = Intent(Intent.ACTION_VIEW).apply { 16 | data = string.toUri() 17 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 18 | } 19 | 20 | fun viewImageIntent(uri: Uri) = Intent(Intent.ACTION_VIEW).apply { 21 | setDataAndType(uri, MimeType.IMAGE) 22 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION) 23 | } 24 | 25 | fun appSettingsIntent(context: Context) = Intent( 26 | Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 27 | Uri.fromParts("package", context.packageName, null) 28 | ) 29 | 30 | fun notificationPendingIntent( 31 | context: Context, 32 | intent: Intent, 33 | flags: Int = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT 34 | ): PendingIntent = PendingIntent.getActivity(context, 0, intent, flags) 35 | 36 | fun mainActivityPendingIntent( 37 | context: Context, 38 | flags: Int = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT 39 | ): PendingIntent = notificationPendingIntent(context, Intent(context, MainActivity::class.java), flags) 40 | } 41 | 42 | inline fun Intent.getParcelableExtraCompat( 43 | name: String 44 | ): T? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 45 | getParcelableExtra(name, T::class.java) 46 | } else { 47 | getParcelableExtra(name) 48 | } 49 | -------------------------------------------------------------------------------- /realesrgan/core/android/realesrgan_jni.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "bitmap_utils.h" 5 | #include "../image_tile_interpreter.h" 6 | #include "../jni_common/mnn_model.h" 7 | #include "../upscaling.h" 8 | 9 | extern "C" JNIEXPORT jint JNICALL 10 | Java_com_zhenxiang_realesrgan_RealESRGAN_runUpscaling( 11 | JNIEnv *env, 12 | jobject /* thiz */, 13 | jobject progress_tracker, 14 | jobject coroutine_scope, 15 | jbyteArray model_data_jarray, 16 | jint scale, 17 | jobject input_bitmap, 18 | jobject output_bitmap) { 19 | 20 | const mnn_model model = mnn_model_from_jbytes(env, model_data_jarray); 21 | 22 | void* input_bitmap_buffer; 23 | void* output_bitmap_buffer; 24 | AndroidBitmap_lockPixels(env, input_bitmap, &input_bitmap_buffer); 25 | AndroidBitmap_lockPixels(env, output_bitmap, &output_bitmap_buffer); 26 | 27 | const image_dimensions input_dimens = get_bitmap_dimensions(env, input_bitmap); 28 | 29 | const PixelMatrix input_image_matrix( 30 | (int*) input_bitmap_buffer, 31 | input_dimens.height, 32 | input_dimens.width); 33 | 34 | PixelMatrix output_image_matrix( 35 | (int*) output_bitmap_buffer, 36 | input_image_matrix.rows() * scale, 37 | input_image_matrix.cols() * scale); 38 | 39 | int result = 0; 40 | 41 | try { 42 | run_inference(env, progress_tracker, coroutine_scope, &model, scale, input_image_matrix, output_image_matrix); 43 | } catch (ImageTileInterpreterException& e) { 44 | result = e.error; 45 | } 46 | 47 | // Cleanup and return data 48 | env->ReleaseByteArrayElements(model_data_jarray, model.data, JNI_OK); 49 | AndroidBitmap_unlockPixels(env, input_bitmap); 50 | AndroidBitmap_unlockPixels(env, output_bitmap); 51 | 52 | return result; 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gear_24.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /decompose/src/main/java/com/arkivanov/essenty/lifecycle/ext/FlowExt.kt: -------------------------------------------------------------------------------- 1 | package com.arkivanov.essenty.lifecycle.ext 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.State 5 | import androidx.compose.runtime.produceState 6 | import com.arkivanov.decompose.extensions.compose.LocalLifecycleOwner 7 | import com.arkivanov.essenty.lifecycle.Lifecycle 8 | import com.arkivanov.essenty.lifecycle.LifecycleOwner 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.StateFlow 11 | import kotlinx.coroutines.withContext 12 | import kotlin.coroutines.CoroutineContext 13 | import kotlin.coroutines.EmptyCoroutineContext 14 | 15 | @Composable 16 | fun StateFlow.collectAsStateWithLifecycle( 17 | lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, 18 | minActiveState: Lifecycle.State = Lifecycle.State.STARTED, 19 | context: CoroutineContext = EmptyCoroutineContext 20 | ): State = collectAsStateWithLifecycle( 21 | initialValue = this.value, 22 | lifecycleOwner = lifecycleOwner, 23 | minActiveState = minActiveState, 24 | context = context 25 | ) 26 | 27 | @Composable 28 | fun Flow.collectAsStateWithLifecycle( 29 | initialValue: T, 30 | lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, 31 | minActiveState: Lifecycle.State = Lifecycle.State.STARTED, 32 | context: CoroutineContext = EmptyCoroutineContext 33 | ): State = lifecycleOwner.lifecycle.let { 34 | return produceState(initialValue, this, it, minActiveState, context) { 35 | it.repeatOnLifecycle(minActiveState) { 36 | if (context == EmptyCoroutineContext) { 37 | this@collectAsStateWithLifecycle.collect { this@produceState.value = it } 38 | } else withContext(context) { 39 | this@collectAsStateWithLifecycle.collect { this@produceState.value = it } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/mono/Text.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.mono 2 | 3 | import androidx.compose.material3.LocalTextStyle 4 | import androidx.compose.material3.Text 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.text.TextLayoutResult 9 | import androidx.compose.ui.text.TextStyle 10 | import androidx.compose.ui.text.font.FontFamily 11 | import androidx.compose.ui.text.font.FontStyle 12 | import androidx.compose.ui.text.font.FontWeight 13 | import androidx.compose.ui.text.style.TextAlign 14 | import androidx.compose.ui.text.style.TextDecoration 15 | import androidx.compose.ui.text.style.TextOverflow 16 | import androidx.compose.ui.unit.TextUnit 17 | 18 | @Composable 19 | fun EllipsisText( 20 | text: String, 21 | modifier: Modifier = Modifier, 22 | color: Color = Color.Unspecified, 23 | fontSize: TextUnit = TextUnit.Unspecified, 24 | fontStyle: FontStyle? = null, 25 | fontWeight: FontWeight? = null, 26 | fontFamily: FontFamily? = null, 27 | letterSpacing: TextUnit = TextUnit.Unspecified, 28 | textDecoration: TextDecoration? = null, 29 | textAlign: TextAlign? = null, 30 | lineHeight: TextUnit = TextUnit.Unspecified, 31 | softWrap: Boolean = true, 32 | onTextLayout: (TextLayoutResult) -> Unit = {}, 33 | style: TextStyle = LocalTextStyle.current 34 | ) = Text( 35 | text = text, 36 | modifier = modifier, 37 | color = color, 38 | fontSize = fontSize, 39 | fontStyle = fontStyle, 40 | fontWeight = fontWeight, 41 | fontFamily = fontFamily, 42 | letterSpacing = letterSpacing, 43 | textDecoration = textDecoration, 44 | textAlign = textAlign, 45 | lineHeight = lineHeight, 46 | overflow = TextOverflow.Ellipsis, 47 | softWrap = false, 48 | maxLines = 1, 49 | onTextLayout = onTextLayout, 50 | style = style 51 | ) -------------------------------------------------------------------------------- /realesrgan/core/desktop/realesrgan_jni.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 26/02/23. 3 | // 4 | 5 | #include 6 | 7 | #include "image_utils.h" 8 | #include "../image_tile_interpreter.h" 9 | #include "../jni_common/mnn_model.h" 10 | #include "../upscaling.h" 11 | 12 | extern "C" JNIEXPORT jint JNICALL 13 | Java_com_zhenxiang_realesrgan_RealESRGAN_runUpscaling( 14 | JNIEnv *env, 15 | jobject /* thiz */, 16 | jobject progress_tracker, 17 | jobject coroutine_scope, 18 | jbyteArray model_data_jarray, 19 | jint scale, 20 | jobject input_image, 21 | jintArray output_image_array) { 22 | 23 | const mnn_model model = mnn_model_from_jbytes(env, model_data_jarray); 24 | 25 | const image_dimensions input_dimens = get_image_dimensions(env, input_image); 26 | const jintArray input_image_array = get_rgb(env, input_image, input_dimens); 27 | 28 | PixelMatrix input_image_matrix( 29 | reinterpret_cast(env->GetIntArrayElements(input_image_array, nullptr)), 30 | input_dimens.height, 31 | input_dimens.width); 32 | 33 | PixelMatrix output_image_matrix( 34 | reinterpret_cast(env->GetIntArrayElements(output_image_array, nullptr)), 35 | input_image_matrix.rows() * scale, 36 | input_image_matrix.cols() * scale); 37 | 38 | int result = 0; 39 | 40 | try { 41 | run_inference(env, progress_tracker, coroutine_scope, &model, scale, input_image_matrix, output_image_matrix); 42 | } catch (ImageTileInterpreterException& e) { 43 | result = e.error; 44 | } 45 | 46 | // Cleanup and return data 47 | env->ReleaseByteArrayElements(model_data_jarray, model.data, JNI_OK); 48 | env->ReleaseIntArrayElements(input_image_array, reinterpret_cast(input_image_matrix.data()), JNI_COMMIT); 49 | env->ReleaseIntArrayElements(output_image_array, reinterpret_cast(output_image_matrix.data()), JNI_COMMIT); 50 | 51 | return result; 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v26/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/settings/SettingsPageComponent.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.settings 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import com.arkivanov.decompose.ComponentContext 7 | import com.arkivanov.decompose.router.stack.StackNavigation 8 | import com.zhenxiang.superimage.datastore.SETTINGS_DATA_STORE_QUALIFIER 9 | import com.zhenxiang.superimage.navigation.ChildComponent 10 | import com.zhenxiang.superimage.navigation.RootComponent 11 | import com.zhenxiang.superimage.navigation.getViewModel 12 | import com.zhenxiang.superimage.ui.daynight.DayNightManager 13 | import com.zhenxiang.superimage.ui.daynight.DayNightMode 14 | import com.zhenxiang.superimage.utils.IntentUtils 15 | import org.koin.core.component.KoinComponent 16 | import org.koin.core.component.inject 17 | 18 | class SettingsPageComponent( 19 | componentContext: ComponentContext, 20 | navigation: StackNavigation 21 | ): ChildComponent(componentContext, navigation) { 22 | 23 | override val viewModel = getViewModel(::ViewModel) 24 | class ViewModel: ChildComponent.ViewModel(), KoinComponent { 25 | 26 | private val dataStore by inject>(SETTINGS_DATA_STORE_QUALIFIER) 27 | 28 | val themeMode = IntPreferenceState(dataStore, DayNightManager.THEME_MODE_KEY, DayNightMode.AUTO.id) 29 | } 30 | 31 | companion object { 32 | 33 | const val GITHUB_PAGE_URL = "github.com/Lucchetto/SuperImage" 34 | 35 | fun openGithubPage(context: Context) { 36 | context.startActivity(IntentUtils.openStringUriIntent("https://$GITHUB_PAGE_URL")) 37 | } 38 | 39 | fun openTelegramGroup(context: Context) { 40 | context.startActivity(IntentUtils.openStringUriIntent("https://t.me/super_image")) 41 | } 42 | 43 | fun openPatreonPage(context: Context) { 44 | context.startActivity(IntentUtils.openStringUriIntent("https://patreon.com/SuperImage")) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/home/Banner.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.home 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material3.Icon 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Surface 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.platform.LocalContext 13 | import androidx.compose.ui.res.painterResource 14 | import androidx.compose.ui.res.stringResource 15 | import androidx.compose.ui.tooling.preview.Preview 16 | import com.zhenxiang.superimage.R 17 | import com.zhenxiang.superimage.settings.SettingsPageComponent 18 | import com.zhenxiang.superimage.ui.mono.* 19 | import com.zhenxiang.superimage.ui.theme.MonoTheme 20 | import com.zhenxiang.superimage.ui.theme.spacing 21 | 22 | @Composable 23 | fun DesktopVersionBanner(modifier: Modifier = Modifier) = Row(modifier = modifier) { 24 | Icon( 25 | painterResource(id = R.drawable.ic_laptop_24), 26 | modifier = Modifier.padding(end = MaterialTheme.spacing.level3), 27 | contentDescription = null 28 | ) 29 | Column { 30 | Text( 31 | stringResource(id = R.string.desktop_version_banner_text), 32 | style = MaterialTheme.typography.bodyMedium 33 | ) 34 | val context = LocalContext.current 35 | MonoButton( 36 | modifier = Modifier 37 | .align(Alignment.End) 38 | .padding(top = MaterialTheme.spacing.level4), 39 | onClick = { 40 | SettingsPageComponent.openPatreonPage(context) 41 | } 42 | ) { 43 | MonoButtonIcon( 44 | painter = painterResource(id = R.drawable.ic_patreon_24), 45 | contentDescription = null 46 | ) 47 | EllipsisText(text = stringResource(id = R.string.join_patreon_label)) 48 | } 49 | } 50 | } 51 | 52 | @Composable 53 | @Preview 54 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) 55 | private fun DesktopVersionBannerPreview() = MonoTheme { 56 | Surface { 57 | DesktopVersionBanner(Modifier.padding(MaterialTheme.spacing.level5)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /playstore/impl/src/test/java/com/zhenxiang/superimage/playstore/InAppReviewManagerTest.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.playstore 2 | 3 | import android.app.Activity 4 | import com.google.android.play.core.review.ReviewManager 5 | import com.google.android.play.core.review.ReviewManagerFactory 6 | import io.mockk.every 7 | import io.mockk.mockk 8 | import io.mockk.mockkStatic 9 | import io.mockk.verify 10 | import org.junit.Test 11 | 12 | import org.junit.Before 13 | 14 | /** 15 | * Example local unit test, which will execute on the development machine (host). 16 | * 17 | * See [testing documentation](http://d.android.com/tools/testing). 18 | */ 19 | class InAppReviewManagerTest { 20 | 21 | private lateinit var inAppReviewManager: InAppReviewManager 22 | private val playStoreReviewManager = mockk() 23 | 24 | @Before 25 | fun setup() { 26 | mockkStatic(ReviewManagerFactory::create) 27 | every { ReviewManagerFactory.create(any()) } returns playStoreReviewManager 28 | inAppReviewManager = InAppReviewManager(mockk()) 29 | } 30 | 31 | @Test 32 | fun testPrepareReviewInfo_whenReviewInfoNull() { 33 | every { playStoreReviewManager.requestReviewFlow() } returns mockk { 34 | every { addOnCompleteListener(any()) } returns this 35 | } 36 | inAppReviewManager.prepareReviewInfo() 37 | inAppReviewManager.prepareReviewInfo() 38 | 39 | verify(exactly = 1) { playStoreReviewManager.requestReviewFlow() } 40 | } 41 | 42 | @Test 43 | fun testPrepareReviewInfo_whenReviewInfoAlreadyLoaded() { 44 | every { playStoreReviewManager.requestReviewFlow() } returns mockk { 45 | every { addOnCompleteListener(any()) } returns this 46 | } 47 | inAppReviewManager.reviewInfo = mockk() 48 | 49 | inAppReviewManager.prepareReviewInfo() 50 | 51 | verify(inverse = true) { playStoreReviewManager.requestReviewFlow() } 52 | } 53 | 54 | @Test 55 | fun testLaunchReviewFlow() { 56 | inAppReviewManager.reviewInfo = mockk() 57 | val fakeActivity = mockk() 58 | every { playStoreReviewManager.launchReviewFlow(any(), any()) } returns mockk() 59 | 60 | inAppReviewManager.launchReviewFlow(fakeActivity) 61 | inAppReviewManager.launchReviewFlow(fakeActivity) 62 | 63 | verify(exactly = 1) { playStoreReviewManager.launchReviewFlow(any(), any()) } 64 | } 65 | } -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.compose.material3.ExperimentalMaterial3Api 7 | import androidx.compose.material3.Scaffold 8 | import androidx.compose.material3.ScaffoldDefaults 9 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 10 | import androidx.core.view.WindowCompat 11 | import androidx.lifecycle.lifecycleScope 12 | import androidx.navigation.compose.NavHost 13 | import com.arkivanov.decompose.defaultComponentContext 14 | import com.zhenxiang.superimage.navigation.RootComponent 15 | import com.zhenxiang.superimage.navigation.RootNavigation 16 | import com.zhenxiang.superimage.ui.daynight.DayNightManager 17 | import com.zhenxiang.superimage.ui.daynight.DayNightMode 18 | import com.zhenxiang.superimage.ui.theme.MonoTheme 19 | import kotlinx.coroutines.launch 20 | import org.koin.android.ext.android.inject 21 | 22 | class MainActivity : AppCompatActivity() { 23 | 24 | private val dayNightManager by inject() 25 | 26 | @OptIn(ExperimentalMaterial3Api::class) 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | // Draw edge-to-edge 29 | WindowCompat.setDecorFitsSystemWindows(window, false) 30 | lifecycleScope.launch { 31 | dayNightManager.themeModeFlow.collect { 32 | delegate.localNightMode = it.delegateNightMode 33 | } 34 | } 35 | // Handle the splash screen transition. 36 | installSplashScreen() 37 | super.onCreate(savedInstanceState) 38 | val rootComponent = RootComponent(defaultComponentContext()) 39 | setContent { 40 | /** 41 | * Due to this bug https://issuetracker.google.com/issues/227926002 when using 42 | * splash screen API on some devices with Compose Jetpack Navigation leads to a blank 43 | * screen. However wrapping the [NavHost] in a [Scaffold] solves this issue, so after 44 | * digging a bit, I figured out the following line is the workaround for the 45 | * blank screen bug 46 | */ 47 | ScaffoldDefaults.contentWindowInsets 48 | MonoTheme(DayNightMode.fromDelegateNightMode(delegate.localNightMode).lightMode) { 49 | RootNavigation(rootComponent) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /decompose/src/main/java/com/arkivanov/essenty/lifecycle/ext/LifecycleEvent.kt: -------------------------------------------------------------------------------- 1 | package com.arkivanov.essenty.lifecycle.ext 2 | 3 | import com.arkivanov.essenty.lifecycle.Lifecycle 4 | import com.arkivanov.essenty.lifecycle.Lifecycle.State 5 | import com.arkivanov.essenty.lifecycle.LifecycleOwner 6 | 7 | enum class LifecycleEvent { 8 | /** 9 | * Constant for onCreate event of the [LifecycleOwner]. 10 | */ 11 | ON_CREATE, 12 | 13 | /** 14 | * Constant for onStart event of the [LifecycleOwner]. 15 | */ 16 | ON_START, 17 | 18 | /** 19 | * Constant for onResume event of the [LifecycleOwner]. 20 | */ 21 | ON_RESUME, 22 | 23 | /** 24 | * Constant for onPause event of the [LifecycleOwner]. 25 | */ 26 | ON_PAUSE, 27 | 28 | /** 29 | * Constant for onStop event of the [LifecycleOwner]. 30 | */ 31 | ON_STOP, 32 | 33 | /** 34 | * Constant for onDestroy event of the [LifecycleOwner]. 35 | */ 36 | ON_DESTROY; 37 | 38 | companion object { 39 | /** 40 | * Returns the [LifecycleEvent] that will be reported by a [Lifecycle] 41 | * leaving the specified [State] to a lower state, or `null` 42 | * if there is no valid event that can move down from the given state. 43 | * 44 | * @param state the higher state that the returned event will transition down from 45 | * @return the event moving down the lifecycle phases from state 46 | */ 47 | @JvmStatic 48 | fun downFrom(state: State): LifecycleEvent? { 49 | return when (state) { 50 | State.CREATED -> ON_DESTROY 51 | State.STARTED -> ON_STOP 52 | State.RESUMED -> ON_PAUSE 53 | else -> null 54 | } 55 | } 56 | 57 | /** 58 | * Returns the [LifecycleEvent] that will be reported by a [Lifecycle] 59 | * entering the specified [State] from a lower state, or `null` 60 | * if there is no valid event that can move up to the given state. 61 | * 62 | * @param state the higher state that the returned event will transition up to 63 | * @return the event moving up the lifecycle phases to state 64 | */ 65 | @JvmStatic 66 | fun upTo(state: State): LifecycleEvent? { 67 | return when (state) { 68 | State.CREATED -> ON_CREATE 69 | State.STARTED -> ON_START 70 | State.RESUMED -> ON_RESUME 71 | else -> null 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /realesrgan/core/image_tile_interpreter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Zhenxiang Chen on 06/02/23. 3 | // 4 | 5 | #include 6 | 7 | #include "image_tile_interpreter.h" 8 | 9 | ImageTileInterpreter::ImageTileInterpreter( 10 | const mnn_model* model, 11 | const image_dimensions tile_dimensions) : tile_dimensions(tile_dimensions) { 12 | MNN::ScheduleConfig config; 13 | MNN::BackendConfig backendConfig; 14 | backendConfig.memory = MNN::BackendConfig::Memory_High; 15 | backendConfig.power = MNN::BackendConfig::Power_High; 16 | backendConfig.precision = MNN::BackendConfig::Precision_Low; 17 | config.backendConfig = &backendConfig; 18 | config.type = MNN_FORWARD_VULKAN; 19 | config.backupType = MNN_FORWARD_OPENCL; 20 | config.numThread = std::thread::hardware_concurrency(); 21 | 22 | interpreter = MNN::Interpreter::createFromBuffer(model->data, model->size); 23 | if (interpreter == nullptr) { 24 | throw ImageTileInterpreterException(ImageTileInterpreterException::Error::CreateInterpreterFailed); 25 | } 26 | 27 | session = interpreter->createSession(config); 28 | if (session == nullptr) { 29 | throw ImageTileInterpreterException(ImageTileInterpreterException::Error::CreateBackendFailed); 30 | } 31 | 32 | interpreter_input = interpreter->getSessionInput(session, nullptr); 33 | // We store matrix as row major so ignore MNN default tensor orientation 34 | interpreter->resizeTensor( 35 | interpreter_input, 36 | 1, 37 | REALESRGAN_IMAGE_CHANNELS, 38 | tile_dimensions.width, 39 | tile_dimensions.height); 40 | interpreter->resizeSession(session); 41 | interpreter_output = interpreter->getSessionOutput(session, nullptr); 42 | 43 | input_tensor = new MNN::Tensor(interpreter_input, MNN::Tensor::CAFFE); 44 | output_tensor = new MNN::Tensor(interpreter_output, MNN::Tensor::CAFFE); 45 | 46 | input_buffer = input_tensor->host(); 47 | output_buffer = output_tensor->host(); 48 | } 49 | 50 | void ImageTileInterpreter::inference() const { 51 | // Feed data to the interpreter 52 | interpreter_input->copyFromHostTensor(input_tensor); 53 | 54 | // Run the interpreter 55 | interpreter->runSession(session); 56 | 57 | // Extract result from interpreter 58 | interpreter_output->copyToHostTensor(output_tensor); 59 | } 60 | 61 | ImageTileInterpreter::~ImageTileInterpreter() { 62 | MNN::Tensor::destroy(input_tensor); 63 | MNN::Tensor::destroy(output_tensor); 64 | interpreter->releaseSession(session); 65 | MNN::Interpreter::destroy(interpreter); 66 | } 67 | -------------------------------------------------------------------------------- /realesrgan/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.18.1) 7 | 8 | # Declares and names the project. 9 | project("realesrgan") 10 | 11 | set(EIGEN_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/eigen") 12 | 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17") 14 | set(CMAKE_CXX_STANDARD 17) 15 | 16 | set( 17 | REALERSGAN_SOURCES 18 | 19 | jni_common/coroutine_utils.cpp 20 | jni_common/mnn_model.cpp 21 | jni_common/progress_tracker.cpp 22 | image_tile_interpreter.cpp 23 | upscaling.cpp 24 | ) 25 | 26 | if(ANDROID) 27 | # Reproducible builds 28 | add_link_options("LINKER:--hash-style=gnu,--build-id=none") 29 | 30 | list(APPEND REALERSGAN_SOURCES android/realesrgan_jni.cpp android/bitmap_utils.cpp) 31 | 32 | find_library(jnigraphics-lib jnigraphics) 33 | else() 34 | list(APPEND REALERSGAN_SOURCES desktop/realesrgan_jni.cpp desktop/image_utils.cpp) 35 | 36 | find_package(JNI REQUIRED) 37 | include_directories(${JNI_INCLUDE_DIRS}) 38 | endif() 39 | 40 | option(MNN_VULKAN "" ON) 41 | option(MNN_OPENCL "" ON) 42 | 43 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/MNN) 44 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/MNN/include) 45 | 46 | # Creates and names a library, sets it as either STATIC 47 | # or SHARED, and provides the relative paths to its source code. 48 | # You can define multiple libraries, and CMake builds them for you. 49 | # Gradle automatically packages shared libraries with your APK. 50 | 51 | add_library( # Sets the name of the library. 52 | realesrgan 53 | 54 | # Sets the library as a shared library. 55 | SHARED 56 | 57 | # Provides a relative path to your source file(s). 58 | ${REALERSGAN_SOURCES}) 59 | 60 | # Searches for a specified prebuilt library and stores the path as a 61 | # variable. Because CMake includes system libraries in the search path by 62 | # default, you only need to specify the name of the public NDK library 63 | # you want to add. CMake verifies that the library exists before 64 | # completing its build. 65 | 66 | include_directories(${EIGEN_INCLUDE}) 67 | 68 | # Specifies libraries CMake should link to your target library. You 69 | # can link multiple libraries, such as libraries you define in this 70 | # build script, prebuilt third-party libraries, or system libraries. 71 | 72 | target_link_libraries( # Specifies the target library. 73 | realesrgan 74 | 75 | ${jnigraphics-lib} 76 | MNN) -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/mono/BlurShadowProvider.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.mono 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.Canvas 6 | import com.zhenxiang.superimage.utils.BitmapUtils 7 | import eightbitlab.com.blurview.BlurAlgorithm 8 | import eightbitlab.com.blurview.RenderScriptBlur 9 | import timber.log.Timber 10 | import kotlin.math.roundToInt 11 | 12 | class BlurShadowProvider(context: Context) { 13 | 14 | private val blurEffect: BlurAlgorithm = RenderScriptBlur(context) 15 | 16 | fun getBlurShadow(input: Bitmap, radius: Float): Bitmap? = BitmapUtils.copyToSoftware(input).let { 17 | try { 18 | val downscalingRadius: Float 19 | val downscaledRadius: Float 20 | if (radius > 25f) { 21 | downscalingRadius = radius / 25 22 | downscaledRadius = 25f 23 | } else { 24 | downscalingRadius = 1f 25 | downscaledRadius = radius 26 | } 27 | 28 | val outputBitmapWidth = it.width + (radius * 2).roundToInt() 29 | val outputBitmapHeight = it.height + (radius * 2).roundToInt() 30 | 31 | // Downscale input bitmap to multiply actual blur radius and performance 32 | val downscaledInput = Bitmap.createScaledBitmap( 33 | it, 34 | (it.width / downscalingRadius).roundToInt(), 35 | (it.height / downscalingRadius).roundToInt(), 36 | true 37 | ) 38 | 39 | // Draw the downscaled input into another bitmap for blurring 40 | val blurBitmap = Bitmap.createBitmap( 41 | (outputBitmapWidth / downscalingRadius).roundToInt(), 42 | (outputBitmapHeight / downscalingRadius).roundToInt(), 43 | Bitmap.Config.ARGB_8888 44 | ) 45 | val canvas = Canvas(blurBitmap) 46 | canvas.drawBitmap(downscaledInput, downscaledRadius, downscaledRadius, null) 47 | 48 | // Blur, yeah 49 | blurEffect.blur(blurBitmap, downscaledRadius) 50 | 51 | // Upscale the blurred bitmap again 52 | val upscaledBlurBitmap = Bitmap.createScaledBitmap( 53 | blurBitmap, 54 | outputBitmapWidth, 55 | outputBitmapHeight, 56 | true 57 | ) 58 | downscaledInput.recycle() 59 | blurBitmap.recycle() 60 | 61 | return upscaledBlurBitmap 62 | } catch (e: Exception) { 63 | Timber.e("Failed to render blur shadow") 64 | Timber.e(e) 65 | 66 | return null 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/mono/Button.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.mono 2 | 3 | import androidx.compose.foundation.interaction.MutableInteractionSource 4 | import androidx.compose.foundation.layout.PaddingValues 5 | import androidx.compose.foundation.layout.RowScope 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material3.* 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.painter.Painter 13 | import androidx.compose.ui.graphics.vector.ImageVector 14 | import androidx.compose.ui.res.painterResource 15 | import androidx.compose.ui.unit.dp 16 | import com.zhenxiang.superimage.R 17 | import com.zhenxiang.superimage.ui.theme.border 18 | import com.zhenxiang.superimage.ui.theme.spacing 19 | 20 | @Composable 21 | fun MonoButton( 22 | onClick: () -> Unit, 23 | modifier: Modifier = Modifier, 24 | enabled: Boolean = true, 25 | colors: ButtonColors = ButtonDefaults.outlinedButtonColors(), 26 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 27 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 28 | content: @Composable RowScope.() -> Unit 29 | ) { 30 | OutlinedButton( 31 | onClick = onClick, 32 | modifier = modifier, 33 | enabled = enabled, 34 | shape = MaterialTheme.shapes.small, 35 | colors = colors, 36 | border = if (enabled) { 37 | MaterialTheme.border.regular 38 | } else { 39 | MaterialTheme.border.RegularWithAlpha(MonoButtonDefaults.DisableBorderColourOpacity) 40 | }, 41 | contentPadding = contentPadding, 42 | interactionSource = interactionSource, 43 | content = content 44 | ) 45 | } 46 | 47 | @Composable 48 | fun MonoButtonIcon( 49 | painter: Painter, 50 | contentDescription: String?, 51 | modifier: Modifier = Modifier, 52 | ) = Icon( 53 | painter, 54 | contentDescription = contentDescription, 55 | modifier = modifier 56 | .padding(end = MaterialTheme.spacing.level3) 57 | .size(MonoButtonDefaults.IconSize) 58 | ) 59 | 60 | @Composable 61 | fun MonoButtonIcon( 62 | imageVector: ImageVector, 63 | contentDescription: String?, 64 | modifier: Modifier = Modifier, 65 | ) = Icon( 66 | imageVector, 67 | contentDescription = contentDescription, 68 | modifier = modifier 69 | .padding(end = MaterialTheme.spacing.level3) 70 | .size(MonoButtonDefaults.IconSize) 71 | ) 72 | 73 | object MonoButtonDefaults { 74 | 75 | val IconSize = 18.dp 76 | 77 | /** 78 | * Matches [OutlinedButtonTokens.DisabledLabelTextOpacity]'s value 79 | * Fuck Google for making every constant internal 80 | */ 81 | val DisableBorderColourOpacity = 0.38f 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/mono/AppBar.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.mono 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.res.Configuration 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.layout.* 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.Build 9 | import androidx.compose.material.icons.rounded.ArrowBack 10 | import androidx.compose.material3.* 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import com.zhenxiang.superimage.ui.theme.MonoTheme 16 | import com.zhenxiang.superimage.ui.theme.border 17 | import com.zhenxiang.superimage.ui.theme.spacing 18 | import com.zhenxiang.superimage.ui.utils.RowSpacer 19 | 20 | @Composable 21 | fun MonoAppBar( 22 | title: @Composable () -> Unit, 23 | modifier: Modifier = Modifier, 24 | windowInsets: WindowInsets = MonoAppBarDefaults.windowInsets, 25 | leadingIcon: @Composable () -> Unit = { }, 26 | trailingIcons: @Composable RowScope.() -> Unit = { } 27 | ) = Row( 28 | modifier = modifier 29 | .background(MaterialTheme.colorScheme.primaryContainer) 30 | .windowInsetsPadding(windowInsets) 31 | .fillMaxWidth() 32 | .drawBottomBorder(MaterialTheme.border.regular) 33 | .padding(MaterialTheme.spacing.level3), 34 | verticalAlignment = Alignment.CenterVertically 35 | ) { 36 | leadingIcon() 37 | Box(modifier = Modifier.padding(MaterialTheme.spacing.level3)) { 38 | ProvideTextStyle( 39 | value = MaterialTheme.typography.displayMedium, 40 | content = title 41 | ) 42 | } 43 | RowSpacer() 44 | trailingIcons() 45 | } 46 | 47 | object MonoAppBarDefaults { 48 | 49 | val windowInsets: WindowInsets 50 | @Composable 51 | get() = WindowInsets.safeDrawing 52 | .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) 53 | } 54 | 55 | @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") 56 | @OptIn(ExperimentalMaterial3Api::class) 57 | @Preview(showBackground = true, showSystemUi = true) 58 | @Preview(showBackground = true, showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES) 59 | @Composable 60 | private fun MonoAppBarPreview() = MonoTheme { 61 | Scaffold( 62 | topBar = { 63 | MonoAppBar( 64 | title = { Text("AppBar") }, 65 | leadingIcon = { 66 | IconButton( 67 | onClick = {} 68 | ) { 69 | Icon(Icons.Rounded.ArrowBack, contentDescription = null) 70 | } 71 | } 72 | ) { 73 | IconButton( 74 | onClick = {} 75 | ) { 76 | Icon(Icons.Default.Build, contentDescription = null) 77 | } 78 | } 79 | } 80 | ) {} 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | 55 | 58 | 59 | 60 | 65 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SuperImage 2 | **Sharpen your low-resolution pictures with the power of AI upscaling**

3 | SuperImage is a neural network based image upscaling application for Android built with the [MNN deep learning framework](https://github.com/alibaba/MNN) and [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN).

4 | 5 | The input image is processed in tiles on the device GPU, using a pre-trained Real-ESRGAN model. The tiles are then merged into the final high-resolution image. This application requires Vulkan or OpenCL support and Android 7 or above 6 | 7 | Get it on Google Play 8 | Get it on F-Droid 9 | 10 | Or get the latest APK from the [Releases Section](https://github.com/Lucchetto/SuperImage/releases/latest). 11 | 12 | ## 🖼 Samples 13 |
14 | 15 | 16 | 17 |
18 | 19 | ## 📊 Benchmarks 20 | Results on Qualcomm Snapdragon 855 (Vulkan) 21 | | Mode | Input resolution | Output resolution | Execution time | 22 | | ------------- | ---------------- | ----------------- | ----------------- | 23 | | 4x (generic) | 1920x1080 | 3840x2160 | 3 minutes | 24 | | 16x (generic) | 1920x1080 | 7680x4320 | 11 minutes | 25 | | 16x (drawing) | 1920x1080 | 7680x4320 | 3 mins 42 seconds | 26 | 27 | ## 📱 Screenshots 28 |

29 |   30 | 31 |    32 | 33 |   34 |

35 | 36 | ## 💬 Community 37 | You can join the [Telegram group](https://t.me/super_image) for support, discussions about AI image processing, and off-topic stuff 38 | 39 | ## 協 Contribute 40 | You can submit feedbacks or bug reports by [opening an issue](https://github.com/Lucchetto/SuperImage/issues/new). Pull requests are welcome ! 41 | 42 | ## 📚 TODO 43 | - Support images with transparency 44 | - Batch processing 45 | - Web and desktop versions 46 | 47 | ## 📝 Credits 48 | - Pre-trained models and original implementation from [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) 49 | - Pictures by [Satoshi Hirayama](https://www.pexels.com/photo/yasaka-pagoda-in-kyoto-7526805), [Skitterphoto](https://www.pexels.com/photo/food-japanese-food-photography-sushi-9210), [天江ひなた](https://www.pixiv.net/en/artworks/103802719) and [Ryutaro Tsukata](https://www.pexels.com/photo/an-illuminated-lanterns-on-the-street-5745029) 50 | 51 | ## ⚖️ License 52 | SuperImage is licensed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.html) 53 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/mono/BlurShadowImage.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.mono 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.drawable.BitmapDrawable 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.layout.SubcomposeLayout 9 | import androidx.compose.ui.platform.LocalContext 10 | import androidx.compose.ui.unit.Constraints 11 | import androidx.compose.ui.unit.dp 12 | import coil.compose.AsyncImage 13 | import coil.request.CachePolicy 14 | import coil.request.ImageRequest 15 | import coil.transition.CrossfadeTransition 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.launch 18 | import kotlin.math.roundToInt 19 | 20 | private val BLUR_RADIUS = 70.dp 21 | 22 | @Composable 23 | fun BlurShadowImage( 24 | model: ImageRequest, 25 | contentDescription: String?, 26 | modifier: Modifier = Modifier, 27 | imageModifier: Modifier = Modifier, 28 | ) { 29 | val context = LocalContext.current 30 | val coroutineScope = rememberCoroutineScope() 31 | val blurShadowProvider = remember { BlurShadowProvider(context) } 32 | var blurShadowBitmap by remember { mutableStateOf(null) } 33 | val crossfade = remember { CrossfadeTransition.Factory(1000) } 34 | 35 | SubcomposeLayout(modifier) { constraints -> 36 | 37 | val blurRadius = BLUR_RADIUS.value * density 38 | val blurRadiusInt = blurRadius.roundToInt() 39 | 40 | val imagePlaceable = subcompose(0) { 41 | AsyncImage( 42 | modifier = imageModifier, 43 | model = model, 44 | contentDescription = contentDescription, 45 | onLoading = { blurShadowBitmap = null }, 46 | onSuccess = { 47 | coroutineScope.launch(Dispatchers.IO) { 48 | (it.result.drawable as? BitmapDrawable)?.bitmap?.let { bitmap -> 49 | blurShadowProvider.getBlurShadow(bitmap, blurRadius)?.let { blur -> 50 | blurShadowBitmap = blur 51 | } 52 | } 53 | } 54 | } 55 | ) 56 | }[0].measure(constraints) 57 | 58 | val blurPlaceable = blurShadowBitmap?.let { 59 | subcompose(1) { 60 | AsyncImage( 61 | modifier = Modifier.fillMaxSize(), 62 | model = ImageRequest.Builder(LocalContext.current) 63 | .data(it) 64 | .transitionFactory(crossfade) 65 | .memoryCachePolicy(CachePolicy.DISABLED) 66 | .build(), 67 | contentDescription = null 68 | ) 69 | }[0].measure( 70 | Constraints.fixed( 71 | imagePlaceable.width + blurRadiusInt * 2, 72 | imagePlaceable.height + blurRadiusInt * 2 73 | ) 74 | ) 75 | } 76 | 77 | layout(imagePlaceable.width, imagePlaceable.height) { 78 | blurPlaceable?.place(blurRadiusInt * -1, blurRadiusInt * -1) 79 | imagePlaceable.place(0, 0) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @string/default_font 4 | 5 | SuperImage 6 | 7 | \u0020 8 | \u0020 9 | 10 | 小时 11 | 小时 12 | 13 | 14 | 分钟 15 | 分钟 16 | 17 | 18 | 19 | 20 | 21 | 22 | %d%% 23 | %1$d%% (预计剩余. %2$s) 24 | 初始化中… 25 | 打开 26 | 重试 27 | 关闭 28 | 取消 29 | 设置 30 | 返回 31 | 32 | 原始分辨率:%1$sx%2$s 33 | 输出分辨率:%1$sx%2$s 34 | 选择一张图片进行画质增强 35 | 选择图片 36 | 更改图片 37 | 画质增强 38 | 模式选择 39 | 增强选项 40 | 输出格式 41 | 处理时间:%1$s 42 | 新增特性 43 | 版本 %1$s 44 | - %1$s 45 | 46 | 没有权限 47 | 请授予写入储存设备权限 48 | 请在设置中授予读取储存设备权限 49 | 打开设置 50 | 授予权限 51 | 52 | 主题 53 | 跟随系统 54 | 浅色 55 | 深色 56 | 项目页面 57 | 在Github上提问或作出贡献 58 | 电报群组 59 | 加入群组寻求帮助,或讨论有关AI图像处理与其它话题 60 | 版本 61 | 62 | 画质增强中 %1$s 63 | 您的设备在处理过程中可能变慢 64 | 画质增强时发生了一个错误 %1$s 65 | 设备不支持 66 | 您的设备不支持处理图像所需的Vulkan或OpenCL功能 67 | %1$s 画质增强成功! 68 | 查看图片 69 | 增强过程 70 | 增强结果 71 | -------------------------------------------------------------------------------- /decompose/src/main/java/com/arkivanov/essenty/lifecycle/ext/LifecycleExt.kt: -------------------------------------------------------------------------------- 1 | package com.arkivanov.essenty.lifecycle.ext 2 | 3 | import com.arkivanov.essenty.lifecycle.Lifecycle 4 | import kotlinx.coroutines.* 5 | import kotlinx.coroutines.sync.Mutex 6 | import kotlinx.coroutines.sync.withLock 7 | import kotlin.coroutines.resume 8 | 9 | inline fun observeEvents(crossinline stateObserver: (LifecycleEvent) -> Unit) = object : Lifecycle.Callbacks { 10 | 11 | override fun onCreate() = stateObserver(LifecycleEvent.ON_CREATE) 12 | 13 | override fun onStart() = stateObserver(LifecycleEvent.ON_START) 14 | 15 | override fun onResume() = stateObserver(LifecycleEvent.ON_RESUME) 16 | 17 | override fun onPause() = stateObserver(LifecycleEvent.ON_PAUSE) 18 | 19 | override fun onStop() = stateObserver(LifecycleEvent.ON_STOP) 20 | 21 | override fun onDestroy() = stateObserver(LifecycleEvent.ON_DESTROY) 22 | } 23 | 24 | suspend fun Lifecycle.repeatOnLifecycle( 25 | targetState: Lifecycle.State, 26 | block: suspend CoroutineScope.() -> Unit 27 | ) { 28 | require(targetState !== Lifecycle.State.INITIALIZED) { 29 | "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state." 30 | } 31 | 32 | if (state === Lifecycle.State.DESTROYED) { 33 | return 34 | } 35 | 36 | // This scope is required to preserve context before we move to Dispatchers.Main 37 | coroutineScope { 38 | withContext(Dispatchers.Main.immediate) { 39 | // Check the current state of the lifecycle as the previous check is not guaranteed 40 | // to be done on the main thread. 41 | if (state === Lifecycle.State.DESTROYED) return@withContext 42 | 43 | // Instance of the running repeating coroutine 44 | var launchedJob: Job? = null 45 | 46 | // Registered observer 47 | var observer: Lifecycle.Callbacks? = null 48 | try { 49 | // Suspend the coroutine until the lifecycle is destroyed or 50 | // the coroutine is cancelled 51 | suspendCancellableCoroutine { cont -> 52 | // Lifecycle observers that executes `block` when the lifecycle reaches certain state, and 53 | // cancels when it falls below that state. 54 | val startWorkEvent = LifecycleEvent.upTo(targetState) 55 | val cancelWorkEvent = LifecycleEvent.downFrom(targetState) 56 | val mutex = Mutex() 57 | observer = observeEvents { event -> 58 | if (event == startWorkEvent) { 59 | // Launch the repeating work preserving the calling context 60 | launchedJob = this@coroutineScope.launch { 61 | // Mutex makes invocations run serially, 62 | // coroutineScope ensures all child coroutines finish 63 | mutex.withLock { 64 | coroutineScope { 65 | block() 66 | } 67 | } 68 | } 69 | return@observeEvents 70 | } 71 | if (event == cancelWorkEvent) { 72 | launchedJob?.cancel() 73 | launchedJob = null 74 | } 75 | if (event == LifecycleEvent.ON_DESTROY) { 76 | cont.resume(Unit) 77 | } 78 | }.also { subscribe(it) } 79 | } 80 | } finally { 81 | launchedJob?.cancel() 82 | observer?.let { 83 | this@repeatOnLifecycle.unsubscribe(it) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.theme 2 | 3 | import android.app.Activity 4 | import android.graphics.Color 5 | import android.os.Build 6 | import androidx.compose.foundation.isSystemInDarkTheme 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.lightColorScheme 9 | import androidx.compose.material3.darkColorScheme 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.SideEffect 12 | import androidx.compose.ui.platform.LocalView 13 | import androidx.core.view.WindowCompat 14 | 15 | private val LightColors = lightColorScheme( 16 | primary = black, 17 | onPrimary = white, 18 | primaryContainer = white, 19 | onPrimaryContainer = black, 20 | secondary = black, 21 | onSecondary = white, 22 | secondaryContainer = white, 23 | onSecondaryContainer = black, 24 | tertiary = black, 25 | onTertiary = white, 26 | tertiaryContainer = white, 27 | onTertiaryContainer = black, 28 | error = md_theme_light_error, 29 | onError = md_theme_light_onError, 30 | errorContainer = md_theme_light_errorContainer, 31 | onErrorContainer = md_theme_light_onErrorContainer, 32 | outline = black, 33 | background = white, 34 | onBackground = black, 35 | surface = white, 36 | onSurface = black, 37 | surfaceVariant = white, 38 | onSurfaceVariant = black, 39 | inverseSurface = black, 40 | inverseOnSurface = white, 41 | inversePrimary = md_theme_light_inversePrimary, 42 | surfaceTint = white, 43 | outlineVariant = md_theme_light_outlineVariant, 44 | scrim = md_theme_light_scrim, 45 | ) 46 | 47 | 48 | private val DarkColors = darkColorScheme( 49 | primary = white, 50 | onPrimary = black, 51 | primaryContainer = black, 52 | onPrimaryContainer = white, 53 | secondary = white, 54 | onSecondary = black, 55 | secondaryContainer = black, 56 | onSecondaryContainer = white, 57 | tertiary = white, 58 | onTertiary = black, 59 | tertiaryContainer = black, 60 | onTertiaryContainer = white, 61 | error = md_theme_dark_error, 62 | onError = md_theme_dark_onError, 63 | errorContainer = md_theme_dark_errorContainer, 64 | onErrorContainer = md_theme_dark_onErrorContainer, 65 | outline = white, 66 | background = black, 67 | onBackground = white, 68 | surface = black, 69 | onSurface = white, 70 | surfaceVariant = black, 71 | onSurfaceVariant = white, 72 | inverseSurface = white, 73 | inverseOnSurface = black, 74 | inversePrimary = md_theme_dark_inversePrimary, 75 | surfaceTint = black, 76 | outlineVariant = md_theme_dark_outlineVariant, 77 | scrim = md_theme_dark_scrim, 78 | ) 79 | 80 | @Composable 81 | fun MonoTheme( 82 | lightMode: Boolean = !isSystemInDarkTheme(), 83 | content: @Composable () -> Unit 84 | ) { 85 | val colors = if (lightMode) { 86 | LightColors 87 | } else { 88 | DarkColors 89 | } 90 | 91 | val view = LocalView.current 92 | val activity = view.context as? Activity 93 | activity?.let { 94 | val window = it.window 95 | if (!view.isInEditMode) { 96 | SideEffect { 97 | window.statusBarColor = Color.TRANSPARENT 98 | window.navigationBarColor = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && lightMode) Color.BLACK else Color.TRANSPARENT 99 | WindowCompat.getInsetsController(window, view).apply { 100 | isAppearanceLightStatusBars = lightMode 101 | isAppearanceLightNavigationBars = lightMode 102 | } 103 | } 104 | } 105 | } 106 | 107 | 108 | MaterialTheme( 109 | colorScheme = colors, 110 | shapes = Shapes, 111 | content = content, 112 | typography = Typography 113 | ) 114 | } -------------------------------------------------------------------------------- /app/src/main/res/values-tr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @string/default_font 4 | 5 | SuperImage 6 | 7 | \u0020 8 | \u0020 9 | 10 | saat 11 | saat 12 | 13 | 14 | dakika 15 | dakika 16 | 17 | 18 | saniye 19 | saniye 20 | 21 | 22 | %d%% 23 | %1$d%% (yaklaşık. %2$s kaldı) 24 | Başlatılıyor… 25 | 26 | Yeniden Dene 27 | Kapa 28 | İptal 29 | Ayarlar 30 | Geri 31 | 32 | Orijinal çözünürlük: %1$sx%2$s 33 | Çıkan çözünürlük: %1$sx%2$s 34 | Başlamak için, çözünürlüğü yükseltilecek bir resim seçin 35 | Resim seç 36 | Resim değiştir 37 | Çözünürlüğü yükselt 38 | Seçili mod 39 | Çözünürlük yükseltme ayarları 40 | Çıktı formatı 41 | Uygulanma süresi: %1$s 42 | Yeni 43 | Sürüm %1$s 44 | - %1$s 45 | 46 | İzin reddedildi 47 | Lütfen cihazda dosya yazma izni verin 48 | Lütfen ayarlardan cihazda dosya yazma izni verin 49 | Ayarları aç 50 | İzin ver 51 | 52 | Tema 53 | Sistem 54 | Aydınlık 55 | Karanlık 56 | Proje sayfası 57 | GitHub\'da soru sorun veya katılım yapın 58 | Telegram grubu 59 | Destek, yapay zeka resim işleme hakkında konuşma, ve konu-dışı şeyler için gruba katılın 60 | Sürüm 61 | 62 | %1$s Çözünürlüğü Yükseltilyor 63 | Cihazınız süreç boyunca yavaşlıyabilir 64 | Çözünürlük yükseltilirken bir hata oluştu %1$s 65 | Desteklenmeyen cihaz 66 | Cihazınız resimlerin çözünürlüğünü yükseltmek için gereken Vulkan compute veya OpenCL\'i desteklemiyor 67 | %1$s başarıyla çözünürlüğü yükseltildi ! 68 | Resmi açmak için buraya basın 69 | Çözünürlük yükseltme ilerlemesi 70 | Çözünürlük yükseltme sonucu 71 | 72 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.ExperimentalComposeLibrary 2 | 3 | plugins { 4 | id("com.android.application") 5 | id("org.jetbrains.kotlin.android") 6 | id("org.jetbrains.compose") 7 | id("kotlin-parcelize") 8 | id("android-build-flavours") 9 | } 10 | 11 | android { 12 | namespace = "com.zhenxiang.superimage" 13 | compileSdk = 33 14 | 15 | val changelogFileName = "changelog.txt" 16 | 17 | defaultConfig { 18 | applicationId = "com.zhenxiang.superimage" 19 | minSdk = 24 20 | targetSdk = 33 21 | versionCode = 134 22 | versionName = "1.3.4" 23 | 24 | buildConfigField("String", "CHANGELOG_ASSET_NAME", "\"$changelogFileName\"") 25 | 26 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 27 | } 28 | 29 | // Copy changelog for app assets 30 | val copiedChangelogPath = File(buildDir, "generated/changelogAsset") 31 | val copyArtifactsTask = tasks.register("copyChangelog") { 32 | delete(copiedChangelogPath) 33 | from(File(rootProject.rootDir, "fastlane/metadata/android/en-US/changelogs/${defaultConfig.versionCode}.txt")) 34 | into(copiedChangelogPath) 35 | rename { changelogFileName } 36 | } 37 | tasks.preBuild { 38 | dependsOn(copyArtifactsTask) 39 | } 40 | 41 | buildTypes { 42 | getByName("release") { 43 | isMinifyEnabled = true 44 | setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")) 45 | } 46 | 47 | getByName("debug") { 48 | applicationIdSuffix = ".debug" 49 | versionNameSuffix = "-DEBUG" 50 | } 51 | } 52 | 53 | sourceSets { 54 | getByName("main") { 55 | // Add changelog to assets 56 | assets.srcDirs(copiedChangelogPath) 57 | } 58 | } 59 | buildFeatures { 60 | compose = true 61 | } 62 | compileOptions { 63 | sourceCompatibility = JavaVersion.VERSION_1_8 64 | targetCompatibility = JavaVersion.VERSION_1_8 65 | } 66 | kotlinOptions { 67 | jvmTarget = "1.8" 68 | } 69 | packagingOptions { 70 | resources { 71 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 72 | } 73 | } 74 | } 75 | 76 | @OptIn(ExperimentalComposeLibrary::class) 77 | dependencies { 78 | 79 | implementation(project(":decompose")) 80 | "freeImplementation"(project(":playstore:no-op")) 81 | "playstoreImplementation"(project(":playstore:impl")) 82 | implementation(project(":realesrgan:android")) 83 | implementation(project(":shared")) 84 | 85 | val koin_android_version= "3.3.2" 86 | 87 | implementation("androidx.core:core-ktx:1.9.0") 88 | implementation("androidx.core:core-splashscreen:1.0.0") 89 | implementation("androidx.lifecycle:lifecycle-process:2.6.0-beta01") 90 | implementation(compose.runtime) 91 | implementation(compose.foundation) 92 | implementation(compose.material3) 93 | implementation(compose.preview) 94 | implementation("androidx.datastore:datastore-preferences:1.0.0") 95 | implementation("androidx.exifinterface:exifinterface:1.3.6") 96 | 97 | implementation("androidx.documentfile:documentfile:1.0.1") 98 | implementation("androidx.work:work-runtime-ktx:2.7.1") 99 | implementation("com.github.Dimezis:BlurView:version-2.0.3") 100 | implementation("com.google.accompanist:accompanist-navigation-animation:0.29.1-alpha") 101 | implementation("com.jakewharton.timber:timber:5.0.1") 102 | implementation("io.coil-kt:coil-compose:2.2.2") 103 | implementation("io.insert-koin:koin-android:$koin_android_version") 104 | implementation("joda-time:joda-time:2.12.2") 105 | implementation("org.apache.commons:commons-imaging:1.0-alpha3") 106 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.4") 107 | 108 | testImplementation("junit:junit:4.13.2") 109 | androidTestImplementation("androidx.test.ext:junit:1.1.5") 110 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 111 | androidTestImplementation(compose.uiTestJUnit4) 112 | debugImplementation(compose.uiTooling) 113 | } -------------------------------------------------------------------------------- /app/src/main/res/values-in/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @string/default_font 4 | 5 | SuperImage 6 | 7 | \u0020 8 | \u0020 9 | 10 | jam 11 | jam 12 | 13 | 14 | menit 15 | menit 16 | 17 | 18 | detik 19 | detik 20 | 21 | 22 | %d%% 23 | %1$d%% (perkiraan %2$s tersisa) 24 | Memulai 25 | Buka 26 | Coba lagi 27 | Tutup 28 | Batalkan 29 | Pengaturan 30 | Kembali 31 | 32 | Memori sistem mungkin tidak cukup untuk operasi ini 33 | Resolusi original: %1$sx%2$s 34 | Resolusi output: %1$sx%2$s 35 | Untuk memulai, pilih gambar yang ingin diperbesar 36 | Pilih gambar 37 | Ganti gambar 38 | Perbesar 39 | Mode yang dipilih 40 | Opsi pembesaran 41 | Format output 42 | Durasi eksekusi: %1$s 43 | Apa saja yang baru 44 | Versi %1$s 45 | - %1$s 46 | 47 | Aksi tidak diizinkan 48 | Izinkan akses menulis pada perangkat 49 | Silahkan memberi izin menulis pada pengatiran perangkat agar aplikasi dapat berjalan. 50 | Buka pengaturan 51 | Beri izin 52 | 53 | Tema 54 | Gunakan tema sistem 55 | Terang 56 | Gelap 57 | Halaman proyek 58 | Bertanya dan kontirbusi di GitHub 59 | Grup Telegram 60 | Gabung grup untuk diskusi, bertanya, dan berkontribusi seputar pemrosesan gambar dengan AI. 61 | Versi 62 | 63 | Memperbesar %1$s 64 | Perangkat anda mungkin akan sedikit lambat selama proses berjalan 65 | Terjadi kesalahan dalam proses %1$s 66 | Perangkat tidak didukung 67 | Perangkat anda tidak memiliki dukungan terhadap Vulkan ataupun OpenGL yang dibutuhkan untuk memproses gambar. 68 | %1$s Berhasil diperbesar! 69 | Tekan disini untuk membuka gambar 70 | Proses pembesaran 71 | Hasil pembesaran 72 | -------------------------------------------------------------------------------- /app/src/main/res/values-el/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | @string/source_serif_pro_font 3 | 4 | SuperImage 5 | 6 | \u0020 7 | \u0020 8 | 9 | ώρα 10 | ώρες 11 | 12 | 13 | λεπτό 14 | λεπτά 15 | 16 | 17 | δευτ. 18 | δευτ. 19 | 20 | 21 | %d%% 22 | %1$d%% (περίπου %2$s απομένουν) 23 | Αρχικοποίηση… 24 | Άνοιγμα 25 | Επανάληψη 26 | Κλείσιμο 27 | Άκυρο 28 | Ρυθμίσεις 29 | Πίσω 30 | 31 | Η μνήμη του συστήματος μπορεί να είναι ανεπαρκής για αυτή τη λειτουργία 32 | Αρχική ανάλυση: %1$sx%2$s 33 | Τελική ανάλυση: %1$sx%2$s 34 | Ξεκινήστε, επιλέγοντας μια εικόνα για μεγέθυνση 35 | Επιλογή εικόνας 36 | Αλλαγή 37 | Μεγέθυνση 38 | Λειτουργία 39 | Επιλογές μεγέθυνσης 40 | Αρχείο εξόδου 41 | Χρόνος εκτέλεσης: %1$s 42 | Τι νέο υπάρχει 43 | Έκδοση %1$s 44 | - %1$s 45 | 46 | Η άδεια απορρίφθηκε 47 | Παρακαλούμε χορηγήστε άδεια εγγραφής αρχείων στη συσκευή 48 | Παρακαλούμε δώστε άδεια εγγραφής αρχείων στη συσκευή, στις ρυθμίσεις 49 | Άνοιγμα ρυθμίσεων 50 | Χορήγηση άδειας 51 | 52 | Θέμα 53 | Συστήματος 54 | Φωτεινό 55 | Σκοτεινό 56 | Σελίδα έργου 57 | Κάντε ερωτήσεις ή συνεισφέρετε στο GitHub 58 | Ομάδα Telegram 59 | Γίνετε μέλος της ομάδας για υποστήριξη, συζητήσεις σχετικά με την επεξεργασία εικόνας AI και πράγματα εκτός θέματος 60 | Έκδοση 61 | 62 | Μεγέθυνση %1$s 63 | Η συσκευή σας ενδέχεται να επιβραδυνθεί κατά τη διάρκεια της διαδικασίας 64 | Παρουσιάστηκε σφάλμα κατά τη μεγέθυνση της %1$s 65 | Μη υποστηριζόμενη συσκευή 66 | Η συσκευή σας δεν υποστηρίζει Vulkan ή OpenCL που απαιτούνται για τη μεγέθυνση των εικόνων 67 | %1$s μεγεθύνθηκε επιτυχώς! 68 | Κάντε κλικ εδώ για να ανοίξετε την εικόνα 69 | Πρόοδος μεγέθυνσης 70 | Αποτέλεσμα μεγέθυνσης 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @string/source_serif_pro_font 4 | 5 | SuperImage 6 | 7 | \u0020 8 | \u0020 9 | 10 | Час 11 | Часов 12 | 13 | 14 | Мин 15 | Минут 16 | 17 | 18 | Сек 19 | Секунд 20 | 21 | 22 | %d%% 23 | %1$d%% (осталось примерно %2$s) 24 | Инициализация… 25 | Открыть 26 | Повторить 27 | Закрыть 28 | Отмена 29 | Настройки 30 | Назад 31 | 32 | Системной памяти может быть недостаточно для этой операции 33 | Оригинальное разрешение: %1$sx%2$s 34 | Выходное разрешение: %1$sx%2$s 35 | Чтобы начать, выберите изображение для увеличения 36 | Выбрать изображение 37 | Изменить 38 | Увеличить 39 | Выбранный режим 40 | Опции увеличения 41 | Выходной формат 42 | Время выполнения: %1$s 43 | Что нового? 44 | Версия %1$s 45 | - %1$s 46 | 47 | Разрешение отклонено 48 | Пожалуйста, выдайте разрешение, чтобы сохранить изображение 49 | Пожалуйста, выдайте разрешение в настройках устройства, чтобы сохранить изображение 50 | Открыть настройки 51 | Выдать разрешение 52 | 53 | Тема 54 | Системная 55 | Светлая 56 | Тёмная 57 | Страница проекта 58 | Задавайте вопросы или вносите вклад на GitHub 59 | Сообщетсво в Telegram 60 | Вступите в группу для обсуждения тем ИИ или остальных разговоров 61 | Версия 62 | 63 | Увеличение %1$s 64 | Ваше устройство может тормозить в процессе увеличения 65 | Ошибка произошла во время увеличения %1$s 66 | Неподдерживаемое устройство 67 | Ваше устройсто не поддерживает возможности Vulkan или OpenCL, которые необходимы для увеличения изображений 68 | %1$s успешно увеличено! 69 | Нажмите сюда, чтобы открыть изображение 70 | Прогресс увеличения 71 | Результат увеличения 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @string/default_font 4 | 5 | SuperImage 6 | 7 | \u0020 8 | \u0020 9 | 10 | hour 11 | hours 12 | 13 | 14 | min 15 | mins 16 | 17 | 18 | sec 19 | secs 20 | 21 | 22 | %d%% 23 | %1$d%% (approx. %2$s left) 24 | Initialising… 25 | Open 26 | Retry 27 | Close 28 | Cancel 29 | Settings 30 | Back 31 | 32 | System memory may be insufficient for this operation 33 | Original resolution: %1$sx%2$s 34 | Output resolution: %1$sx%2$s 35 | To start, select an image to upscale 36 | Select image 37 | Change image 38 | Upscale 39 | Selected mode 40 | Upscaling options 41 | Output format 42 | Execution time: %1$s 43 | What\'s new 44 | Version %1$s 45 | - %1$s 46 | 47 | Permission denied 48 | Please grant permission to write files on the device 49 | Please grant permission to write files on the device in the settings 50 | Open settings 51 | Grant permission 52 | 53 | The experimental desktop version of SuperImage is now available on Patreon 54 | Become a Patron 55 | 56 | Theme 57 | Follow system 58 | Light 59 | Dark 60 | Project page 61 | Ask questions or contribute on GitHub 62 | Telegram group 63 | Join the group for support, discussions about AI image processing, and off-topic stuff 64 | Support the project on Patreon 65 | Get access to experimental features, receive priority support, and help shape the future of SuperImage 66 | Version 67 | 68 | Upscaling %1$s 69 | Your device may slow down during the process 70 | An error occurred while upscaling %1$s 71 | Unsupported device 72 | Your device doesn\'t support the Vulkan features or OpenCL required for image upscaling 73 | %1$s upscaled successfully ! 74 | Click here to open the image 75 | Upscaling progress 76 | Upscaling result 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.zhenxiang.superimage.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.res.stringResource 6 | import androidx.compose.ui.text.TextStyle 7 | import androidx.compose.ui.text.font.Font 8 | import androidx.compose.ui.text.font.FontFamily 9 | import androidx.compose.ui.text.font.FontWeight 10 | import androidx.compose.ui.unit.sp 11 | import com.zhenxiang.superimage.R 12 | 13 | val Typography: Typography 14 | @Composable get() { 15 | val fontFamily = stringResource(R.string.font_family).let { 16 | when (it) { 17 | "source" -> FontFamily( 18 | Font(R.font.source_serif_pro_regular), 19 | Font(R.font.source_serif_pro_semibold, FontWeight.W600), 20 | Font(R.font.source_serif_pro_bold, FontWeight.Bold) 21 | ) 22 | "crimson" -> FontFamily( 23 | Font(R.font.crimson_text_regular), 24 | Font(R.font.crimson_text_semibold, FontWeight.W600), 25 | Font(R.font.crimson_text_bold, FontWeight.Bold) 26 | ) 27 | else -> throw IllegalStateException("Invalid font family for locale") 28 | } 29 | } 30 | 31 | return Typography( 32 | displayMedium = TextStyle( 33 | fontWeight = FontWeight.Normal, 34 | fontFamily = fontFamily, 35 | fontSize = 42.sp, 36 | lineHeight = 48.sp, 37 | letterSpacing = 0.sp 38 | ), 39 | headlineLarge = TextStyle( 40 | fontWeight = FontWeight.SemiBold, 41 | fontFamily = fontFamily, 42 | fontSize = 32.sp, 43 | lineHeight = 40.sp, 44 | letterSpacing = 0.sp 45 | ), 46 | headlineMedium = TextStyle( 47 | fontWeight = FontWeight.SemiBold, 48 | fontFamily = fontFamily, 49 | fontSize = 28.sp, 50 | lineHeight = 36.sp, 51 | letterSpacing = 0.sp 52 | ), 53 | headlineSmall = TextStyle( 54 | fontWeight = FontWeight.Normal, 55 | fontFamily = fontFamily, 56 | fontSize = 22.sp, 57 | lineHeight = 30.sp, 58 | letterSpacing = 0.sp 59 | ), 60 | titleLarge = TextStyle( 61 | fontWeight = FontWeight.SemiBold, 62 | fontFamily = fontFamily, 63 | fontSize = 22.sp, 64 | lineHeight = 28.sp, 65 | letterSpacing = 0.sp 66 | ), 67 | titleMedium = TextStyle( 68 | fontWeight = FontWeight.SemiBold, 69 | fontFamily = fontFamily, 70 | fontSize = 16.sp, 71 | lineHeight = 24.sp, 72 | letterSpacing = 0.15.sp 73 | ), 74 | titleSmall = TextStyle( 75 | fontWeight = FontWeight.Bold, 76 | fontFamily = fontFamily, 77 | fontSize = 14.sp, 78 | lineHeight = 20.sp, 79 | letterSpacing = 0.1.sp 80 | ), 81 | bodyLarge = TextStyle( 82 | fontWeight = FontWeight.Normal, 83 | fontFamily = fontFamily, 84 | fontSize = 16.sp, 85 | lineHeight = 24.sp, 86 | letterSpacing = 0.15.sp 87 | ), 88 | bodyMedium = TextStyle( 89 | fontWeight = FontWeight.Normal, 90 | fontFamily = fontFamily, 91 | fontSize = 14.sp, 92 | lineHeight = 20.sp, 93 | letterSpacing = 0.5.sp 94 | ), 95 | bodySmall = TextStyle( 96 | fontWeight = FontWeight.Normal, 97 | fontFamily = fontFamily, 98 | fontSize = 12.sp, 99 | lineHeight = 16.sp, 100 | letterSpacing = 0.15.sp 101 | ), 102 | labelLarge = TextStyle( 103 | fontWeight = FontWeight.Normal, 104 | fontFamily = fontFamily, 105 | fontSize = 18.sp, 106 | lineHeight = 24.sp, 107 | letterSpacing = 0.1.sp 108 | ), 109 | labelMedium = TextStyle( 110 | fontWeight = FontWeight.SemiBold, 111 | fontFamily = fontFamily, 112 | fontSize = 12.sp, 113 | lineHeight = 16.sp, 114 | letterSpacing = 0.5.sp 115 | ), 116 | labelSmall = TextStyle( 117 | fontWeight = FontWeight.SemiBold, 118 | fontFamily = fontFamily, 119 | fontSize = 11.sp, 120 | lineHeight = 16.sp, 121 | letterSpacing = 0.5.sp 122 | ) 123 | ) 124 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zhenxiang/superimage/ui/form/TextField.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") 2 | 3 | package com.zhenxiang.superimage.ui.form 4 | 5 | import androidx.compose.foundation.interaction.MutableInteractionSource 6 | import androidx.compose.foundation.layout.defaultMinSize 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.text.BasicTextField 9 | import androidx.compose.foundation.text.KeyboardActions 10 | import androidx.compose.foundation.text.KeyboardOptions 11 | import androidx.compose.foundation.text.selection.LocalTextSelectionColors 12 | import androidx.compose.material3.* 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.CompositionLocalProvider 15 | import androidx.compose.runtime.remember 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.graphics.Shape 18 | import androidx.compose.ui.graphics.SolidColor 19 | import androidx.compose.ui.graphics.takeOrElse 20 | import androidx.compose.ui.semantics.semantics 21 | import androidx.compose.ui.text.TextStyle 22 | import androidx.compose.ui.text.input.VisualTransformation 23 | import com.zhenxiang.superimage.ui.theme.Border.thickness 24 | import com.zhenxiang.superimage.ui.theme.border 25 | 26 | @OptIn(ExperimentalMaterial3Api::class) 27 | @Composable 28 | fun MonoTextField(value: String, 29 | onValueChange: (String) -> Unit, 30 | modifier: Modifier = Modifier, 31 | enabled: Boolean = true, 32 | readOnly: Boolean = false, 33 | textStyle: TextStyle = LocalTextStyle.current, 34 | label: @Composable (() -> Unit)? = null, 35 | placeholder: @Composable (() -> Unit)? = null, 36 | leadingIcon: @Composable (() -> Unit)? = null, 37 | trailingIcon: @Composable (() -> Unit)? = null, 38 | supportingText: @Composable (() -> Unit)? = null, 39 | isError: Boolean = false, 40 | visualTransformation: VisualTransformation = VisualTransformation.None, 41 | keyboardOptions: KeyboardOptions = KeyboardOptions.Default, 42 | keyboardActions: KeyboardActions = KeyboardActions.Default, 43 | singleLine: Boolean = false, 44 | maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, 45 | minLines: Int = 1, 46 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 47 | shape: Shape = MaterialTheme.shapes.small, 48 | colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() 49 | ) { 50 | // If color is not provided via the text style, use content color as a default 51 | val textColor = textStyle.color.takeOrElse { 52 | colors.textColor(enabled).value 53 | } 54 | val mergedTextStyle = textStyle.merge(TextStyle(color = textColor)) 55 | 56 | CompositionLocalProvider(LocalTextSelectionColors provides colors.selectionColors) { 57 | @OptIn(ExperimentalMaterial3Api::class) 58 | BasicTextField( 59 | value = value, 60 | modifier = if (label != null) { 61 | modifier 62 | // Merge semantics at the beginning of the modifier chain to ensure padding is 63 | // considered part of the text field. 64 | .semantics(mergeDescendants = true) {} 65 | .padding(top = OutlinedTextFieldTopPadding) 66 | } else { 67 | modifier 68 | } 69 | .defaultMinSize( 70 | minWidth = TextFieldDefaults.MinWidth, 71 | minHeight = TextFieldDefaults.MinHeight 72 | ), 73 | onValueChange = onValueChange, 74 | enabled = enabled, 75 | readOnly = readOnly, 76 | textStyle = mergedTextStyle, 77 | cursorBrush = SolidColor(colors.cursorColor(isError).value), 78 | visualTransformation = visualTransformation, 79 | keyboardOptions = keyboardOptions, 80 | keyboardActions = keyboardActions, 81 | interactionSource = interactionSource, 82 | singleLine = singleLine, 83 | maxLines = maxLines, 84 | minLines = minLines, 85 | decorationBox = @Composable { innerTextField -> 86 | TextFieldDefaults.OutlinedTextFieldDecorationBox( 87 | value = value, 88 | visualTransformation = visualTransformation, 89 | innerTextField = innerTextField, 90 | placeholder = placeholder, 91 | label = label, 92 | leadingIcon = leadingIcon, 93 | trailingIcon = trailingIcon, 94 | supportingText = supportingText, 95 | singleLine = singleLine, 96 | enabled = enabled, 97 | isError = isError, 98 | interactionSource = interactionSource, 99 | colors = colors, 100 | container = { 101 | TextFieldDefaults.OutlinedBorderContainerBox( 102 | enabled, 103 | isError, 104 | interactionSource, 105 | colors, 106 | shape, 107 | MaterialTheme.border.thickness.regular, 108 | MaterialTheme.border.thickness.regular 109 | ) 110 | } 111 | ) 112 | } 113 | ) 114 | } 115 | } 116 | --------------------------------------------------------------------------------