Skip to content

Commit 1f21d7b

Browse files
committed
Implement tile image for TransformImageView.kt
1 parent 6592da2 commit 1f21d7b

2 files changed

Lines changed: 123 additions & 12 deletions

File tree

app/src/main/kotlin/io/bashpsk/emptylibs/TransformImageScreen.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import androidx.compose.runtime.remember
1414
import androidx.compose.runtime.setValue
1515
import androidx.compose.ui.Alignment
1616
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.graphics.ImageBitmap
18+
import androidx.compose.ui.res.imageResource
1719
import io.bashpsk.emptylibs.gestureui.transform.rememberTransformableGesturesState
1820
import io.bashpsk.emptylibs.imageview.transform.TransformImageView
1921
import kotlinx.collections.immutable.persistentListOf
@@ -22,6 +24,7 @@ import kotlinx.collections.immutable.toImmutableList
2224
@Composable
2325
fun TransformImageScreen() {
2426

27+
val imageBitmap = ImageBitmap.imageResource(R.drawable.wallpaper_large)
2528
var simpleList by remember { mutableStateOf(persistentListOf<Any?>()) }
2629

2730
val tooLongList = (0..333).map { simpleList }.flatten().toImmutableList()
@@ -50,18 +53,19 @@ fun TransformImageScreen() {
5053
verticalArrangement = Arrangement.Center
5154
) {
5255

53-
TransformImageView(
56+
/*TransformImageView(
5457
modifier = Modifier.fillMaxWidth(),
5558
state = rememberTransformableGesturesState(enableRotation = true),
5659
imageModelList = simpleList,
5760
initialImage = simpleList.firstOrNull()
58-
)
61+
)*/
5962

60-
/*TransformImageView(
63+
TransformImageView(
6164
modifier = Modifier.fillMaxWidth(),
6265
state = rememberTransformableGesturesState(enableRotation = true),
63-
imageModel = simpleList.firstOrNull()
64-
)*/
66+
imageModel = imageBitmap,
67+
tileSize = 256
68+
)
6569
}
6670
}
6771
}

image-view/src/main/kotlin/io/bashpsk/emptylibs/imageview/transform/TransformImageView.kt

Lines changed: 114 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ import androidx.compose.ui.Alignment
2121
import androidx.compose.ui.Modifier
2222
import androidx.compose.ui.draw.rotate
2323
import androidx.compose.ui.geometry.Offset
24+
import androidx.compose.ui.graphics.ImageBitmap
2425
import androidx.compose.ui.layout.ContentScale
2526
import androidx.compose.ui.res.painterResource
26-
import androidx.compose.ui.unit.IntOffset
27+
import androidx.compose.ui.unit.round
2728
import coil3.compose.SubcomposeAsyncImage
2829
import io.bashpsk.emptylibs.gestureui.transform.TransformableGesturesState
2930
import io.bashpsk.emptylibs.gestureui.transform.rememberTransformableGesturesState
3031
import io.bashpsk.emptylibs.gestureui.transform.transformableGestures
3132
import io.bashpsk.emptylibs.imageview.R
33+
import io.bashpsk.emptylibs.imageview.tile.TileImageView
3234
import io.bashpsk.emptylibs.jetpackui.layout.ZoomableLayout
3335
import kotlinx.collections.immutable.ImmutableList
3436

@@ -48,11 +50,11 @@ import kotlinx.collections.immutable.ImmutableList
4850
*
4951
* @param modifier The [Modifier] to be applied to the composable.
5052
* @param state The [TransformableGesturesState] that holds and manages the current transformation
51-
* state (zoom, pan, rotation). A default state is remembered if not provided.
53+
* state (zoom, pan, rotation). A default state is remembered if not provided.
5254
* @param imageModel The image model to be displayed. This can be a URL, a local
53-
* file path, or any other type supported by the image loading library (Coil).
55+
* file path, or any other type supported by the image loading library (Coil).
5456
* @param contentScale The scaling to be applied to the image to fit within the composable's
55-
* bounds. Defaults to [ContentScale.Fit].
57+
* bounds. Defaults to [ContentScale.Fit].
5658
*/
5759
@Composable
5860
fun TransformImageView(
@@ -99,6 +101,64 @@ fun TransformImageView(
99101
}
100102
}
101103

