@@ -21,14 +21,16 @@ import androidx.compose.ui.Alignment
2121import androidx.compose.ui.Modifier
2222import androidx.compose.ui.draw.rotate
2323import androidx.compose.ui.geometry.Offset
24+ import androidx.compose.ui.graphics.ImageBitmap
2425import androidx.compose.ui.layout.ContentScale
2526import androidx.compose.ui.res.painterResource
26- import androidx.compose.ui.unit.IntOffset
27+ import androidx.compose.ui.unit.round
2728import coil3.compose.SubcomposeAsyncImage
2829import io.bashpsk.emptylibs.gestureui.transform.TransformableGesturesState
2930import io.bashpsk.emptylibs.gestureui.transform.rememberTransformableGesturesState
3031import io.bashpsk.emptylibs.gestureui.transform.transformableGestures
3132import io.bashpsk.emptylibs.imageview.R
33+ import io.bashpsk.emptylibs.imageview.tile.TileImageView
3234import io.bashpsk.emptylibs.jetpackui.layout.ZoomableLayout
3335import 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
5860fun 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