();
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 |
8 |
9 |
10 | Or get the latest APK from the [Releases Section](https://github.com/Lucchetto/SuperImage/releases/latest).
11 |
12 | ## 🖼 Samples
13 |
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 | Aç
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 |
--------------------------------------------------------------------------------