104+
/**
105+
* A Composable that displays an [ImageBitmap] with support for transformations like
106+
* zoom, pan, and rotation. This version uses a tiled rendering approach via [TileImageView]
107+
* for optimized performance when handling large high-resolution bitmaps.
108+
*
109+
* This view allows users to interact with the bitmap using gestures:
110+
* - **Pinch-to-zoom:** Use two fingers to zoom in and out.
111+
* - **Double-tap to zoom:** Quickly zoom to predefined levels.
112+
* - **Pan:** Drag with one finger to move around a zoomed-in image.
113+
* - **Rotate:** Twist with two fingers to rotate the image.
114+
*
115+
* This is a specialized overload for local [ImageBitmap] objects. For loading remote
116+
* URLs or other media types, use the overloads that accept `imageModel` or `imageModelList`.
117+
*
118+
* @param modifier The [Modifier] to be applied to the composable.
119+
* @param state The [TransformableGesturesState] that holds and manages the current transformation
120+
* state (zoom, pan, rotation). A default state is remembered if not provided.
121+
* @param imageModel The [ImageBitmap] to be displayed.
122+
* @param contentScale The scaling to be applied to the image to fit within the composable's
123+
* bounds. Defaults to [ContentScale.Fit].
124+
* @param tileSize The size (in pixels) of the individual tiles used to render the image.
125+
* Larger tiles use more memory but may result in fewer draw calls. Defaults to 512.
126+
* @param onClick A lambda to be invoked when a single tap is detected on the image.
127+
* @param onLongClick A lambda to be invoked when a long press is detected on the image.
128+
*/
129+
@Composable
130+
fun TransformImageView(
131+
modifier: Modifier = Modifier,
132+
state: TransformableGesturesState = rememberTransformableGesturesState(),
133+
imageModel: ImageBitmap,
134+
contentScale: ContentScale = ContentScale.Fit,
135+
tileSize: Int = 512,
136+
onClick: (offset: Offset) -> Unit = {},
137+
onLongClick: (offset: Offset) -> Unit = {}
138+
) {
139+
140+
LaunchedEffect(imageModel) {
141+
142+
state.resetAllValues()
143+
}
144+
145+
TransformImageViewLayout(
146+
modifier = modifier,
147+
state = state,
148+
onClick = onClick,
149+
onLongClick = onLongClick
150+
) {
151+
152+
ImageView(
153+
modifier = Modifier.fillMaxSize(),
154+
state = state,
155+
model = imageModel,
156+
contentScale = contentScale,
157+
tileSize = tileSize
158+
)
159+
}
160+
}
161+
102162
/**
103163
* A Composable that displays a swipeable gallery of images with support for
104164
* transformations like zoom, pan, and rotation.
@@ -273,9 +333,7 @@ private fun ImageView(
273333
}
274334
) {
275335

276-
val layoutPosition by remember(state.position) {
277-
derivedStateOf { IntOffset(state.position.x.toInt(), state.position.y.toInt()) }
278-
}
336+
val layoutPosition by remember(state.position) { derivedStateOf { state.position.round() } }
279337

280338
ZoomableLayout(
281339
modifier = modifier
@@ -320,4 +378,53 @@ private fun ImageView(
320378
contentDescription = "Image View"
321379
)
322380
}
381+
}
382+
383+
/**
384+
* A private composable that renders a [ImageBitmap] using a tiled approach for optimized
385+
* performance at high zoom levels.
386+
*
387+
* This version of `ImageView` is specifically designed for local [ImageBitmap] resources
388+
* and utilizes [TileImageView] to handle the rendering of image segments. It applies
389+
* transformations such as zoom, pan, and rotation managed by the provided
390+
* [TransformableGesturesState].
391+
*
392+
* @param modifier The [Modifier] to be applied to this composable.
393+
* @param state The [TransformableGesturesState] that holds the current transformation values
394+
* (zoom, position, rotation).
395+
* @param model The [ImageBitmap] to be displayed.
396+
* @param contentScale The scaling to be applied to the image within its bounds.
397+
* Defaults to [ContentScale.Fit].
398+
* @param tileSize The size (in pixels) of the individual tiles used to render the image.
399+
* Defaults to 512.
400+
*/
401+
@Composable
402+
private fun ImageView(
403+
modifier: Modifier = Modifier,
404+
state: TransformableGesturesState,
405+
model: ImageBitmap,
406+
contentScale: ContentScale = ContentScale.Fit,
407+
tileSize: Int = 512
408+
) {
409+
410+
val layoutPosition by remember(state.position) { derivedStateOf { state.position.round() } }
411+
412+
ZoomableLayout(
413+
modifier = modifier
414+
.fillMaxWidth()
415+
.offset { layoutPosition }
416+
.rotate(degrees = state.rotation),
417+
zoomScale = state.zoom
418+
) {
419+
420+
TileImageView(
421+
modifier = Modifier.fillMaxWidth(),
422+
imageBitmap = model,
423+
contentScale = contentScale,
424+
tileSize = tileSize,
425+
zoomScale = state.zoom,
426+
centerPosition = layoutPosition,
427+
viewportSize = state.boundSize
428+
)
429+
}
323430
}

0 commit comments

Comments
 (0)