diff --git a/README.md b/README.md
index 7202b00..da813f0 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@ Share image to WallpaperTunnel to set it as wallpaper.
- Provide a simple method for setting images as wallpapers.
- Allow adjustment of image brightness and blur intensity.
+- Fill images with solid color or blurred images.
+- Image Super-Resolution. (Beta)
- Maintain a history of wallpapers set through this app.
- Retrieve and modify the current wallpaper (static wallpapers only).
diff --git a/app/build.gradle b/app/build.gradle
index daca588..11a1ab2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,7 +12,7 @@ android {
minSdkVersion 29
targetSdkVersion 34
versionCode 4
- versionName "2.5-alpha-240411"
+ versionName "3.0-alpha-240419"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
@@ -59,11 +59,11 @@ android {
universalApk false
}
}
- task wrapper(type: Wrapper) {
+ tasks.register('wrapper', Wrapper) {
gradleVersion = '8.0'
}
namespace 'com.maary.shareas'
- applicationVariants.all { variant ->
+ applicationVariants.configureEach { variant ->
variant.outputs.each { output ->
def newName = output.outputFileName
print(newName)
@@ -92,14 +92,25 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'androidx.core:core-ktx:1.12.0'
- implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.core:core-ktx:1.13.0'
implementation 'androidx.activity:activity:1.8.2'
implementation "androidx.datastore:datastore-preferences:1.0.0"
// optional - RxJava2 support
implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0"
// optional - RxJava3 support
implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0"
+ implementation "androidx.fragment:fragment-ktx:1.6.2"
+
+ implementation 'com.microsoft.onnxruntime:onnxruntime-android:1.17.3'
+ implementation 'com.microsoft.onnxruntime:onnxruntime-extensions-android:0.9.0'
+
+ implementation("androidx.transition:transition:1.5.0-rc02")
+ implementation("androidx.transition:transition-ktx:1.5.0-rc02")
+
+ implementation "androidx.fragment:fragment:1.7.0-rc02"
+ // Kotlin
+ implementation "androidx.fragment:fragment-ktx:1.7.0-rc02"
+
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
@@ -114,9 +125,5 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.16.0'
- //noinspection GradleCompatible
- implementation 'com.android.support:palette-v7:28.0.0'
-
-
- //TODO: implement monet for photo imported
+ implementation 'androidx.palette:palette:1.0.0'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 009822f..12e0bd9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,12 +10,9 @@
-
-
+
-
-
+ android:exported="true"/>
+
+
@@ -48,11 +46,13 @@
android:exported="true">
+
-
+
>()
@@ -55,17 +55,6 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl
}
}
- fun getUriList(): ArrayList {
- val uriList = ArrayList()
- viewModelScope.launch {
- val images = queryImages()
-
- for (image in images) uriList.add(image.contentUri)
-
- }
- return uriList
- }
-
@RequiresApi(Build.VERSION_CODES.R)
fun deleteImage(image: MediaStoreImage) {
viewModelScope.launch {
@@ -215,7 +204,7 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl
Log.i(TAG, "Found ${cursor.count} images")
while (cursor.moveToNext()) {
- // Here we'll use the column indexs that we found above.
+ // Here we'll use the column index that we found above.
val id = cursor.getLong(idColumn)
val dateModified =
Date(TimeUnit.SECONDS.toMillis(cursor.getLong(dateModifiedColumn)))
@@ -292,14 +281,8 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl
* so it can be either updated or deleted.
*/
- val pendingIntent = MediaStore.createDeleteRequest(getApplication().contentResolver, arrayListOf(image.contentUri))
-// getApplication().startIntentSenderForResult(pendingIntent.intentSender, null, 0, 0, 0)
+ MediaStore.createDeleteRequest(getApplication().contentResolver, arrayListOf(image.contentUri))
-// getApplication().contentResolver.delete(
-// image.contentUri,
-// "${MediaStore.Images.Media._ID} = ?",
-// arrayOf(image.id.toString())
-// )
} catch (securityException: SecurityException) {
val recoverableSecurityException =
securityException as? RecoverableSecurityException
@@ -315,20 +298,6 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl
}
}
- /**
- * Convenience method to convert a day/month/year date into a UNIX timestamp.
- *
- * We're suppressing the lint warning because we're not actually using the date formatter
- * to format the date to display, just to specify a format to use to parse it, and so the
- * locale warning doesn't apply.
- */
- @Suppress("SameParameterValue")
- @SuppressLint("SimpleDateFormat")
- private fun dateToTimestamp(day: Int, month: Int, year: Int): Long =
- SimpleDateFormat("dd.MM.yyyy").let { formatter ->
- TimeUnit.MICROSECONDS.toSeconds(formatter.parse("$day.$month.$year")?.time ?: 0)
- }
-
/**
* Since we register a [ContentObserver], we want to unregister this when the `ViewModel`
* is being released.
diff --git a/app/src/main/java/com/maary/shareas/WallpaperViewModel.kt b/app/src/main/java/com/maary/shareas/WallpaperViewModel.kt
new file mode 100644
index 0000000..9f9c0ac
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/WallpaperViewModel.kt
@@ -0,0 +1,699 @@
+package com.maary.shareas
+
+import ai.onnxruntime.OrtEnvironment
+import ai.onnxruntime.OrtSession
+import ai.onnxruntime.extensions.OrtxPackage
+import ai.onnxruntime.providers.NNAPIFlags
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.RectF
+import android.net.Uri
+import android.util.Log
+import androidx.core.content.FileProvider
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
+import androidx.palette.graphics.Palette
+import com.hoko.blur.HokoBlur
+import com.hoko.blur.task.AsyncBlurTask
+import com.maary.shareas.data.ViewerBitmap
+import com.maary.shareas.helper.SuperResPerformer
+import com.maary.shareas.helper.Util
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.util.EnumSet
+import kotlin.math.pow
+import kotlin.random.Random
+
+
+class WallpaperViewModel : ViewModel() {
+ init {
+ Log.v("WVM", "INIT")
+ }
+
+ companion object {
+ const val HOME = 1
+ const val LOCK = 0
+
+ const val TOP = 0
+ const val BOTTOM = 1
+ const val LEFT = 2
+ const val RIGHT = 3
+ const val CENTER = 4
+ }
+
+ private var bakBitmap = ViewerBitmap()
+
+ private var bitmapRaw: Bitmap? = null
+ private var bitmapFit: Bitmap? = null
+ private var background: Bitmap? = null
+
+ private var bitmap: Bitmap? = null
+ set(value) {
+ Log.v("WVM", "BITMAP SET")
+ field = value
+ bakBitmap.bitmapHome = value
+ bakBitmap.bitmapLock = value
+ _viewerState.value = bakBitmap
+ }
+
+ private var inEditor = false
+ set(value) {
+ field = value
+ _inEditor.value = value
+ }
+
+ var currentBitmap = HOME
+ set(value) {
+ field = value
+ _currentBitmapState.value = value
+ }
+
+ var upscaleToggle = false
+ set(value) {
+ field = value
+ _upscaleToggleState.value = value
+ }
+
+
+ var primary: Int? = null
+ var secondary: Int? = null
+ var tertiary: Int? = null
+
+ private val _currentBitmapState = MutableStateFlow(currentBitmap)
+ val currentBitmapState: StateFlow = _currentBitmapState.asStateFlow()
+ val currentBitmapStateLiveData: LiveData = _currentBitmapState.asLiveData()
+
+ private val _viewerState = MutableStateFlow(ViewerBitmap())
+ val viewerStateLiveData = _viewerState.asLiveData()
+
+ private val _inEditor: MutableStateFlow = MutableStateFlow(inEditor)
+ val inEditorLiveData: LiveData = _inEditor.asLiveData()
+
+ private val _upscaleProgressState = MutableStateFlow(0)
+ val upscaleProgressState = _upscaleProgressState.asStateFlow()
+
+ private val _upscaleToggleState = MutableStateFlow(upscaleToggle)
+ val upscaleToggleState = _upscaleToggleState.asStateFlow()
+
+ private val _primaryColorState = MutableStateFlow(Color.TRANSPARENT)
+ val primaryColorState = _primaryColorState.asStateFlow()
+
+ private var ortEnv: OrtEnvironment = OrtEnvironment.getEnvironment()
+ private lateinit var ortSession: OrtSession
+
+
+ fun setBitmapRaw(value: Bitmap, context: Context) {
+ bitmapRaw = value
+ bitmapFit = fitBitmapToScreenAlt(value, context)
+ bitmap = fitBitmapToScreen(value, context)
+ val deviceBounds = Util.getDeviceBounds(context)
+ background = Bitmap.createBitmap(deviceBounds.x, deviceBounds.y, Bitmap.Config.ARGB_8888)
+ val colors = extractColorsFromPalette()
+ primary = adjustColor(colors[0])
+ secondary = adjustColor(colors[1])
+ tertiary = adjustColor(colors[2])
+ _primaryColorState.value += 1
+ }
+ private fun fitBitmapToScreen(value: Bitmap, context: Context): Bitmap {
+ val deviceBounds = Util.getDeviceBounds(context)
+ val deviceHeight = deviceBounds.y
+ val deviceWidth = deviceBounds.x
+
+ //image ratio > device ratio?
+ val isVertical = Util.isVertical(deviceHeight, deviceWidth, value)
+
+ //show image to imageview
+ val bitmapFullWidth = value.width
+ val bitmapFullHeight = value.height
+ val desiredWidth: Int
+ val desiredHeight: Int
+
+ if (isVertical) {
+ desiredWidth = deviceWidth
+ val scale = deviceWidth.toFloat() / bitmapFullWidth
+ desiredHeight = (scale * bitmapFullHeight).toInt()
+ } else {
+ desiredHeight = deviceHeight
+ val scale = deviceHeight.toFloat() / bitmapFullHeight
+ desiredWidth = (scale * bitmapFullWidth).toInt()
+ }
+
+ return Bitmap.createScaledBitmap(value, desiredWidth, desiredHeight, true)
+ }
+
+ private fun fitBitmapToScreenAlt(value: Bitmap, context: Context): Bitmap {
+ val deviceBounds = Util.getDeviceBounds(context)
+ val deviceHeight = deviceBounds.y
+ val deviceWidth = deviceBounds.x
+
+ //image ratio > device ratio?
+ val isVertical = Util.isVertical(deviceHeight, deviceWidth, value)
+
+ //show image to imageview
+ val bitmapFullWidth = value.width
+ val bitmapFullHeight = value.height
+ val desiredWidth: Int
+ val desiredHeight: Int
+
+ if (isVertical) {
+ desiredHeight = deviceHeight
+ val scale = deviceHeight.toFloat() / bitmapFullHeight
+ desiredWidth = (scale * bitmapFullWidth).toInt()
+ } else {
+ desiredWidth = deviceWidth
+ val scale = deviceWidth.toFloat() / bitmapFullWidth
+ desiredHeight = (scale * bitmapFullHeight).toInt()
+ }
+
+ return Bitmap.createScaledBitmap(value, desiredWidth, desiredHeight, true)
+ }
+
+ fun getBitmapHome(): Bitmap? {
+ return _viewerState.value.bitmapHome
+ }
+
+ fun getBitmapLock(): Bitmap? {
+ return _viewerState.value.bitmapLock
+ }
+
+ // 丢弃所有可能的修改
+ fun restoreChanges() {
+ bakBitmap.bitmapLock = bitmap
+ bakBitmap.bitmapHome = bitmap
+ _viewerState.update { currentState ->
+ currentState.copy(
+ bitmapHome = bitmap,
+ bitmapLock = bitmap
+ )
+ }
+ }
+
+ private fun adjustColor(color: Int, threshold: Float = 0.5f): Int {
+ val red = Color.red(color)
+ val green = Color.green(color)
+ val blue = Color.blue(color)
+ val brightness = (0.299 * red + 0.587 * green + 0.114 * blue) / 255
+
+ return if (brightness > threshold) {
+ // 如果亮度超过阈值,则将颜色调暗
+ val factor = 0.8f // 调暗因子,可以根据需要调整
+ darkenColor(red, green, blue, factor)
+ } else if (brightness < (1 - threshold)) {
+ // 如果亮度低于 (1 - threshold),则将颜色调亮
+ val factor = 1.2f // 调亮因子,可以根据需要调整
+ lightenColor(red, green, blue, factor)
+ } else {
+ // 如果颜色过于偏白,随机添加一些颜色
+ val offset = Random.nextInt(20, 50) // 随机偏移量
+ val newRed = (red + offset).coerceIn(0, 255)
+ val newGreen = (green + offset).coerceIn(0, 255)
+ val newBlue = (blue + offset).coerceIn(0, 255)
+ Color.rgb(newRed, newGreen, newBlue)
+ }
+ }
+
+ private fun darkenColor(red: Int, green: Int, blue: Int, factor: Float): Int {
+ val offset = Random.nextInt(-20, 20) // 随机偏移量
+ val newRed = (red * factor + offset).coerceIn(0f, 255f)
+ val newGreen = (green * factor + offset).coerceIn(0f, 255f)
+ val newBlue = (blue * factor + offset).coerceIn(0f, 255f)
+ return Color.rgb(newRed.toInt(), newGreen.toInt(), newBlue.toInt())
+ }
+
+ private fun lightenColor(red: Int, green: Int, blue: Int, factor: Float): Int {
+ val offset = Random.nextInt(-20, 20) // 随机偏移量
+ val newRed = (red * factor + offset).coerceIn(0f, 255f)
+ val newGreen = (green * factor + offset).coerceIn(0f, 255f)
+ val newBlue = (blue * factor + offset).coerceIn(0f, 255f)
+ return Color.rgb(newRed.toInt(), newGreen.toInt(), newBlue.toInt())
+ }
+
+
+
+ // 丢弃当前的修改
+ fun abortEdit() {
+ _viewerState.update { current ->
+ current.copy(
+ bitmapHome = bakBitmap.bitmapHome,
+ bitmapLock = bakBitmap.bitmapLock
+ )
+ }
+ }
+
+ fun abortEditHome() {
+ _viewerState.update { current ->
+ current.copy(
+ bitmapHome = bakBitmap.bitmapHome
+ )
+ }
+ }
+
+ fun abortEditLock() {
+ _viewerState.update { current ->
+ current.copy(
+ bitmapLock = bakBitmap.bitmapLock
+ )
+ }
+ }
+
+ fun startEditing() {
+ inEditor = true
+ }
+
+ fun finishEditing() {
+ inEditor = false
+ }
+
+ fun getDisplayBitmap(): Bitmap? {
+ if (currentBitmap == HOME) {
+ return _viewerState.value.bitmapHome
+ } else if (currentBitmap == LOCK) {
+ return _viewerState.value.bitmapLock
+ }
+ return bitmap
+ }
+
+ fun currentBitmapToggle() {
+ if (currentBitmap == HOME) {
+ currentBitmap = LOCK
+ } else if (currentBitmap == LOCK) {
+ currentBitmap = HOME
+ }
+ }
+
+ fun getFabResource(): Int {
+ if (currentBitmap == HOME) {
+ return R.drawable.ic_vertical
+ }
+ if (currentBitmap == LOCK) {
+ return R.drawable.ic_lockscreen
+ }
+ return R.drawable.ic_vertical
+ }
+
+ fun editBlur(context: Context, value: Float) {
+ HokoBlur.with(context)
+ .radius(value.toInt())
+ .forceCopy(true)
+ .asyncBlur(bakBitmap.bitmapHome, object : AsyncBlurTask.Callback {
+ override fun onBlurSuccess(bitmap: Bitmap) {
+ _viewerState.update { current ->
+ current.copy(bitmapHome = bitmap)
+ }
+ HokoBlur.with(context)
+ .radius(value.toInt())
+ .forceCopy(true)
+ .asyncBlur(bakBitmap.bitmapLock, object : AsyncBlurTask.Callback {
+ override fun onBlurSuccess(bitmap: Bitmap) {
+ _viewerState.update { current ->
+ current.copy(bitmapLock = bitmap)
+ }
+ }
+
+ override fun onBlurFailed(error: Throwable) {}
+ })
+ }
+
+ override fun onBlurFailed(error: Throwable) {}
+ })
+ }
+
+ fun editBrightness(value: Float) {
+ CoroutineScope(Dispatchers.Main).launch {
+ withContext(Dispatchers.IO) {
+ val homeA = Util.adjustBrightness(bakBitmap.bitmapHome, value)
+ val lockA = Util.adjustBrightness(bakBitmap.bitmapLock, value)
+ _viewerState.update { current ->
+ current.copy(
+ bitmapHome = homeA,
+ bitmapLock = lockA
+ )
+ }
+ }
+ }
+ }
+
+ fun saveEdit() {
+ bakBitmap = _viewerState.value
+ }
+
+ fun getBitmapUri(context: Context, cacheDir: File): Uri? {
+ //---Save bitmap to external cache directory---//
+ //get cache directory
+
+ val cachePath = File(cacheDir, "my_images/")
+ cachePath.mkdirs()
+
+ //create png file
+ val file = File(cachePath, "Image_123.png")
+ val fileOutputStream: FileOutputStream
+ try {
+ fileOutputStream = FileOutputStream(file)
+ _viewerState.value.bitmapHome?.compress(
+ Bitmap.CompressFormat.PNG,
+ 100,
+ fileOutputStream
+ )
+ fileOutputStream.flush()
+ fileOutputStream.close()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+
+ return FileProvider.getUriForFile(
+ context,
+ context.packageName + ".provider",
+ file
+ )
+ }
+
+ private fun readModel(context: Context, modelID: Int): ByteArray {
+ Log.e("WVM", "READ MODEL")
+ return context.resources.openRawResource(modelID).readBytes()
+ }
+
+ private fun performSuperResolution(ortSession: OrtSession, bitmap: Bitmap): Bitmap? {
+ val superResPerformer = SuperResPerformer()
+ Log.v("WVM", "STARTED")
+
+ val result = superResPerformer.upscale(bitmapToInputStream(bitmap), ortEnv, ortSession)
+ return result.outputBitmap
+ }
+
+ private fun bitmapToInputStream(bitmap: Bitmap?): InputStream {
+ val outputStream = ByteArrayOutputStream()
+ bitmap?.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
+ return ByteArrayInputStream(outputStream.toByteArray())
+ }
+
+ private suspend fun process(tileBitmap: Bitmap): Bitmap? {
+ // 实现对图片的超分辨率处理
+ return viewModelScope.async(Dispatchers.IO) {
+ performSuperResolution(ortSession, tileBitmap)
+ }.await()
+ }
+
+ private fun scaleBitmapTo(originalBitmap: Bitmap, scale: Float): Bitmap {
+ val originalWidth = originalBitmap.width
+ val originalHeight = originalBitmap.height
+ val scaledWidth = (originalWidth * scale).toInt()
+ val scaledHeight = (originalHeight * scale).toInt()
+ return Bitmap.createScaledBitmap(originalBitmap, scaledWidth, scaledHeight, true)
+ }
+
+ fun extractTopColorsFromBitmap(): List {
+
+ val _bitmap = Bitmap.createScaledBitmap(bitmapRaw!!, 128, 128, true)
+ val colorMap = mutableMapOf()
+
+ // 遍历图片的每个像素,并统计每种颜色的出现次数
+ for (x in 0 until _bitmap.width) {
+ for (y in 0 until _bitmap.height) {
+ val pixel = _bitmap.getPixel(x, y)
+ val colorCount = colorMap.getOrDefault(pixel, 0)
+ colorMap[pixel] = colorCount + 1
+ }
+ }
+
+ // 按颜色出现次数排序,并取前五个颜色
+ val sortedColors = colorMap.toList().sortedByDescending { it.second }.take(5)
+ return sortedColors.map { it.first }
+ }
+
+ fun extractColorsFromPalette(): List {
+ val palette = Palette.from(bitmap!!).generate()
+
+ // 获取所有的颜色 swatch,并根据 population 属性进行排序
+ val sortedSwatches = palette.swatches.sortedByDescending { it.population }
+
+ // 提取前五个颜色
+ return sortedSwatches.take(3).map { it.rgb }
+ }
+
+ fun paintColor(position: Int, color: Int, scale: Float = 1f) {
+ var left = 0
+ var top = 0
+ val bHeight = background!!.height
+ val bWidth = background!!.width
+ val _background = Bitmap.createBitmap(bWidth, bHeight, Bitmap.Config.ARGB_8888)
+ val _bitmapFit = scaleBitmapTo(bitmapFit!!, scale)
+
+ val fHeight = _bitmapFit.height
+ val fWidth = _bitmapFit.width
+ when (position) {
+ TOP -> top = 0
+ BOTTOM -> top = bHeight - fHeight
+ LEFT -> left = 0
+ RIGHT -> left = bWidth - fWidth
+ CENTER -> {
+ left = (bWidth - fWidth) / 2
+ top = (bHeight - fHeight) / 2
+ }
+ }
+
+ val canvas = Canvas(_background)
+ canvas.drawColor(color)
+ canvas.drawBitmap(_bitmapFit, left.toFloat(), top.toFloat(), null)
+
+ _viewerState.update { current ->
+ current.copy(
+ bitmapHome = _background,
+ bitmapLock = _background
+ )
+ }
+ }
+
+ private fun addShadow(image: Bitmap, zoom: Float, offset: Int = 32): Bitmap {
+ val width = image.width
+ val height = image.height
+
+ // 创建新的背景图片
+ val _background = Bitmap.createBitmap(width + offset * 2, height + offset * 2, Bitmap.Config.ARGB_8888)
+
+ if (_background.width > width/zoom || _background.height > height/zoom) return image
+
+ val canvas = Canvas(_background)
+
+ // 创建绘制阴影的 Paint 对象
+ val shadowPaint = Paint().apply {
+ color = Color.BLACK
+ alpha = 255
+ xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)
+ }
+
+ // 绘制阴影
+ for (i in 0 until offset) {
+ val alpha = (255 * (1.3.pow(i) / 1.3.pow(offset))).toInt() // 根据当前距离计算透明度
+ shadowPaint.alpha = alpha
+ val rect = RectF(i.toFloat(), i.toFloat(), (width + offset * 2 - i).toFloat(), (height + offset * 2 - i).toFloat())
+ canvas.drawRect(rect, shadowPaint)
+ }
+
+ // 将原始图片绘制在背景上
+ canvas.drawBitmap(image, offset.toFloat(), offset.toFloat(), null)
+
+ return _background
+ }
+
+ fun paintBlur(
+ position: Int,
+ blur: Int,
+ context: Context,
+ scale: Float = 1f
+ ) {
+ var left = 0
+ var top = 0
+
+ val bHeight = background!!.height
+ val bWidth = background!!.width
+
+ val _bitmapFit = scaleBitmapTo(bitmapFit!!, scale)
+ val target = addShadow(_bitmapFit, scale)
+
+ val canvasBack = Canvas(background!!)
+ val srcLeft = (bitmap!!.width - bWidth)/2
+ val srcTop = (bitmap!!.height - bHeight)/2
+ val srcRect = Rect(
+ srcLeft,
+ srcTop,
+ srcLeft + bWidth,
+ srcTop + bHeight)
+
+ // 定义目标区域
+ val destRect = Rect(0, 0, bWidth, bHeight)
+
+ // 在 Canvas 上绘制裁剪后的图像
+ canvasBack.drawBitmap(bitmap!!, srcRect, destRect, null)
+
+ val fHeight = target.height
+ val fWidth = target.width
+ when (position) {
+ TOP -> top = 0
+ BOTTOM -> top = bHeight - fHeight
+ LEFT -> left = 0
+ RIGHT -> left = bWidth - fWidth
+ CENTER -> {
+ left = (bWidth - fWidth) / 2
+ top = (bHeight - fHeight) / 2
+ }
+ }
+
+ HokoBlur.with(context)
+ .radius(blur)
+ .forceCopy(true)
+ .asyncBlur(background, object : AsyncBlurTask.Callback {
+ override fun onBlurSuccess(bitmap: Bitmap) {
+ val canvas = Canvas(bitmap)
+ canvas.drawBitmap(target, left.toFloat(), top.toFloat(), null)
+ _viewerState.update { current ->
+ current.copy(
+ bitmapHome = bitmap,
+ bitmapLock = bitmap
+ )
+ }
+ }
+
+ override fun onBlurFailed(error: Throwable) {}
+ })
+
+ }
+
+ fun upscale(context: Context, modelName: String) {
+ Log.v("WVM", modelName)
+
+ var model = R.raw.realesrgan_anime
+ var scale = 4
+ val modelArray = context.resources.getStringArray(R.array.model_names)
+ when (modelName) {
+ modelArray[0] -> {
+ model = R.raw.realesrgan_x2plus
+ scale = 2
+ }
+
+ modelArray[1] -> {
+ model = R.raw.realesrgan_x4plus
+ scale = 4
+ scaleBitmapTo(bitmapRaw!!, 0.5f)
+ }
+
+ modelArray[2] -> {
+ model = R.raw.realesrgan_anime
+ scaleBitmapTo(bitmapRaw!!, 0.5f)
+ }
+ }
+
+ val sessionOptions: OrtSession.SessionOptions = OrtSession.SessionOptions()
+ sessionOptions.registerCustomOpLibrary(OrtxPackage.getLibraryPath())
+ sessionOptions.addNnapi(
+ EnumSet.of(
+ NNAPIFlags.USE_FP16,
+ NNAPIFlags.CPU_DISABLED
+ )
+ )
+ ortSession = ortEnv.createSession(readModel(context, model), sessionOptions)
+
+ // 1. 输入是一个 bimap
+ val originalWidth = bitmapRaw!!.width
+ val originalHeight = bitmapRaw!!.height
+
+ // 2. 将 bitmap 分割为多个 tile,记录 tile 的相对位置
+ val tileSize = 256
+ val numTilesX = (originalWidth + tileSize - 1) / tileSize
+ val numTilesY = (originalHeight + tileSize - 1) / tileSize
+ val sub = numTilesX * numTilesY
+
+ // 存储处理后的 tile
+ val processedTiles = mutableListOf()
+
+ val processedTilesDeferred = mutableListOf>()
+
+
+ viewModelScope.launch {
+ var numFinished = 0
+ var progress = 0
+ for (y in 0 until numTilesY) {
+ for (x in 0 until numTilesX) {
+
+ if (!_upscaleToggleState.value) break
+
+ val tileX = x * tileSize
+ val tileY = y * tileSize
+ val tileWidth = minOf(tileSize, originalWidth - tileX)
+ val tileHeight = minOf(tileSize, originalHeight - tileY)
+
+ val tileBitmap =
+ Bitmap.createBitmap(bitmapRaw!!, tileX, tileY, tileWidth, tileHeight)
+
+ val deferred = async { process(tileBitmap) }
+ processedTilesDeferred.add(deferred)
+ val processedTile = deferred.await()
+
+ numFinished++
+ Log.e("WVM", numFinished.toString())
+ if (sub != 0) {
+ progress = (String.format("%.2f", (numFinished.toDouble() / sub.toDouble()))
+ .toDouble() * 100).toInt()
+ }
+ _upscaleProgressState.value = progress
+
+ if (processedTile != null) {
+ processedTiles.add(processedTile)
+ }
+
+ System.gc()
+ }
+ }
+
+ val resultWidth = originalWidth * scale
+ val resultHeight = originalHeight * scale
+ var resultBitmap = Bitmap.createBitmap(resultWidth, resultHeight, bitmapRaw!!.config)
+ val canvas = Canvas(resultBitmap)
+ var currentX = 0
+ var currentY = 0
+
+ for (processedTileDeferred in processedTilesDeferred) {
+ val processedTile = processedTileDeferred.await()
+ if (processedTile != null) {
+ canvas.drawBitmap(processedTile, currentX.toFloat(), currentY.toFloat(), null)
+ currentX += processedTile.width
+ if (currentX >= resultWidth) {
+ currentX = 0
+ currentY += processedTile.height
+ }
+ }
+ }
+
+ resultBitmap = fitBitmapToScreen(resultBitmap, context)
+
+ _viewerState.update { current ->
+ current.copy(
+ bitmapHome = resultBitmap,
+ bitmapLock = resultBitmap
+ )
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt b/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt
index b90c177..5d584e3 100644
--- a/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt
+++ b/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt
@@ -2,14 +2,16 @@ package com.maary.shareas.activity
import android.Manifest
import android.app.Activity
-import android.content.*
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.IntentSender
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
-import android.provider.Settings
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
@@ -24,7 +26,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
-import androidx.core.view.*
+import androidx.core.view.WindowCompat
+import androidx.core.view.updatePadding
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ListAdapter
@@ -33,11 +36,15 @@ import com.bumptech.glide.Glide
import com.google.android.material.color.DynamicColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.maary.shareas.HistoryActivityViewModel
-import com.maary.shareas.MediaStoreImage
import com.maary.shareas.R
-import com.maary.shareas.helper.Util
+import com.maary.shareas.data.MediaStoreImage
import com.maary.shareas.databinding.ActivityHistoryBinding
-import kotlinx.coroutines.*
+import com.maary.shareas.helper.Util
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/** The request code for requesting [Manifest.permission.READ_EXTERNAL_STORAGE] permission. */
@@ -144,19 +151,9 @@ class HistoryActivity : AppCompatActivity(){
}
if (!haveStoragePermission()) {
-// binding.welcomeView.visibility = View.VISIBLE
requestPermission()
} else {
showImages()
-// if (galleryAdapter.itemCount == 0){
-// Log.v("WALLP", "0")
-// binding.layoutNoHistory.visibility = View.VISIBLE
-// binding.buttonClearAll.visibility = View.GONE
-// }else {
-// Log.v("WALLP", "1")
-// binding.layoutNoHistory.visibility = View.INVISIBLE
-// binding.buttonClearAll.visibility = View.VISIBLE
-// }
}
}
@@ -171,30 +168,6 @@ class HistoryActivity : AppCompatActivity(){
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showImages()
- } else {
- // If we weren't granted the permission, check to see if we should show
- // rationale for the permission.
- val showRationale =
- ActivityCompat.shouldShowRequestPermissionRationale(
- this,
- Manifest.permission.READ_EXTERNAL_STORAGE
- )
-
- /**
- * If we should show the rationale for requesting storage permission, then
- * we'll show [ActivityMainBinding.permissionRationaleView] which does this.
- *
- * If `showRationale` is false, this means the user has not only denied
- * the permission, but they've clicked "Don't ask again". In this case
- * we send the user to the settings page for the app so they can grant
- * the permission (Yay!) or uninstall the app.
- */
- if (showRationale) {
-// showNoAccess()
- //TODO: do something
- } else {
- goToSettings()
- }
}
return
}
@@ -209,7 +182,6 @@ class HistoryActivity : AppCompatActivity(){
}
}
- @OptIn(DelicateCoroutinesApi::class)
private fun showImages() {
viewModel.loadImages()
@@ -222,23 +194,6 @@ class HistoryActivity : AppCompatActivity(){
}
}
- private fun openMediaStore() {
- if (haveStoragePermission()) {
- showImages()
- } else {
- requestPermission()
- }
- }
-
- private fun goToSettings() {
- Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:$packageName")).apply {
- addCategory(Intent.CATEGORY_DEFAULT)
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }.also { intent ->
- startActivity(intent)
- }
- }
-
/**
* Convenience method to check if [Manifest.permission.READ_EXTERNAL_STORAGE] permission
* has been granted to the app.
@@ -280,7 +235,7 @@ class HistoryActivity : AppCompatActivity(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val pendingIntent = MediaStore.createDeleteRequest(contentResolver, arrayListOf(image.contentUri))
- val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent.intentSender).build()
+ IntentSenderRequest.Builder(pendingIntent.intentSender).build()
startIntentSenderForResult(pendingIntent.intentSender, 42, null, 0, 0, 0, null)
} else {
application.contentResolver.delete(
diff --git a/app/src/main/java/com/maary/shareas/activity/MainActivity.java b/app/src/main/java/com/maary/shareas/activity/MainActivity.java
index dfdb467..87573c1 100644
--- a/app/src/main/java/com/maary/shareas/activity/MainActivity.java
+++ b/app/src/main/java/com/maary/shareas/activity/MainActivity.java
@@ -1,7 +1,5 @@
package com.maary.shareas.activity;
-import static com.google.android.material.slider.LabelFormatter.LABEL_GONE;
-
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
@@ -12,59 +10,42 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
-import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
+
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.view.menu.ActionMenuItemView;
-import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
-import androidx.core.content.FileProvider;
import androidx.core.view.WindowCompat;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.lifecycle.ViewModelProvider;
import androidx.palette.graphics.Palette;
-import com.google.android.material.bottomappbar.BottomAppBar;
-import com.google.android.material.chip.Chip;
import com.google.android.material.color.DynamicColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import com.google.android.material.slider.Slider;
import com.google.android.material.snackbar.Snackbar;
-import com.hoko.blur.HokoBlur;
-import com.hoko.blur.task.AsyncBlurTask;
-import com.maary.shareas.helper.PreferencesHelper;
import com.maary.shareas.R;
+import com.maary.shareas.WallpaperViewModel;
+import com.maary.shareas.databinding.ActivityMainBinding;
+import com.maary.shareas.fragment.EditorFragment;
+import com.maary.shareas.helper.PreferencesHelper;
import com.maary.shareas.helper.Util;
import com.maary.shareas.helper.Util_Files;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
@@ -73,476 +54,168 @@
public class MainActivity extends AppCompatActivity {
static final int MENU_RESET = 0;
- //请求权限
- Bitmap bitmap;
- Bitmap processed;
- Bitmap blurProcessed;
- Bitmap brightnessProcessed;
- Bitmap raw;
Rect cord;
- int blurBias = 0;
- int brightnessBias = 0;
MaterialAlertDialogBuilder builder;
- Boolean applyEditToLock = true;
- Boolean applyEditToHome = true;
- Boolean isProcessed = false;
-
- Boolean currentImageViewIsHome = true;
- int device_height, device_width;
-
Palette.Swatch vibrant;
- Palette.Swatch darkVibrant;
Palette.Swatch dominant;
- Palette.Swatch muted ;
-
- Intent intent;
-
- Snackbar snackbarReturnHome;
+ Palette.Swatch muted;
+ private ActivityMainBinding binding;
@SuppressLint("RestrictedApi")
@RequiresApi(api = Build.VERSION_CODES.S)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
+ binding = ActivityMainBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
- PreferencesHelper preferencesHelper = new PreferencesHelper(this);
-
- device_height = preferencesHelper.getHeight();
- if (device_height == -1){
- Point deviceBounds = Util.getDeviceBounds(MainActivity.this);
- device_height = deviceBounds.y;
- device_width = deviceBounds.x;
-
- preferencesHelper.setWidthAndHeight(device_width, device_height);
- } else {
- device_width = preferencesHelper.getWidth();
- }
-
- intent = getIntent();
+ Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
final WallpaperManager wallpaperManager = WallpaperManager.getInstance(getApplicationContext());
- try {
- if (Intent.ACTION_SEND.equals(action) && type != null) {
- if (type.startsWith("image/")) {
- bitmap = Util.getBitmap(intent, MainActivity.this);
- }
+ if (!Intent.ACTION_SEND.equals(action) || type == null) return;
+ if (!type.startsWith("image/")) return;
+ Bitmap bitmap = Util.getBitmap(intent, MainActivity.this);
+ if (bitmap == null) return;
+
+ WallpaperViewModel viewModel = new ViewModelProvider(this).get(WallpaperViewModel.class);
+
+ viewModel.setBitmapRaw(bitmap, this);
+ viewModel.getViewerStateLiveData().observe(this, state -> binding.mainView.setImageBitmap(Objects.requireNonNull(viewModel.getDisplayBitmap())));
+ viewModel.getCurrentBitmapStateLiveData().observe(this, state -> {
+ binding.mainView.setImageBitmap(Objects.requireNonNull(viewModel.getDisplayBitmap()));
+ binding.fab.setImageResource(viewModel.getFabResource());
+ });
+ viewModel.getInEditorLiveData().observe(this, inEditor -> {
+ if (inEditor) {
+ binding.bottomAppBarContainer.setVisibility(View.INVISIBLE);
+ } else {
+ binding.bottomAppBarContainer.setVisibility(View.VISIBLE);
}
+ });
- if (bitmap != null) {
- //Parent layout
- ConstraintLayout container = findViewById(R.id.container);
- //parent layout of bottomAppBar
- CoordinatorLayout bottomAppBarContainer = findViewById(R.id.bottomAppBarContainer);
- //progressBar usd in wallpaper setting process
-
- BottomAppBar bottomAppBar = findViewById(R.id.bottomAppBar);
-
- FloatingActionButton fab = findViewById(R.id.fab);
- //make image preview scrollable. parent of imageView.
- ScrollView verticalScrollView = new ScrollView(this);
- HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
- //preview image
- ImageView imageView = new ImageView(this);
- //image ratio > device ratio?
- Boolean isVertical = Util.isVertical(device_height, device_width, bitmap);
-
- ExecutorService executorService = Executors.newSingleThreadExecutor();
-
- //Show Image
- imageView.setScaleType(ImageView.ScaleType.FIT_XY);
- imageView.setAdjustViewBounds(true);
- imageView.setId(View.generateViewId());
-
- //show image to imageview
- int bitmap_full_width = bitmap.getWidth();
- int bitmap_full_height = bitmap.getHeight();
- int desired_width;
- int desired_height;
-
- LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT
- );
-
- if (isVertical) {
- desired_width = device_width;
- float scale = (float) device_width / bitmap_full_width;
- desired_height = (int) (scale * bitmap_full_height);
-
- verticalScrollView.setFillViewport(true);
- container.addView(verticalScrollView, layoutParams);
- verticalScrollView.addView(imageView, layoutParams);
- } else {
- desired_height = device_height;
- float scale = (float) device_height / bitmap_full_height;
- desired_width = (int) (scale * bitmap_full_width);
-
- horizontalScrollView.setFillViewport(true);
- container.addView(horizontalScrollView, layoutParams);
- horizontalScrollView.addView(imageView, layoutParams);
- }
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
- bitmap = Bitmap.createScaledBitmap(bitmap, desired_width, desired_height, true);
- processed = bitmap;
-
- raw = bitmap;
- imageView.setImageBitmap(bitmap);
-
- imageView.setOnClickListener(v -> {
- if (!isProcessed || applyEditToHome == applyEditToLock) {
- if (currentImageViewIsHome) {
- currentImageViewIsHome = false;
- fab.setImageResource(R.drawable.ic_lockscreen);
- } else {
- currentImageViewIsHome = true;
- fab.setImageResource(R.drawable.ic_vertical);
- }
- }
- if (applyEditToHome) {
- if (currentImageViewIsHome) {
- imageView.setImageBitmap(raw);
- currentImageViewIsHome = false;
- fab.setImageResource(R.drawable.ic_lockscreen);
- } else {
- imageView.setImageBitmap(bitmap);
- currentImageViewIsHome = true;
- fab.setImageResource(R.drawable.ic_vertical);
- }
- }
- if (applyEditToLock) {
- if (currentImageViewIsHome) {
- imageView.setImageBitmap(bitmap);
- currentImageViewIsHome = false;
- fab.setImageResource(R.drawable.ic_lockscreen);
- } else {
- imageView.setImageBitmap(raw);
- currentImageViewIsHome = true;
- fab.setImageResource(R.drawable.ic_vertical);
- }
- }
- });
+ binding.mainView.setOnImageClickListener(v -> viewModel.currentBitmapToggle());
- Palette.from(bitmap).generate(palette -> {
- // Access the colors from the palette
- assert palette != null;
- vibrant = palette.getVibrantSwatch();
- muted = palette.getMutedSwatch();
- dominant = palette.getDominantSwatch();
- darkVibrant = palette.getDarkVibrantSwatch();
+ Palette.from(bitmap).generate(palette -> {
+ // Access the colors from the palette
+ assert palette != null;
+ vibrant = palette.getVibrantSwatch();
+ muted = palette.getMutedSwatch();
+ dominant = palette.getDominantSwatch();
+ if (vibrant != null) {
+ binding.fab.setBackgroundTintList(ColorStateList.valueOf(vibrant.getRgb()));
+ } else {
+ binding.fab.setBackgroundColor(palette.getVibrantColor(getColor(R.color.colorAccent)));
+ }
- if (vibrant != null) {
- fab.setBackgroundTintList(ColorStateList.valueOf(vibrant.getRgb()));
+ });
+
+ //setup the fab click listener
+ binding.fab.setOnClickListener(view -> {
+ cord = binding.mainView.getVisibleRect();
+ binding.bottomAppBar.getMenu().getItem(MENU_RESET).setEnabled(true);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ });
+
+ binding.fab.setOnLongClickListener(view -> {
+ cord = null;
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ return false;
+ });
+
+ //TODO:ADD zoom (if possible
+
+ Snackbar snackbarReturnHome = Snackbar.make(binding.container, getString(R.string.wallpaper_setted), Snackbar.LENGTH_INDEFINITE)
+ .setAction(getString(R.string.gohome), v -> returnToHomeScreen());
+
+ WallpaperManager.OnColorsChangedListener wallpaperChangedListener = new WallpaperManager.OnColorsChangedListener() {
+ @Override
+ public void onColorsChanged(@Nullable WallpaperColors colors, int which) {
+ wallpaperManager.removeOnColorsChangedListener(this);
+ snackbarReturnHome.show();
+ }
+ };
+
+ wallpaperManager.addOnColorsChangedListener(wallpaperChangedListener, new Handler(Looper.getMainLooper()));
+
+ //set bottomAppBar menu item
+ //tap blur and brightness button will disable other menu item
+ binding.bottomAppBar.setOnMenuItemClickListener(item -> {
+ if (item.getItemId() == R.id.edit) {
+// viewModel.startEditing();
+ loadFragment(new EditorFragment());
+ } else if (item.getItemId() == R.id.reset) {
+ viewModel.restoreChanges();
+ }
+ return true;
+ });
+
+ ActionMenuItemView resetItem = binding.bottomAppBar.findViewById(R.id.reset);
+ resetItem.setOnLongClickListener(view -> {
+ Intent intent1 = new Intent(getApplicationContext(), HistoryActivity.class);
+ startActivity(intent1);
+ return true;
+ });
+
+ Context context = DynamicColors.wrapContextIfAvailable(
+ MainActivity.this
+ );
+
+ //setup AlertDialog builder
+ builder = new MaterialAlertDialogBuilder(context);
+ builder.setTitle(R.string.setAs);
+
+ String[] options = {
+ getResources().getString(R.string.home),
+ getResources().getString(R.string.lockscreen),
+ getResources().getString(R.string.homeAndLockscreen),
+ getResources().getString(R.string.use_others)};
+
+ builder.setItems(options, (dialog, which) -> executorService.execute(() -> {
+ try {
+ if (new PreferencesHelper(this).getSettingsHistory()) {
+ //若已经选择保存选项,弹出「设置图片为」选项之前保存图片
+ if (checkPermission()) {
+ Bitmap currentWallpaper = ((BitmapDrawable) Objects.requireNonNull(wallpaperManager.getDrawable())).getBitmap();
+ Util_Files.saveWallpaper(currentWallpaper, this);
} else {
- fab.setBackgroundColor(palette.getVibrantColor(getColor(R.color.colorAccent)));
+ Snackbar.make(binding.container, R.string.no_permission, Snackbar.LENGTH_SHORT)
+ .show();
}
+ }
-
- });
-
- //setup the fab click listener
- fab.setOnClickListener(view -> {
- if (isVertical) {
- int start = verticalScrollView.getScrollY();
- cord = new Rect(0, start, device_width, start + device_height);
- } else {
- int start = horizontalScrollView.getScrollX();
- cord = new Rect(start, 0, start + device_width, device_height);
- }
- bottomAppBar.getMenu().getItem(MENU_RESET).setEnabled(true);
- AlertDialog dialog = builder.create();
- dialog.show();
-
- });
-
- fab.setOnLongClickListener(view -> {
- cord = null;
- AlertDialog dialog = builder.create();
- dialog.show();
- return false;
- });
-
- //TODO:ADD zoom (if possible
-
- WallpaperManager.OnColorsChangedListener wallpaperChangedListener = new WallpaperManager.OnColorsChangedListener() {
- @Override
- public void onColorsChanged(@Nullable WallpaperColors colors, int which) {
- wallpaperManager.removeOnColorsChangedListener(this);
- snackbarReturnHome.show();
+ switch (which) {
+ case 0 -> wallpaperManager.setBitmap(viewModel.getBitmapHome(), cord, true, WallpaperManager.FLAG_SYSTEM);
+ case 1 -> wallpaperManager.setBitmap(viewModel.getBitmapLock(), cord, true, WallpaperManager.FLAG_LOCK);
+ case 2 -> {
+ wallpaperManager.setBitmap(viewModel.getBitmapHome(), cord, true, WallpaperManager.FLAG_SYSTEM);
+ wallpaperManager.setBitmap(viewModel.getBitmapLock(), cord, true, WallpaperManager.FLAG_LOCK);
}
- };
-
- snackbarReturnHome = Snackbar.make(container, getString(R.string.wallpaper_setted), Snackbar.LENGTH_INDEFINITE)
- .setAction(getString(R.string.gohome), v -> returnToHomeScreen());
-
- wallpaperManager.addOnColorsChangedListener(wallpaperChangedListener, null);
-
- //set bottomAppBar menu item
- //tap blur and brightness button will disable other menu item
-
- bottomAppBar.setOnMenuItemClickListener(item -> {
-
- if (item.getItemId() == R.id.edit) {
- bottomAppBarContainer.setVisibility(View.INVISIBLE);
- AlertDialog dialog;
-
- dialog = createSliderDialog();
- Objects.requireNonNull(dialog.getWindow())
- .clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- dialog.getWindow().setGravity(Gravity.BOTTOM);
- dialog.setCancelable(false);
-
- // 获取 Drawable 对象
- Drawable drawable = AppCompatResources.getDrawable(this, R.drawable.dialog_background);
-
- assert drawable != null;
- Drawable modifiedDrawable = Objects.requireNonNull(drawable.getConstantState()).newDrawable().mutate();
- // 复制 Drawable 对象,以便进行修改
- int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
- switch (currentNightMode) {
- case Configuration.UI_MODE_NIGHT_NO:
- modifiedDrawable.setTint(Color.WHITE);
- break;
- case Configuration.UI_MODE_NIGHT_YES:
- modifiedDrawable.setTint(Color.BLACK);
- break;
- }
-
- dialog.getWindow().setBackgroundDrawable(modifiedDrawable);
- dialog.show();
-
- Slider sliderBlur = dialog.findViewById(R.id.dialog_slider_blur);
- assert sliderBlur != null;
- sliderBlur.setLabelBehavior(LABEL_GONE);
- sliderBlur.setTickVisible(false);
-
- Slider sliderBrightness = dialog.findViewById(R.id.dialog_slider_brightness);
- assert sliderBrightness != null;
- sliderBrightness.setLabelBehavior(LABEL_GONE);
- sliderBrightness.setTickVisible(false);
-
- Chip chipLock = dialog.findViewById(R.id.chip_apply_lock);
- Chip chipHome = dialog.findViewById(R.id.chip_apply_home);
-
- assert chipLock != null;
- assert chipHome != null;
-
- if (vibrant != null) {
- sliderBlur.setThumbTintList(ColorStateList.valueOf(vibrant.getRgb()));
- sliderBlur.setTrackActiveTintList(ColorStateList.valueOf(vibrant.getRgb()));
- sliderBrightness.setThumbTintList(ColorStateList.valueOf(vibrant.getRgb()));
- sliderBrightness.setTrackActiveTintList(ColorStateList.valueOf(vibrant.getRgb()));
- }
-
- chipLock.setChecked(applyEditToLock);
- chipHome.setChecked(applyEditToHome);
-
- chipLock.setOnCheckedChangeListener((buttonView, isChecked) -> applyEditToLock = isChecked);
- chipHome.setOnCheckedChangeListener((buttonView, isChecked) -> applyEditToHome = isChecked);
-
- sliderBlur.setValueFrom(0);
- sliderBlur.setValueTo(30);
- sliderBlur.setStepSize(1);
- sliderBlur.setValue(blurBias);
-
- sliderBrightness.setValueFrom(-50);
- sliderBrightness.setValueTo(50);
- sliderBrightness.setStepSize(1);
- sliderBrightness.setValue(brightnessBias);
-
- dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(view -> {
- bitmap = processed;
- isProcessed = true;
- bottomAppBarContainer.setVisibility(View.VISIBLE);
- bottomAppBar.getMenu().getItem(MENU_RESET).setEnabled(true);
- blurBias = (int) sliderBlur.getValue();
- brightnessBias = (int) sliderBrightness.getValue();
- if (applyEditToHome == applyEditToLock) {
- imageView.setImageBitmap(bitmap);
- } else if (applyEditToHome) {
- imageView.setImageBitmap(bitmap);
- } else if (applyEditToLock) {
- imageView.setImageBitmap(bitmap);
- currentImageViewIsHome = false;
- fab.setImageResource(R.drawable.ic_lockscreen);
- //todo: set fab icon
- }
-
- dialog.dismiss();
- });
-
- dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(view -> {
- sliderBlur.setValue(0.0f);
- sliderBrightness.setValue(0.0f);
- bitmap = raw;
- isProcessed = false;
- applyEditToLock = applyEditToHome = true;
- chipHome.setChecked(true);
- chipLock.setChecked(applyEditToLock);
- imageView.setImageBitmap(raw);
- Log.v("WALLP", "SET RAW NEUTRAL");
-
- });
-
- dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(view -> {
- bitmap = raw;
- blurBias = 0;
- brightnessBias = 0;
- isProcessed = false;
- applyEditToLock = applyEditToHome = true;
- chipHome.setChecked(true);
- chipLock.setChecked(applyEditToLock);
- imageView.setImageBitmap(raw);
- Log.v("WALLP", "SET RAW NEGATIVE");
- bottomAppBarContainer.setVisibility(View.VISIBLE);
- dialog.dismiss();
- });
-
- sliderBlur.addOnChangeListener((slider1, value, fromUser) -> {
- Bitmap toProcess = bitmap;
- if (brightnessProcessed != null) {
- toProcess = brightnessProcessed;
- }
- HokoBlur.with(getApplicationContext())
- .radius((int) value)
- .sampleFactor(1.0f)
- .forceCopy(true)
- .asyncBlur(toProcess, new AsyncBlurTask.Callback() {
- @Override
- public void onBlurSuccess(Bitmap bitmap) {
- Log.v("WALLP", "BLURRRRRRRRRRRING");
- processed = bitmap;
- blurProcessed = bitmap;
- imageView.setImageBitmap(bitmap);
- if (value == 0.0f && sliderBrightness.getValue() == 0.0f) {
- imageView.setImageBitmap(bitmap);
- Log.v("WALLP", "SET RAW IN BLUR");
- }
- }
-
- @Override
- public void onBlurFailed(Throwable error) {
-
- }
- });
-
- });
- sliderBrightness.addOnChangeListener((slider1, value, fromUser) -> {
- Bitmap toProcess = bitmap;
- if (blurProcessed != null) {
- toProcess = blurProcessed;
- }
- Bitmap finalToProcess = toProcess;
- new Thread(() -> {
- processed = Util.adjustBrightness(finalToProcess, (int) value);
- brightnessProcessed = processed;
- Log.v("WALLP", "BRIGHTTTTTTTTTTING");
-
- runOnUiThread(() -> {
- imageView.setImageBitmap(processed);
- if (value == 0.0f && sliderBlur.getValue() == 0.0f) {
- imageView.setImageBitmap(bitmap);
- Log.v("WALLP", "SET RAW IN BRIG");
- }
- });
- }).start();
-
- });
- } else if (item.getItemId() == R.id.reset) {
- bitmap = raw;
- blurBias = 0;
- brightnessBias = 0;
- cord = null;
- isProcessed = false;
- applyEditToLock = applyEditToHome = true;
- imageView.setImageBitmap(bitmap);
- if (isVertical) {
- verticalScrollView.post(() -> verticalScrollView.scrollTo(0, 0));
- } else {
- horizontalScrollView.post(() -> horizontalScrollView.scrollTo(0, 0));
- }
- }
- return true;
- });
-
- ActionMenuItemView resetItem = bottomAppBar.findViewById(R.id.reset);
- resetItem.setOnLongClickListener(view -> {
- Intent intent1 = new Intent(getApplicationContext(), HistoryActivity.class);
- startActivity(intent1);
- return true;
- });
-
- Context context = DynamicColors.wrapContextIfAvailable(
- MainActivity.this
- );
-
- //setup AlertDialog builder
- builder = new MaterialAlertDialogBuilder(context);
- builder.setTitle(R.string.setAs);
-
- String[] options = {
- getResources().getString(R.string.home),
- getResources().getString(R.string.lockscreen),
- getResources().getString(R.string.homeAndLockscreen),
- getResources().getString(R.string.use_others)};
-
- builder.setItems(options, (dialog, which) -> executorService.execute(() -> {
- try {
- if (new PreferencesHelper(this).getSettingsHistory()){
- //若已经选择保存选项,弹出「设置图片为」选项之前保存图片
- if (checkPermission()) {
- Bitmap currentWallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
- Util_Files.saveWallpaper(currentWallpaper, this);
- }else {
- Snackbar.make(container, R.string.no_permission, Snackbar.LENGTH_SHORT)
- .show();
- }
- }
-
- switch (which) {
- case 0 -> {
- wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_SYSTEM);
- }
- case 1 -> {
- wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_LOCK);
- }
- case 2 -> {
- if (applyEditToLock) {
- wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_LOCK);
- } else {
- wallpaperManager.setBitmap(raw, cord, true, WallpaperManager.FLAG_LOCK);
- }
- if (applyEditToHome) {
- wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_SYSTEM);
- } else {
- wallpaperManager.setBitmap(raw, cord, true, WallpaperManager.FLAG_SYSTEM);
- }
- }
- //应对定制 Rom(如 Color OS)可能存在的魔改导致 "WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM" 参数失效的情况。
- case 3 -> {
- shareBitmap(bitmap, getApplicationContext());
- }
- default -> throw new IllegalStateException("Unexpected value: " + which);
- }
- } catch (IOException e) {
- e.printStackTrace();
+ case 3 -> {
+ Uri myImageFileUri = viewModel.getBitmapUri(getApplicationContext(), Objects.requireNonNull(getExternalCacheDir()));
+ shareUri(getApplicationContext(), myImageFileUri);
}
+ default -> throw new IllegalStateException("Unexpected value: " + which);
+ }
+ } catch (IOException e) {
+ Log.e("ERROR", e.toString());
+ }
- }));
+ }));
+
+ binding.bottomAppBarContainer.bringToFront();
- bottomAppBarContainer.bringToFront();
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
}
- private Boolean checkPermission(){
+ private Boolean checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Boolean hasStoragePermission = ContextCompat.checkSelfPermission(
getApplicationContext(),
@@ -563,47 +236,27 @@ private void returnToHomeScreen() {
startActivity(intent);
}
- //用于亮度/模糊的 Slider Bar 对话框
- @SuppressLint("InflateParams")
- private AlertDialog createSliderDialog() {
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);//, R.style.TransparentDialogTheme);
- LayoutInflater inflater = this.getLayoutInflater();
-
- builder.setView(inflater.inflate(R.layout.layout_dialog_adjustment, null))
- .setPositiveButton(R.string.save, null)
- .setNeutralButton(R.string.reset, null)
- .setNegativeButton(R.string.cancel, null);
-
- return builder.create();
- }
-
-
-
- private void shareBitmap(@NonNull Bitmap bitmap, Context context) {
- //---Save bitmap to external cache directory---//
- //get cache directory
- File cachePath = new File(getExternalCacheDir(), "my_images/");
- cachePath.mkdirs();
-
- //create png file
- File file = new File(cachePath, "Image_123.png");
- FileOutputStream fileOutputStream;
- try {
- fileOutputStream = new FileOutputStream(file);
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
- fileOutputStream.flush();
- fileOutputStream.close();
+ public static class ShareReceiver extends BroadcastReceiver {
- } catch (IOException e) {
- e.printStackTrace();
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Intent local = new Intent();
+ local.setAction("done");
+ context.sendBroadcast(local);
}
+ }
- //---Share File---//
- //get file uri
- Uri myImageFileUri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);
+ private void loadFragment(Fragment fragment) {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ FragmentTransaction transaction = fragmentManager.beginTransaction();
+ transaction.replace(R.id.editor_container, fragment);
+ transaction.addToBackStack(null); // 可选,用于返回栈管理
+ transaction.commit();
+ }
+ private void shareUri(Context context, Uri uri) {
Intent sendIntent = new Intent(Intent.ACTION_ATTACH_DATA);
- sendIntent.setDataAndType(myImageFileUri, "image/*");
+ sendIntent.setDataAndType(uri, "image/*");
sendIntent.putExtra("mimeType", "image/*");
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -617,14 +270,4 @@ private void shareBitmap(@NonNull Bitmap bitmap, Context context) {
, pendingIntent.getIntentSender()
));
}
-
- public static class ShareReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Intent local = new Intent();
- local.setAction("done");
- context.sendBroadcast(local);
- }
- }
}
diff --git a/app/src/main/java/com/maary/shareas/MediaStoreImage.kt b/app/src/main/java/com/maary/shareas/data/MediaStoreImage.kt
similarity index 95%
rename from app/src/main/java/com/maary/shareas/MediaStoreImage.kt
rename to app/src/main/java/com/maary/shareas/data/MediaStoreImage.kt
index 867a0dd..43b9bfb 100644
--- a/app/src/main/java/com/maary/shareas/MediaStoreImage.kt
+++ b/app/src/main/java/com/maary/shareas/data/MediaStoreImage.kt
@@ -1,4 +1,4 @@
-package com.maary.shareas
+package com.maary.shareas.data
import android.net.Uri
import androidx.recyclerview.widget.DiffUtil
diff --git a/app/src/main/java/com/maary/shareas/data/ViewerBitmap.kt b/app/src/main/java/com/maary/shareas/data/ViewerBitmap.kt
new file mode 100644
index 0000000..f1cef4c
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/data/ViewerBitmap.kt
@@ -0,0 +1,8 @@
+package com.maary.shareas.data
+
+import android.graphics.Bitmap
+
+data class ViewerBitmap(
+ var bitmapHome: Bitmap? = null,
+ var bitmapLock: Bitmap? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/EditorFragment.kt b/app/src/main/java/com/maary/shareas/fragment/EditorFragment.kt
new file mode 100644
index 0000000..ab6630d
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/fragment/EditorFragment.kt
@@ -0,0 +1,201 @@
+package com.maary.shareas.fragment
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedCallback
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.activityViewModels
+import androidx.fragment.app.commitNow
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.maary.shareas.WallpaperViewModel
+import com.maary.shareas.databinding.FragmentEditorBinding
+import com.maary.shareas.fragment.editor.BlurFragment
+import com.maary.shareas.fragment.editor.BrightnessFragment
+import com.maary.shareas.fragment.editor.PaintFragment
+import com.maary.shareas.fragment.editor.UpscaleFragment
+import kotlinx.coroutines.launch
+
+class EditorFragment : Fragment() {
+
+ private val viewModel: WallpaperViewModel by activityViewModels()
+ private var _binding: FragmentEditorBinding? = null
+ private val binding get() = _binding!!
+
+ private lateinit var onBackPressedCallback: OnBackPressedCallback
+
+ private var isSaved = false
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ viewModel.startEditing()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.currentBitmapState.collect { state ->
+ if (state == WallpaperViewModel.HOME) {
+ binding.appbarToggleGroup.check(binding.appbarButtonHome.id)
+ }
+ if (state == WallpaperViewModel.LOCK) {
+ binding.appbarToggleGroup.check(binding.appbarButtonLock.id)
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.upscaleProgressState.collect { state ->
+ if (state == 100) {
+ binding.editorButtonApply.visibility = View.VISIBLE
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.primaryColorState.collect {
+ if (viewModel.primary != null) {
+ val colorValue = viewModel.tertiary!!
+ val colorStateList = ColorStateList.valueOf(colorValue)
+ binding.editorButtonBlur.backgroundTintList = colorStateList
+ binding.editorButtonBrightness.backgroundTintList = colorStateList
+ binding.editorButtonFill.backgroundTintList = colorStateList
+ binding.editorButtonUpscale.backgroundTintList = colorStateList
+ binding.appbarButtonCancel.setBackgroundColor(viewModel.secondary!!)
+ binding.appbarButtonConfirm.setBackgroundColor(viewModel.secondary!!)
+ binding.editorButtonApply.setTextColor(viewModel.primary!!)
+ binding.editorButtonAbort.setTextColor(viewModel.primary!!)
+ binding.chipApplyHome.backgroundTintList = colorStateList
+ binding.chipApplyLock.backgroundTintList = colorStateList
+ }
+ }
+ }
+ }
+
+ onBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ if (!isSaved) viewModel.restoreChanges()
+ isEnabled = false
+ activity?.onBackPressedDispatcher?.onBackPressed()
+ }
+ }
+ requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+
+ }
+
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentEditorBinding.inflate(inflater, container, false)
+ ViewCompat.setOnApplyWindowInsetsListener(binding.main) { _, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ binding.editorButtons.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ val chipHome = binding.chipApplyHome
+ val chipLock = binding.chipApplyLock
+
+ binding.appbarButtonHome.setOnClickListener {
+ viewModel.currentBitmap = WallpaperViewModel.HOME
+ }
+
+ binding.appbarButtonLock.setOnClickListener {
+ viewModel.currentBitmap = WallpaperViewModel.LOCK
+ }
+
+ binding.appbarButtonCancel.setOnClickListener {
+ viewModel.restoreChanges()
+ isSaved = false
+ if (binding.editorCard.visibility == View.VISIBLE) {
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+ activity?.onBackPressedDispatcher?.onBackPressed()
+ }
+
+ binding.appbarButtonConfirm.setOnClickListener {
+ viewModel.saveEdit()
+ isSaved = true
+ if (binding.editorCard.visibility == View.VISIBLE) {
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+ activity?.onBackPressedDispatcher?.onBackPressed()
+ }
+
+ binding.editorButtonApply.setOnClickListener {
+ if (!chipHome.isChecked) viewModel.abortEditHome()
+ if (!chipLock.isChecked) viewModel.abortEditLock()
+ viewModel.saveEdit()
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+
+ binding.editorButtonAbort.setOnClickListener {
+ viewModel.abortEdit()
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+
+ binding.editorButtonBlur.setOnClickListener {
+ loadFragment(BlurFragment())
+ }
+
+ binding.editorButtonBrightness.setOnClickListener {
+ loadFragment(BrightnessFragment())
+ }
+
+ binding.editorButtonFill.setOnClickListener {
+ loadFragment(PaintFragment())
+ }
+
+ binding.editorButtonUpscale.setOnClickListener {
+ loadFragment(UpscaleFragment())
+ }
+
+ // Inflate the layout for this fragment
+ return binding.root
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ viewModel.finishEditing()
+ onBackPressedCallback.remove()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ private fun loadFragment(fragment: Fragment) {
+ viewModel.abortEdit()
+ val fragmentManager: FragmentManager = childFragmentManager
+ fragmentManager.commitNow {
+ replace(binding.editorFragmentContainer.id, fragment)
+ }
+ if (fragment is UpscaleFragment) {
+ binding.editorButtonApply.visibility = View.INVISIBLE
+ binding.chipApplyHome.isClickable = false
+ binding.chipApplyLock.isClickable = false
+ } else {
+ binding.editorButtonApply.visibility = View.VISIBLE
+ binding.chipApplyHome.isClickable = true
+ binding.chipApplyLock.isClickable = true
+ }
+ binding.editorCard.visibility = View.VISIBLE
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt
index 80cab0c..c8ede6d 100644
--- a/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt
+++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt
@@ -20,27 +20,7 @@ import com.maary.shareas.activity.StartActivity
import com.maary.shareas.databinding.FragmentWelcomeFinishBinding
import kotlinx.coroutines.launch
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val ARG_PARAM1 = "param1"
-private const val ARG_PARAM2 = "param2"
-
-/**
- * A simple [Fragment] subclass.
- * Use the [WelcomeFinishFragment.newInstance] factory method to
- * create an instance of this fragment.
- */
class WelcomeFinishFragment : Fragment() {
- // TODO: Rename and change types of parameters
- private var param1: String? = null
- private var param2: String? = null
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- arguments?.let {
- param1 = it.getString(ARG_PARAM1)
- param2 = it.getString(ARG_PARAM2)
- }
- }
private var _binding: FragmentWelcomeFinishBinding? = null
// This property is only valid between onCreateView and
@@ -85,24 +65,4 @@ class WelcomeFinishFragment : Fragment() {
super.onDestroyView()
_binding = null
}
-
- companion object {
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment WelcomeFinishFragment.
- */
- // TODO: Rename and change types and number of parameters
- @JvmStatic
- fun newInstance(param1: String, param2: String) =
- WelcomeFinishFragment().apply {
- arguments = Bundle().apply {
- putString(ARG_PARAM1, param1)
- putString(ARG_PARAM2, param2)
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt
index 017aaf0..8f83dee 100644
--- a/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt
+++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt
@@ -33,28 +33,7 @@ import com.maary.shareas.databinding.FragmentWelcomeHistoryBinding
import kotlinx.coroutines.launch
import java.util.concurrent.Executor
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val ARG_PARAM1 = "param1"
-private const val ARG_PARAM2 = "param2"
-
-/**
- * A simple [Fragment] subclass.
- * Use the [WelcomeHistoryFragment.newInstance] factory method to
- * create an instance of this fragment.
- */
class WelcomeHistoryFragment : Fragment() {
- // TODO: Rename and change types of parameters
- private var param1: String? = null
- private var param2: String? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- arguments?.let {
- param1 = it.getString(ARG_PARAM1)
- param2 = it.getString(ARG_PARAM2)
- }
- }
private var _binding: FragmentWelcomeHistoryBinding? = null
// This property is only valid between onCreateView and
@@ -199,26 +178,6 @@ class WelcomeHistoryFragment : Fragment() {
_binding = null
}
- companion object {
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment WelcomeHistoryFragment.
- */
- // TODO: Rename and change types and number of parameters
- @JvmStatic
- fun newInstance(param1: String, param2: String) =
- WelcomeHistoryFragment().apply {
- arguments = Bundle().apply {
- putString(ARG_PARAM1, param1)
- putString(ARG_PARAM2, param2)
- }
- }
- }
-
private fun checkPermission(): Boolean{
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val hasStoragePermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED
diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt
index a4f8184..6a8317a 100644
--- a/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt
+++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt
@@ -20,28 +20,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.maary.shareas.R
import com.maary.shareas.databinding.FragmentWelcomeSystemBinding
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val ARG_PARAM1 = "param1"
-private const val ARG_PARAM2 = "param2"
-
-/**
- * A simple [Fragment] subclass.
- * Use the [WelcomeSystemFragment.newInstance] factory method to
- * create an instance of this fragment.
- */
class WelcomeSystemFragment : Fragment() {
- // TODO: Rename and change types of parameters
- private var param1: String? = null
- private var param2: String? = null
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- arguments?.let {
- param1 = it.getString(ARG_PARAM1)
- param2 = it.getString(ARG_PARAM2)
- }
- }
private var _binding: FragmentWelcomeSystemBinding? = null
// This property is only valid between onCreateView and
@@ -105,24 +85,4 @@ class WelcomeSystemFragment : Fragment() {
binding.textWelcomeSystemPermission1.helperText = getText(R.string.permission_got)
}
}
-
- companion object {
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment WelcomeSystemFragment.
- */
- // TODO: Rename and change types and number of parameters
- @JvmStatic
- fun newInstance(param1: String, param2: String) =
- WelcomeSystemFragment().apply {
- arguments = Bundle().apply {
- putString(ARG_PARAM1, param1)
- putString(ARG_PARAM2, param2)
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/editor/BlurFragment.kt b/app/src/main/java/com/maary/shareas/fragment/editor/BlurFragment.kt
new file mode 100644
index 0000000..46ad2a8
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/fragment/editor/BlurFragment.kt
@@ -0,0 +1,83 @@
+package com.maary.shareas.fragment.editor
+
+import android.content.res.ColorStateList
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedCallback
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.slider.Slider
+import com.maary.shareas.WallpaperViewModel
+import com.maary.shareas.databinding.FragmentBlurBinding
+import kotlinx.coroutines.launch
+
+class BlurFragment : Fragment() {
+
+ private val viewModel: WallpaperViewModel by activityViewModels()
+ private var _binding: FragmentBlurBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var onBackPressedCallback: OnBackPressedCallback
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ onBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ viewModel.abortEdit()
+ requireParentFragment().childFragmentManager.beginTransaction().remove(this@BlurFragment).commit()
+ requireParentFragment().childFragmentManager.popBackStack()
+ // 获取包含当前 Fragment 的布局
+ val containingLayout = requireView().parent.parent.parent as? View
+ // 如果布局不为空,则隐藏布局
+ containingLayout?.visibility = View.INVISIBLE
+ isEnabled = false
+ }
+ }
+ requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.primaryColorState.collect {
+ if (viewModel.primary != null) {
+ val primaryValue = viewModel.primary!!
+ val primaryStateList = ColorStateList.valueOf(primaryValue)
+ val secondaryValue = viewModel.secondary!!
+ val secondaryStateList = ColorStateList.valueOf(secondaryValue)
+ binding.adjustmentSlider.thumbTintList = primaryStateList
+ binding.adjustmentSlider.trackActiveTintList = secondaryStateList
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?): View {
+ // Inflate the layout for this fragment
+ _binding = FragmentBlurBinding.inflate(inflater, container, false)
+ val sliderBlur = binding.adjustmentSlider
+
+ sliderBlur.valueFrom = 0f
+ sliderBlur.valueTo = 25f
+ sliderBlur.stepSize = 1f
+ sliderBlur.addOnChangeListener(Slider.OnChangeListener { _: Slider?, value: Float, _: Boolean ->
+ viewModel.editBlur(requireActivity(), value)
+ })
+ return binding.root
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ onBackPressedCallback.remove()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/editor/BrightnessFragment.kt b/app/src/main/java/com/maary/shareas/fragment/editor/BrightnessFragment.kt
new file mode 100644
index 0000000..39e2184
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/fragment/editor/BrightnessFragment.kt
@@ -0,0 +1,88 @@
+package com.maary.shareas.fragment.editor
+
+import android.content.res.ColorStateList
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedCallback
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.slider.Slider
+import com.maary.shareas.WallpaperViewModel
+import com.maary.shareas.databinding.FragmentBrightnessBinding
+import kotlinx.coroutines.launch
+
+class BrightnessFragment : Fragment() {
+
+ private val viewModel: WallpaperViewModel by activityViewModels()
+ private var _binding: FragmentBrightnessBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var onBackPressedCallback: OnBackPressedCallback
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ onBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ viewModel.abortEdit()
+ requireParentFragment().childFragmentManager.beginTransaction().remove(this@BrightnessFragment).commit()
+ requireParentFragment().childFragmentManager.popBackStack()
+ // 获取包含当前 Fragment 的布局
+ val containingLayout = requireView().parent.parent.parent as? View
+ // 如果布局不为空,则隐藏布局
+ containingLayout?.visibility = View.INVISIBLE
+ isEnabled = false
+ }
+ }
+ requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.primaryColorState.collect {
+ if (viewModel.primary != null) {
+ val primaryValue = viewModel.primary!!
+ val primaryStateList = ColorStateList.valueOf(primaryValue)
+ val secondaryValue = viewModel.secondary!!
+ val secondaryStateList = ColorStateList.valueOf(secondaryValue)
+ binding.adjustmentSlider.thumbTintList = primaryStateList
+ binding.adjustmentSlider.trackActiveTintList = secondaryStateList
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentBrightnessBinding.inflate(inflater, container, false)
+
+ val sliderBrightness = binding.adjustmentSlider
+ sliderBrightness.valueFrom = -50f
+ sliderBrightness.valueTo = 50f
+ sliderBrightness.stepSize = 1f
+ sliderBrightness.value = 0f
+
+ sliderBrightness.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
+ viewModel.editBrightness(value)
+ })
+
+ return binding.root
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ onBackPressedCallback.remove()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/editor/PaintFragment.kt b/app/src/main/java/com/maary/shareas/fragment/editor/PaintFragment.kt
new file mode 100644
index 0000000..15823b1
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/fragment/editor/PaintFragment.kt
@@ -0,0 +1,219 @@
+package com.maary.shareas.fragment.editor
+
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import androidx.activity.OnBackPressedCallback
+import androidx.core.content.ContextCompat.getSystemService
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.button.MaterialButton
+import com.maary.shareas.R
+import com.maary.shareas.WallpaperViewModel
+import com.maary.shareas.databinding.FragmentPaintBinding
+import kotlinx.coroutines.launch
+
+class PaintFragment : Fragment() {
+
+ private var _binding: FragmentPaintBinding? = null
+ private val binding get() = _binding!!
+ private val viewModel: WallpaperViewModel by activityViewModels()
+
+ private var position = 4 //viewModel.CENTER
+ private lateinit var onBackPressedCallback: OnBackPressedCallback
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ onBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ viewModel.abortEdit()
+ requireParentFragment().childFragmentManager.beginTransaction().remove(this@PaintFragment).commit()
+ requireParentFragment().childFragmentManager.popBackStack()
+ // 获取包含当前 Fragment 的布局
+ val containingLayout = requireView().parent.parent.parent as? View
+ // 如果布局不为空,则隐藏布局
+ containingLayout?.visibility = View.INVISIBLE
+ isEnabled = false
+ }
+ }
+ requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.primaryColorState.collect {
+ if (viewModel.primary != null) {
+ val colorValue = viewModel.primary!!
+ val colorStateList = ColorStateList.valueOf(colorValue)
+ binding.buttonPaint.iconTint = colorStateList
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentPaintBinding.inflate(inflater, container, false)
+
+ var paintType = 0
+
+ binding.buttonAlignCenter.isChecked = true
+
+ binding.hexEditText.addTextChangedListener(object: TextWatcher {
+ override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
+ }
+
+ override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
+ }
+
+ override fun afterTextChanged(p0: Editable?) {
+ if (p0?.length == 6){
+ binding.hexEditText.onEditorAction(EditorInfo.IME_ACTION_DONE)
+ paintType = binding.buttonColorCustom.id
+ checkButton(paintType)
+ binding.buttonColorCustom.setBackgroundColor(Color.parseColor("#${binding.hexEditText.text}"))
+ }
+ }
+ })
+
+ binding.buttonsAlignment.addOnButtonCheckedListener { _, checkId, isChecked ->
+ if (isChecked) {
+ when (checkId) {
+ R.id.button_align_left -> position = WallpaperViewModel.LEFT
+ R.id.button_align_top -> position = WallpaperViewModel.TOP
+ R.id.button_align_center -> position = WallpaperViewModel.CENTER
+ R.id.button_align_bottom -> position = WallpaperViewModel.BOTTOM
+ R.id.button_align_right -> position = WallpaperViewModel.RIGHT
+ }
+ }
+
+ }
+
+ val colors = viewModel.extractTopColorsFromBitmap()
+ binding.buttonColor1.setBackgroundColor(colors[0])
+ binding.buttonColor2.setBackgroundColor(colors[1])
+ binding.buttonColor3.setBackgroundColor(colors[2])
+ binding.buttonColor4.setBackgroundColor(colors[3])
+ binding.buttonColor5.setBackgroundColor(colors[4])
+
+ binding.buttonColorCustom.setOnClickListener {
+ if (binding.hexEditText.text.isNullOrEmpty()) {
+ binding.hexEditText.requestFocus()
+ // 显示键盘
+ val imm = getSystemService(requireContext(), InputMethodManager::class.java) as InputMethodManager
+ imm.showSoftInput(binding.hexEditText, InputMethodManager.SHOW_IMPLICIT)
+ } else {
+ paintType = it.id
+ setZoom()
+ }
+ }
+
+ binding.buttonColor1.setOnClickListener {
+ paintType = it.id
+ setZoom()
+ checkButton(it.id)
+ }
+ binding.buttonColor2.setOnClickListener {
+ paintType = it.id
+ setZoom()
+ checkButton(it.id)
+ }
+ binding.buttonColor3.setOnClickListener {
+ paintType = it.id
+ setZoom()
+ checkButton(it.id)
+ }
+ binding.buttonColor4.setOnClickListener {
+ paintType = it.id
+ setZoom()
+ checkButton(it.id)
+ }
+ binding.buttonColor5.setOnClickListener {
+ paintType = it.id
+ setZoom()
+ checkButton(it.id)
+ }
+
+ binding.buttonBlur.setOnClickListener {
+ paintType = it.id
+ if (binding.scaleEditText.text.isNullOrEmpty()) {
+ binding.scaleEditText.setText(
+ resources.getStringArray(R.array.zoom_scales)[3],
+ false
+ )
+ }
+ checkButton(it.id)
+ }
+
+ binding.buttonPaint.setOnClickListener {
+ val zoom = binding.scaleEditText.text.toString().toFloat()
+ when (paintType) {
+ R.id.button_color1 -> viewModel.paintColor(position, colors[0], zoom)
+ R.id.button_color2 -> viewModel.paintColor(position, colors[1], zoom)
+ R.id.button_color3 -> viewModel.paintColor(position, colors[2], zoom)
+ R.id.button_color4 -> viewModel.paintColor(position, colors[3], zoom)
+ R.id.button_color5 -> viewModel.paintColor(position, colors[4], zoom)
+ R.id.button_color_custom ->
+ viewModel.paintColor(position, Color.parseColor("#${binding.hexEditText.text}"), zoom)
+ R.id.button_blur -> viewModel.paintBlur(position, 16, requireContext(), zoom)
+
+ }
+ }
+
+ // Inflate the layout for this fragment
+ return binding.root
+ }
+
+ private fun checkButton(buttonId: Int) {
+ val buttons = mutableListOf(
+ R.id.button_blur,
+ R.id.button_color1,
+ R.id.button_color2,
+ R.id.button_color3,
+ R.id.button_color4,
+ R.id.button_color5,
+ R.id.button_color_custom)
+
+ for (button in buttons) {
+ if (button == buttonId) {
+ binding.root.findViewById(button).setIconResource(R.drawable.ic_done)
+ } else {
+ if (button == R.id.button_blur) {
+ binding.root.findViewById(button).setIconResource(R.drawable.ic_blur_16)
+ } else {
+ binding.root.findViewById(button).icon = null
+ }
+ }
+ }
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ onBackPressedCallback.remove()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ private fun setZoom() {
+ if (binding.scaleEditText.text.isNullOrEmpty()) {
+ binding.scaleEditText.setText(resources.getStringArray(R.array.zoom_scales)[5], false)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/editor/UpscaleFragment.kt b/app/src/main/java/com/maary/shareas/fragment/editor/UpscaleFragment.kt
new file mode 100644
index 0000000..dbb6f61
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/fragment/editor/UpscaleFragment.kt
@@ -0,0 +1,116 @@
+package com.maary.shareas.fragment.editor
+
+import android.content.res.ColorStateList
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedCallback
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.maary.shareas.R
+import com.maary.shareas.WallpaperViewModel
+import com.maary.shareas.databinding.FragmentUpscaleBinding
+import kotlinx.coroutines.launch
+
+class UpscaleFragment : Fragment() {
+
+ private val viewModel: WallpaperViewModel by activityViewModels()
+ private var _binding: FragmentUpscaleBinding? = null
+ private val binding get() = _binding!!
+
+ private lateinit var onBackPressedCallback: OnBackPressedCallback
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ onBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ viewModel.abortEdit()
+ requireParentFragment().childFragmentManager.beginTransaction().remove(this@UpscaleFragment).commit()
+ requireParentFragment().childFragmentManager.popBackStack()
+ // 获取包含当前 Fragment 的布局
+ val containingLayout = requireView().parent.parent.parent as? View
+ // 如果布局不为空,则隐藏布局
+ containingLayout?.visibility = View.INVISIBLE
+ isEnabled = false
+ }
+ }
+ requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.upscaleProgressState.collect { state ->
+ binding.progressUpscale.setProgressCompat(state, true)
+ if (state == 100) {
+ viewModel.upscaleToggle = !viewModel.upscaleToggle
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.upscaleToggleState.collect { state ->
+ when (state) {
+ true -> {
+ binding.buttonUpscaleToggle.setIconResource(R.drawable.ic_action_close)
+ binding.progressUpscale.isIndeterminate = true
+ viewModel.upscale(requireContext(), binding.menuChooseModelTextview.text.toString())
+ }
+ false -> {
+ binding.buttonUpscaleToggle.setIconResource(R.drawable.ic_play)
+ binding.progressUpscale.setProgressCompat(0, true)
+ }
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.primaryColorState.collect {
+ if (viewModel.primary != null) {
+ val primaryValue = viewModel.primary!!
+ val primaryStateList = ColorStateList.valueOf(primaryValue)
+ val tertiaryValue = viewModel.tertiary!!
+ val tertiaryStateList = ColorStateList.valueOf(tertiaryValue)
+ binding.buttonUpscaleToggle.iconTint = primaryStateList
+ binding.betaIcon.iconTint = tertiaryStateList
+ binding.betaIcon.setTextColor(tertiaryValue)
+ }
+ }
+ }
+ }
+
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ onBackPressedCallback.remove()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentUpscaleBinding.inflate(inflater, container, false)
+
+ binding.menuChooseModelTextview.setText(resources.getStringArray(R.array.model_names)[2], false)
+
+ binding.buttonUpscaleToggle.setOnClickListener {
+ viewModel.upscaleToggle = !viewModel.upscaleToggle
+ }
+
+ return binding.root
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/helper/SuperResPerformer.kt b/app/src/main/java/com/maary/shareas/helper/SuperResPerformer.kt
new file mode 100644
index 0000000..492268c
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/helper/SuperResPerformer.kt
@@ -0,0 +1,60 @@
+package com.maary.shareas.helper
+
+import ai.onnxruntime.OnnxJavaType
+import ai.onnxruntime.OnnxTensor
+import ai.onnxruntime.OrtEnvironment
+import ai.onnxruntime.OrtSession
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.util.Log
+import java.io.InputStream
+import java.nio.ByteBuffer
+import java.util.Collections
+
+internal data class Result(
+ var outputBitmap: Bitmap? = null
+)
+
+internal class SuperResPerformer {
+
+ fun upscale(inputStream: InputStream, ortEnv: OrtEnvironment, ortSession: OrtSession): Result {
+ val result = Result()
+
+ // Step 1: convert image into byte array (raw image bytes)
+ val rawImageBytes = inputStream.readBytes()
+
+ // Step 2: get the shape of the byte array and make ort tensor
+ val shape = longArrayOf(rawImageBytes.size.toLong())
+
+ val inputTensor = OnnxTensor.createTensor(
+ ortEnv,
+ ByteBuffer.wrap(rawImageBytes),
+ shape,
+ OnnxJavaType.UINT8
+ )
+
+ inputTensor.use {
+ // Step 3: call ort inferenceSession run
+ Log.v("WVM", "RUN")
+
+ val output = ortSession.run(Collections.singletonMap("image", inputTensor))
+ Log.v("WVM", "RUN FINISHED")
+
+
+ // Step 4: output analysis
+ output.use {
+ val rawOutput = (output?.get(0)?.value) as ByteArray
+ val outputImageBitmap =
+ byteArrayToBitmap(rawOutput)
+
+ // Step 5: set output result
+ result.outputBitmap = outputImageBitmap
+ }
+ }
+ return result
+ }
+
+ private fun byteArrayToBitmap(data: ByteArray): Bitmap {
+ return BitmapFactory.decodeByteArray(data, 0, data.size)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/helper/Util.java b/app/src/main/java/com/maary/shareas/helper/Util.java
index 7fbec3e..a21c43f 100644
--- a/app/src/main/java/com/maary/shareas/helper/Util.java
+++ b/app/src/main/java/com/maary/shareas/helper/Util.java
@@ -8,47 +8,60 @@
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.ImageDecoder;
import android.graphics.Paint;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
-import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.view.WindowMetrics;
-import java.io.IOException;
+import java.io.FileNotFoundException;
import java.io.InputStream;
public class Util {
- public static Bitmap getBitmap(Intent intent, Context context) throws IOException {
+ public static Bitmap getBitmap(Intent intent, Context context) {
Uri imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (imageUri != null) {
- InputStream inputStream = context.getContentResolver().openInputStream(imageUri);
+ InputStream inputStream;
+ try {
+ inputStream = context.getContentResolver().openInputStream(imageUri);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
return BitmapFactory.decodeStream(inputStream);
} else return null;
}
public static Point getDeviceBounds(Context context) {
- WindowMetrics windowMetrics;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- windowMetrics = ((Activity)context).getWindowManager().getMaximumWindowMetrics();
- return new Point(windowMetrics.getBounds().width(), windowMetrics.getBounds().height());
- }else {
- DisplayMetrics displayMetrics = new DisplayMetrics();
- ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
- int height = displayMetrics.heightPixels;
- int width = displayMetrics.widthPixels;
- return new Point(width, height);
+ PreferencesHelper preferencesHelper = new PreferencesHelper(context);
+ int device_height, device_width;
+
+ device_height = preferencesHelper.getHeight();
+ if (device_height == -1){
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ WindowMetrics windowMetrics = ((Activity)context).getWindowManager().getMaximumWindowMetrics();
+ device_height = windowMetrics.getBounds().height();
+ device_width = windowMetrics.getBounds().width();
+ }else {
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ device_height = displayMetrics.heightPixels;
+ device_width = displayMetrics.widthPixels;
+ }
+ preferencesHelper.setWidthAndHeight(device_width, device_height);
+ } else {
+ device_width = preferencesHelper.getWidth();
}
+ return new Point(device_width, device_height);
+
}
- public static Boolean isVertical(int dheight, int dwidth, Bitmap bitmap) {
+ public static Boolean isVertical(int dHeight, int dWidth, Bitmap bitmap) {
int bitmap_full_width = bitmap.getWidth();
int bitmap_full_height = bitmap.getHeight();
- double device_scale = (double) dheight / dwidth;
+ double device_scale = (double) dHeight / dWidth;
double bitmap_scale = (double) bitmap_full_height / bitmap_full_width;
return device_scale < bitmap_scale;
diff --git a/app/src/main/java/com/maary/shareas/helper/Util_Files.java b/app/src/main/java/com/maary/shareas/helper/Util_Files.java
index ea73885..81f8870 100644
--- a/app/src/main/java/com/maary/shareas/helper/Util_Files.java
+++ b/app/src/main/java/com/maary/shareas/helper/Util_Files.java
@@ -7,11 +7,13 @@
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
-import java.util.ArrayList;
import java.util.Calendar;
public class Util_Files {
@@ -19,18 +21,7 @@ public class Util_Files {
static String customDir = Environment.DIRECTORY_PICTURES + File.separator + "Wallpaper History";
public static void saveWallpaper(Bitmap bitmap, Activity activity){
- Calendar calendar = Calendar.getInstance();
- String fileName = "WLP_" +
- (calendar.get(Calendar.YEAR) - 1900) +
- calendar.get(Calendar.MONTH) +
- calendar.get(Calendar.DAY_OF_MONTH) +
- calendar.get(Calendar.HOUR_OF_DAY) +
- calendar.get(Calendar.MINUTE) +
- calendar.get(Calendar.MILLISECOND);
- final ContentValues contentValues = new ContentValues();
- contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
- contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
- contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, customDir);
+ final ContentValues contentValues = getContentValues();
final ContentResolver contentResolver = activity.getContentResolver();
Uri uri;
@@ -47,7 +38,24 @@ public static void saveWallpaper(Bitmap bitmap, Activity activity){
}
outputStream.close();
} catch (IOException e) {
- e.printStackTrace();
+ Log.e("WPT", e.toString());
}
}
+
+ @NonNull
+ private static ContentValues getContentValues() {
+ Calendar calendar = Calendar.getInstance();
+ String fileName = "WLP_" +
+ (calendar.get(Calendar.YEAR) - 1900) +
+ calendar.get(Calendar.MONTH) +
+ calendar.get(Calendar.DAY_OF_MONTH) +
+ calendar.get(Calendar.HOUR_OF_DAY) +
+ calendar.get(Calendar.MINUTE) +
+ calendar.get(Calendar.MILLISECOND);
+ final ContentValues contentValues = new ContentValues();
+ contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+ contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
+ contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, customDir);
+ return contentValues;
+ }
}
diff --git a/app/src/main/java/com/maary/shareas/view/ScrollableImageView.kt b/app/src/main/java/com/maary/shareas/view/ScrollableImageView.kt
new file mode 100644
index 0000000..5a38365
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/view/ScrollableImageView.kt
@@ -0,0 +1,97 @@
+package com.maary.shareas.view
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.net.Uri
+import android.util.AttributeSet
+import android.util.Log
+import android.widget.HorizontalScrollView
+import android.widget.ImageView
+import android.widget.ScrollView
+
+class ScrollableImageView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : ScrollView(context, attrs, defStyleAttr) {
+
+ private lateinit var horizontalView: CustomHorizontalView
+ private lateinit var imageView: ImageView
+
+ init {
+ initHorizontalView(context)
+ }
+
+ private fun initHorizontalView(context: Context) {
+ horizontalView = CustomHorizontalView(context)
+ addView(horizontalView)
+ imageView = ImageView(context)
+ horizontalView.addView(imageView)
+ }
+
+ fun setImageBitmap(bitmap: Bitmap) {
+ imageView.setImageBitmap(bitmap)
+ imageView.scaleType = ImageView.ScaleType.FIT_XY
+ }
+
+ fun setImageUri(uri: Uri) {
+ imageView.setImageURI(uri)
+ imageView.scaleType = ImageView.ScaleType.FIT_XY
+ }
+
+ fun setOnImageClickListener(listener: OnClickListener) {
+ imageView.setOnClickListener(listener)
+ }
+
+ fun getVisibleBitmap(): Bitmap {
+ // 获取 ScrollView 和 HorizontalView 的滚动距离
+ val scrollX = scrollX
+ val scrollY = scrollY
+ val horizontalScrollX = horizontalView.scrollX
+ val horizontalScrollY = horizontalView.scrollY
+
+ // 计算可视部分的矩形区域
+ val visibleRect = Rect(scrollX + horizontalScrollX, scrollY + horizontalScrollY,
+ scrollX + horizontalScrollX + width, scrollY + horizontalScrollY + height)
+
+ // 创建与可视部分尺寸相同的 Bitmap
+ val bitmap = Bitmap.createBitmap(visibleRect.width(), visibleRect.height(), Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+
+ // 将 Canvas 移动到 ScrollView 和 HorizontalView 的滚动位置
+ canvas.translate(-visibleRect.left.toFloat(), -visibleRect.top.toFloat())
+
+ // 绘制 ScrollView 和 HorizontalView 可视部分的内容
+ draw(canvas)
+
+ return bitmap
+ }
+
+ fun getVisibleRect(): Rect {
+ // 获取 ScrollView 和 HorizontalView 的滚动距离
+ val scrollX = scrollX
+ val scrollY = scrollY
+ val horizontalScrollX = horizontalView.scrollX
+ val horizontalScrollY = horizontalView.scrollY
+
+ // 计算可视部分的矩形区域
+ return Rect(scrollX + horizontalScrollX, scrollY + horizontalScrollY,
+ scrollX + horizontalScrollX + width, scrollY + horizontalScrollY + height)
+
+ }
+
+
+ inner class CustomHorizontalView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+ ) : HorizontalScrollView(context, attrs, defStyleAttr) {
+
+ init {
+ isHorizontalScrollBarEnabled = false
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_2x.xml b/app/src/main/res/drawable/ic_2x.xml
new file mode 100644
index 0000000..36cf71b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_2x.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_blur_16.xml b/app/src/main/res/drawable/ic_blur_16.xml
new file mode 100644
index 0000000..cd1cacb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_blur_16.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_bottom.xml b/app/src/main/res/drawable/ic_bottom.xml
new file mode 100644
index 0000000..1ad74c1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bottom.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_center.xml b/app/src/main/res/drawable/ic_center.xml
new file mode 100644
index 0000000..fc78fa0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_center.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_fill.xml b/app/src/main/res/drawable/ic_fill.xml
new file mode 100644
index 0000000..6d6b032
--- /dev/null
+++ b/app/src/main/res/drawable/ic_fill.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_foreground.xml b/app/src/main/res/drawable/ic_foreground.xml
new file mode 100644
index 0000000..a3be1b8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_foreground.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_lab.xml b/app/src/main/res/drawable/ic_lab.xml
new file mode 100644
index 0000000..9c73dc3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_lab.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml
new file mode 100644
index 0000000..18c979b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_left.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml
new file mode 100644
index 0000000..34882f5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_lock.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_paint_action.xml b/app/src/main/res/drawable/ic_paint_action.xml
new file mode 100644
index 0000000..392c51c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_paint_action.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_paint_color.xml b/app/src/main/res/drawable/ic_paint_color.xml
new file mode 100644
index 0000000..a3b9d80
--- /dev/null
+++ b/app/src/main/res/drawable/ic_paint_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml
new file mode 100644
index 0000000..b592a28
--- /dev/null
+++ b/app/src/main/res/drawable/ic_play.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_right.xml b/app/src/main/res/drawable/ic_right.xml
new file mode 100644
index 0000000..9b0d9a4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_right.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_top.xml b/app/src/main/res/drawable/ic_top.xml
new file mode 100644
index 0000000..478edc8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_top.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 0d6e5fc..76d0239 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,4 +1,5 @@
+
+
+
-
-
-
\ No newline at end of file
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_start.xml b/app/src/main/res/layout/activity_start.xml
index 84c2779..19bcb49 100644
--- a/app/src/main/res/layout/activity_start.xml
+++ b/app/src/main/res/layout/activity_start.xml
@@ -3,6 +3,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
>
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_blur.xml b/app/src/main/res/layout/fragment_blur.xml
new file mode 100644
index 0000000..a1ab59b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_blur.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_brightness.xml b/app/src/main/res/layout/fragment_brightness.xml
new file mode 100644
index 0000000..5a5a3c6
--- /dev/null
+++ b/app/src/main/res/layout/fragment_brightness.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_editor.xml b/app/src/main/res/layout/fragment_editor.xml
new file mode 100644
index 0000000..b6743f3
--- /dev/null
+++ b/app/src/main/res/layout/fragment_editor.xml
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_paint.xml b/app/src/main/res/layout/fragment_paint.xml
new file mode 100644
index 0000000..476cdbe
--- /dev/null
+++ b/app/src/main/res/layout/fragment_paint.xml
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_upscale.xml b/app/src/main/res/layout/fragment_upscale.xml
new file mode 100644
index 0000000..6ebebc7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_upscale.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_app_bar.xml b/app/src/main/res/menu/bottom_app_bar.xml
index 3940632..4e06bd7 100644
--- a/app/src/main/res/menu/bottom_app_bar.xml
+++ b/app/src/main/res/menu/bottom_app_bar.xml
@@ -14,11 +14,10 @@
android:title="@string/blur"
app:showAsAction="ifRoom"/>
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/raw/realesrgan_anime.onnx b/app/src/main/res/raw/realesrgan_anime.onnx
new file mode 100644
index 0000000..cb0a453
Binary files /dev/null and b/app/src/main/res/raw/realesrgan_anime.onnx differ
diff --git a/app/src/main/res/raw/realesrgan_x2plus.onnx b/app/src/main/res/raw/realesrgan_x2plus.onnx
new file mode 100644
index 0000000..a884ecf
Binary files /dev/null and b/app/src/main/res/raw/realesrgan_x2plus.onnx differ
diff --git a/app/src/main/res/raw/realesrgan_x4plus.onnx b/app/src/main/res/raw/realesrgan_x4plus.onnx
new file mode 100644
index 0000000..e757c28
Binary files /dev/null and b/app/src/main/res/raw/realesrgan_x4plus.onnx differ
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index d86cee9..45693ef 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -47,4 +47,15 @@
权限已获取
添加磁贴
完成初始设置。
+ 确认
+ 应用
+ 超分
+ 选择模型
+ 取消超分
+ 开始超分
+ 测试版
+ 处理过程可能较慢
+ 填充
+ 颜色 Hex
+ 缩放
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..82edd9c
--- /dev/null
+++ b/app/src/main/res/values/arrays.xml
@@ -0,0 +1,17 @@
+
+
+
+ - RealESRGAN-x2p
+ - RealESRGAN-x4p
+ - RealESRGAN-anime
+
+
+
+ - 0.5
+ - 0.6
+ - 0.7
+ - 0.8
+ - 0.9
+ - 1.0
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/custom_style.xml b/app/src/main/res/values/custom_style.xml
index 96f0a1f..a8ba83a 100644
--- a/app/src/main/res/values/custom_style.xml
+++ b/app/src/main/res/values/custom_style.xml
@@ -12,11 +12,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e952b6e..ff6c42a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -57,4 +57,15 @@
Permission Got.
Add Tile
Finish initial settings.
+ Confirm
+ Apply
+ Upscale
+ Choose Model
+ Cancel Upscale
+ Start Upscale
+ beta
+ Processing might be slow.
+ Paint
+ Color Hex
+ Zoom
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 0b33f90..df852ce 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -40,5 +40,22 @@
- false
+
+
+
+
diff --git a/build.gradle b/build.gradle
index d978757..673d0a2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,10 +7,9 @@ buildscript {
repositories {
google()
mavenCentral()
- jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.3.1'
+ classpath 'com.android.tools.build:gradle:8.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
@@ -22,11 +21,10 @@ allprojects {
repositories {
google()
mavenCentral()
- jcenter()
maven { url 'https://jitpack.io' }
}
}
-task clean(type: Delete) {
+tasks.register('clean', Delete) {
delete rootProject.buildDir
}
diff --git a/scripts/prepost.py b/scripts/prepost.py
new file mode 100644
index 0000000..f90aba3
--- /dev/null
+++ b/scripts/prepost.py
@@ -0,0 +1,113 @@
+import io
+import numpy as np
+import onnx
+import onnxruntime as ort
+
+from PIL import Image
+from pathlib import Path
+from onnxruntime_extensions.tools.pre_post_processing import *
+
+# NOTE: Currently assumes we're running from the root directory of the Real-ESRGAN repo so the weights and input image
+# are loaded using paths relative to that.
+_this_dirpath = Path(__file__).parent
+
+def add_pre_post_processing(input_model_path: str, output_model_path: str):
+ # we do a Resize with anti-aliasing which requires ONNX opset 18 in onnxruntime version 1.14 or later
+ from packaging import version
+ if version.parse(ort.__version__) < version.parse("1.14.0"):
+ raise ValueError("ONNX Runtime version 1.14 or later required. Please update your onnxruntime python package.")
+
+ onnx_opset = 18
+ model = onnx.load(input_model_path)
+ inputs = [create_named_value("image", onnx.TensorProto.UINT8, ["num_bytes"])]
+
+ pipeline = PrePostProcessor(inputs, onnx_opset)
+ pipeline.add_pre_processing(
+ [
+ ConvertImageToBGR(), # jpg/png image to BGR in HWC layout
+ ReverseAxis(axis=2, dim_value=3, name="BGR_to_RGB"), # BGR to RGB
+ ChannelsLastToChannelsFirst(), # HWC to CHW
+ ImageBytesToFloat(), # convert to float in range 0..1
+ Unsqueeze([0]), # add batch dimensions
+ ]
+ )
+
+ pipeline.add_post_processing(
+ [
+ Squeeze([0]), # remove batch dimensions
+ FloatToImageBytes(), # convert back to uint8
+ Transpose(perms=[1, 2, 0], name="CHW_to_HWC"), # channels first to channels last
+ ReverseAxis(axis=2, dim_value=3, name="RGB_to_BGR"), # RGB to BGR
+ ConvertBGRToImage(image_format="png", name="convert_to_png"),
+ ]
+ )
+
+ new_model = pipeline.run(model)
+ onnx.save_model(new_model, output_model_path)
+
+
+def test_onnx_model(model_path: str):
+ from onnxruntime_extensions import get_library_path
+
+ so = ort.SessionOptions()
+ so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_EXTENDED
+
+ # register the custom operators for the image decode/encode pre/post processing provided by onnxruntime-extensions
+ # with onnxruntime. if we do not do this we'll get an error on model load about the operators not being found.
+ ortext_lib_path = get_library_path()
+ so.register_custom_ops_library(ortext_lib_path)
+ inference_session = ort.InferenceSession(model_path, so)
+
+ test_image_path = '/home/maary/Pictures/FoIya1TaAAEkhMl.resized.png'
+ test_image_bytes = np.fromfile(test_image_path, dtype=np.uint8)
+ outputs = inference_session.run(['image_out'], {'image': test_image_bytes})
+ upsized_image_bytes = outputs[0]
+
+ original_img = Image.open(io.BytesIO(test_image_bytes))
+ updated_img = Image.open(io.BytesIO(upsized_image_bytes))
+
+ # centered crop of original to match the area processed
+ def _center_crop_to_square(img: Image):
+ if img.height != img.width:
+ target_size = img.width if img.width < img.height else img.height
+ w_start = int(np.floor((img.width - target_size) / 2))
+ w_end = w_start + target_size
+ h_start = int(np.floor((img.height - target_size) / 2))
+ h_end = h_start + target_size
+
+ return img.crop((w_start, h_start, w_end, h_end))
+ else:
+ return img
+
+ original_cropped_img = _center_crop_to_square(original_img)
+
+ new_width, new_height = updated_img.size
+
+ # create a side-by-side image with both.
+ # resize the original to the model input size followed by the output size so the processing is more equivalent
+ original_cropped_img = original_cropped_img.resize((240, 240))
+ resized_orig_img = original_cropped_img.resize((new_width, new_height))
+
+ combined = Image.new('RGB', (new_width * 2, new_height))
+ combined.paste(resized_orig_img, (0, 0))
+ combined.paste(updated_img, (new_width, 0))
+ combined.show('Original resized vs Super Resolution resized')
+ # combined.save('Original resized vs Super Resolution resized.png', format='PNG')
+
+
+def main():
+ onnx_model_path = Path('realesrgan-2k.onnx')
+ if not onnx_model_path.exists():
+ return
+
+ assert onnx_model_path.exists()
+
+ print("Adding pre/post processing to ONNX model...")
+ output_model_path = str(onnx_model_path).replace(".onnx", "_with_pre_post_processing.onnx")
+ add_pre_post_processing(str(onnx_model_path), output_model_path)
+
+ print("Testing ONNX model with pre/post processing")
+ test_onnx_model(output_model_path)
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/pytorch2onnx.py b/scripts/pytorch2onnx.py
new file mode 100644
index 0000000..3f849f7
--- /dev/null
+++ b/scripts/pytorch2onnx.py
@@ -0,0 +1,52 @@
+import argparse
+import torch
+import torch.onnx
+from basicsr.archs.rrdbnet_arch import RRDBNet
+
+def main(args):
+ # An instance of the model
+ model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2)
+ # RealESRGAN_x2plus.pth
+ # model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2)
+ # RealESRGAN_x4plus.pth
+ # model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4)
+ # RealESRGAN_x4plus_anime_6B
+ # model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=6, num_grow_ch=32, scale=2)
+
+ if args.params:
+ keyname = 'params'
+ else:
+ keyname = 'params_ema'
+ model.load_state_dict(torch.load(args.input)['params_ema'])
+ # set the train mode to false since we will only run the forward pass.
+ model.train(False)
+ model.cpu().eval()
+
+ # An example input
+ x = torch.rand(1, 3, 64, 64, requires_grad=True)
+ # Export the model
+ with torch.no_grad():
+ torch_out = torch.onnx._export(model, x, args.output, opset_version=18, dynamic_axes={
+ 'input': {
+ 0: 'batch',
+ 2: 'height',
+ 3: 'width'}, # shape(1,3,640,640)
+ 'output': {
+ 0: 'batch',
+ 1: 'anchors'} # shape(1,25200,85)
+ }, export_params=True,
+ input_names=['input'], # the model's input names
+ output_names=['output'])
+ print(torch_out.shape)
+
+
+if __name__ == '__main__':
+ """Convert pytorch model to onnx models"""
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '--input', type=str, default='experiments/pretrained_models/RealESRGAN_x2plus.pth', help='Input model path')
+ parser.add_argument('--output', type=str, default='realesrgan-2k.onnx', help='Output onnx path')
+ parser.add_argument('--params', action='store_false', help='Use params instead of params_ema')
+ args = parser.parse_args()
+
+ main(args)
\ No newline at end of file