Skip to content

Commit b6c6fb5

Browse files
committed
upscaler page drag & zoom
1 parent 0f718a0 commit b6c6fb5

File tree

4 files changed

+144
-30
lines changed

4 files changed

+144
-30
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
minSdk = 28
1313
// minSdk = 31
1414
targetSdk = 36
15-
versionCode = 49
16-
versionName = "2.1.1"
15+
versionCode = 50
16+
versionName = "2.1.2"
1717

1818
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1919
vectorDrawables {

app/src/main/java/io/github/xororz/localdream/data/Model.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ class ModelRepository(private val context: Context) {
643643
approximateSize = "Custom",
644644
isDownloaded = true,
645645
isPartiallyDownloaded = false,
646-
defaultPrompt = "masterpiece, best quality, flowers,",
646+
defaultPrompt = "masterpiece, best quality, a cat sat on a mat,",
647647
defaultNegativePrompt = "lowres, bad anatomy, bad hands, missing fingers, extra fingers, bad arms, missing legs, missing arms, poorly drawn face, bad face, fused face, cloned face, three crus, fused feet, fused thigh, extra crus, ugly fingers, horn, huge eyes, worst face, 2girl, long fingers, disconnected limbs,",
648648
runOnCpu = !isNpu,
649649
useCpuClip = true,

app/src/main/java/io/github/xororz/localdream/ui/screens/ModelRunScreen.kt

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,25 +1408,26 @@ fun ModelRunScreen(
14081408
shape = RoundedCornerShape(8.dp),
14091409
) {
14101410
Box {
1411-
if (croppedBitmap != null) {
1412-
Image(
1413-
bitmap = croppedBitmap!!.asImageBitmap(),
1411+
croppedBitmap?.let { bitmap ->
1412+
AsyncImage(
1413+
model = ImageRequest.Builder(LocalContext.current)
1414+
.data(bitmap)
1415+
.crossfade(true)
1416+
.build(),
14141417
contentDescription = "Cropped Image",
14151418
modifier = Modifier.fillMaxSize()
14161419
)
1417-
} else {
1418-
selectedImageUri?.let { uri ->
1419-
AsyncImage(
1420-
model = ImageRequest.Builder(
1421-
LocalContext.current
1422-
)
1423-
.data(uri)
1424-
.crossfade(true)
1425-
.build(),
1426-
contentDescription = "Selected Image",
1427-
modifier = Modifier.fillMaxSize()
1420+
} ?: selectedImageUri?.let { uri ->
1421+
AsyncImage(
1422+
model = ImageRequest.Builder(
1423+
LocalContext.current
14281424
)
1429-
}
1425+
.data(uri)
1426+
.crossfade(true)
1427+
.build(),
1428+
contentDescription = "Selected Image",
1429+
modifier = Modifier.fillMaxSize()
1430+
)
14301431
}
14311432
IconButton(
14321433
onClick = {
@@ -1505,8 +1506,11 @@ fun ModelRunScreen(
15051506
) {
15061507
Box {
15071508
maskBitmap?.let { mb ->
1508-
Image(
1509-
bitmap = mb.asImageBitmap(),
1509+
AsyncImage(
1510+
model = ImageRequest.Builder(LocalContext.current)
1511+
.data(mb)
1512+
.crossfade(true)
1513+
.build(),
15101514
contentDescription = "Mask Image",
15111515
modifier = Modifier.fillMaxSize()
15121516
)
@@ -1702,8 +1706,12 @@ fun ModelRunScreen(
17021706
shadowElevation = 4.dp
17031707
) {
17041708
currentBitmap?.let { bitmap ->
1705-
Image(
1706-
bitmap = bitmap.asImageBitmap(),
1709+
AsyncImage(
1710+
model = ImageRequest.Builder(LocalContext.current)
1711+
.data(bitmap)
1712+
.size(coil.size.Size.ORIGINAL)
1713+
.crossfade(true)
1714+
.build(),
17071715
contentDescription = "generated image",
17081716
modifier = Modifier.fillMaxSize()
17091717
)
@@ -2013,8 +2021,12 @@ fun ModelRunScreen(
20132021
)
20142022
}
20152023
) {
2016-
Image(
2017-
bitmap = currentBitmap!!.asImageBitmap(),
2024+
AsyncImage(
2025+
model = ImageRequest.Builder(LocalContext.current)
2026+
.data(currentBitmap!!)
2027+
.size(coil.size.Size.ORIGINAL)
2028+
.crossfade(true)
2029+
.build(),
20182030
contentDescription = "preview image",
20192031
modifier = Modifier
20202032
.fillMaxWidth()

app/src/main/java/io/github/xororz/localdream/ui/screens/UpscaleScreen.kt

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,17 @@ import androidx.compose.runtime.*
3434
import androidx.compose.ui.Alignment
3535
import androidx.compose.ui.Modifier
3636
import androidx.compose.ui.draw.clip
37+
import androidx.compose.ui.geometry.Offset
3738
import androidx.compose.ui.graphics.Color
3839
import androidx.compose.ui.graphics.PathEffect
40+
import androidx.compose.ui.graphics.graphicsLayer
41+
import androidx.compose.ui.input.pointer.pointerInput
3942
import androidx.compose.ui.layout.ContentScale
4043
import androidx.compose.ui.platform.LocalContext
44+
import androidx.compose.foundation.gestures.detectTransformGestures
4145
import androidx.compose.ui.res.stringResource
46+
import coil.request.ImageRequest
47+
import coil.size.Size
4248
import androidx.compose.ui.text.font.FontFamily
4349
import androidx.compose.ui.unit.dp
4450
import androidx.compose.ui.unit.sp
@@ -83,6 +89,10 @@ fun UpscaleScreen(
8389
var backendLogs by remember { mutableStateOf<List<String>>(emptyList()) }
8490
var currentLog by remember { mutableStateOf("") }
8591

92+
var sharedScale by remember { mutableFloatStateOf(1f) }
93+
var sharedOffsetX by remember { mutableFloatStateOf(0f) }
94+
var sharedOffsetY by remember { mutableFloatStateOf(0f) }
95+
8696
var showUpscalerDialog by remember { mutableStateOf(false) }
8797
val upscalerRepository = remember { UpscalerRepository(context) }
8898
val upscalerPreferences =
@@ -116,6 +126,11 @@ fun UpscaleScreen(
116126
} else {
117127
selectedImageUri = it
118128
selectedBitmap = bitmap
129+
withContext(Dispatchers.Main) {
130+
sharedScale = 1f
131+
sharedOffsetX = 0f
132+
sharedOffsetY = 0f
133+
}
119134
}
120135
}
121136
} catch (e: Exception) {
@@ -356,13 +371,21 @@ fun UpscaleScreen(
356371
)
357372
}
358373
} else {
359-
AsyncImage(
360-
model = selectedImageUri,
374+
ZoomableImage(
375+
imageUri = selectedImageUri,
361376
contentDescription = stringResource(R.string.selected_image),
362377
modifier = Modifier
363378
.fillMaxSize()
364379
.padding(8.dp),
365-
contentScale = ContentScale.Fit
380+
scale = sharedScale,
381+
offsetX = sharedOffsetX,
382+
offsetY = sharedOffsetY,
383+
onTransform = { newScale, newOffsetX, newOffsetY ->
384+
sharedScale = newScale
385+
sharedOffsetX = newOffsetX
386+
sharedOffsetY = newOffsetY
387+
},
388+
useOriginalSize = true
366389
)
367390
}
368391

@@ -371,6 +394,9 @@ fun UpscaleScreen(
371394
onClick = {
372395
selectedImageUri = null
373396
selectedBitmap = null
397+
sharedScale = 1f
398+
sharedOffsetX = 0f
399+
sharedOffsetY = 0f
374400
},
375401
modifier = Modifier
376402
.align(Alignment.TopEnd)
@@ -439,13 +465,21 @@ fun UpscaleScreen(
439465
shape = RoundedCornerShape(12.dp)
440466
)
441467
) {
442-
AsyncImage(
443-
model = upscaledImageUri,
468+
ZoomableImage(
469+
imageUri = upscaledImageUri,
444470
contentDescription = stringResource(R.string.upscaled_image),
445471
modifier = Modifier
446472
.fillMaxSize()
447473
.padding(8.dp),
448-
contentScale = ContentScale.Fit
474+
scale = sharedScale,
475+
offsetX = sharedOffsetX,
476+
offsetY = sharedOffsetY,
477+
onTransform = { newScale, newOffsetX, newOffsetY ->
478+
sharedScale = newScale
479+
sharedOffsetX = newOffsetX
480+
sharedOffsetY = newOffsetY
481+
},
482+
useOriginalSize = true
449483
)
450484

451485
FilledTonalIconButton(
@@ -694,3 +728,71 @@ fun prepareRuntimeDir(context: Context): File {
694728

695729
return runtimeDir
696730
}
731+
732+
@Composable
733+
fun ZoomableImage(
734+
imageUri: Uri?,
735+
contentDescription: String?,
736+
modifier: Modifier = Modifier,
737+
scale: Float,
738+
offsetX: Float,
739+
offsetY: Float,
740+
onTransform: (scale: Float, offsetX: Float, offsetY: Float) -> Unit,
741+
useOriginalSize: Boolean = false
742+
) {
743+
val context = LocalContext.current
744+
745+
var currentScale by remember { mutableFloatStateOf(scale) }
746+
var currentOffsetX by remember { mutableFloatStateOf(offsetX) }
747+
var currentOffsetY by remember { mutableFloatStateOf(offsetY) }
748+
749+
LaunchedEffect(scale, offsetX, offsetY) {
750+
currentScale = scale
751+
currentOffsetX = offsetX
752+
currentOffsetY = offsetY
753+
}
754+
755+
val imageRequest = remember(imageUri, useOriginalSize) {
756+
ImageRequest.Builder(context)
757+
.data(imageUri)
758+
.apply {
759+
if (useOriginalSize) {
760+
size(Size.ORIGINAL)
761+
memoryCacheKey(imageUri.toString() + "_original")
762+
}
763+
}
764+
.build()
765+
}
766+
767+
Box(
768+
modifier = modifier
769+
.pointerInput(Unit) {
770+
detectTransformGestures { centroid, pan, zoom, rotation ->
771+
val newScale = (currentScale * zoom).coerceIn(1f, 5f)
772+
773+
val newOffsetX = currentOffsetX + pan.x
774+
val newOffsetY = currentOffsetY + pan.y
775+
776+
currentScale = newScale
777+
currentOffsetX = newOffsetX
778+
currentOffsetY = newOffsetY
779+
780+
onTransform(newScale, newOffsetX, newOffsetY)
781+
}
782+
}
783+
) {
784+
AsyncImage(
785+
model = imageRequest,
786+
contentDescription = contentDescription,
787+
modifier = Modifier
788+
.fillMaxSize()
789+
.graphicsLayer(
790+
scaleX = currentScale,
791+
scaleY = currentScale,
792+
translationX = currentOffsetX,
793+
translationY = currentOffsetY
794+
),
795+
contentScale = ContentScale.Fit
796+
)
797+
}
798+
}

0 commit comments

Comments
 (0)