saket / telephoto

Building blocks for designing media experiences in Compose UI
https://saket.github.io/telephoto/
Apache License 2.0
869 stars 28 forks source link

Ability to view image edges in the center of the screen #63

Open gmikhail opened 4 months ago

gmikhail commented 4 months ago

I want to achieve this behavior:

https://github.com/saket/telephoto/assets/4838367/1d29b3d5-0e43-45fe-9b93-4af06923cf3b

How can I do it?

In Subsampling Scale Image View I only have to setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_CENTER) for this.

saket commented 4 months ago

I've been wanting to do this, but haven't been able to find time yet. Wanna help me out? 🙂

We can add a PanSpec class along the lines of ZoomSpec.

gmikhail commented 4 months ago

Next week I'll try to see how it's done in Subsampling Scale Image View.

I haven't looked at the Telephoto source code yet, so I'm not sure how long it will take to implement this functionality.

gmikhail commented 4 months ago

I made a working implementation of this feature using Coil. The implementation itself is quite simple. We calculate the available boundaries and then constrain the offset within them.

var imageWidth by remember { mutableIntStateOf(0) }
var imageHeight by remember { mutableIntStateOf(0) }
var zoom by remember { mutableFloatStateOf(1f) }
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
val minScale = 1f
val maxScale = 5f
// Gesture container
Box(
    modifier = Modifier
        .fillMaxSize()
        .checkerboardBackground()
        .pointerInput(Unit) {
            detectTransformGestures(
                onGesture = { _, pan, gestureZoom, _ ->
                    zoom = (zoom * gestureZoom).coerceIn(minScale, maxScale)
                    // --- PAN_LIMIT_CENTER implementation start ---
                    val imageWidthHalf = (imageWidth / 2f) * zoom
                    val imageHeightHalf = (imageHeight / 2f) * zoom
                    val newOffsetX = offsetX + pan.x
                    val newOffsetY = offsetY + pan.y
                    offsetX = newOffsetX.coerceIn(-imageWidthHalf, imageWidthHalf)
                    offsetY = newOffsetY.coerceIn(-imageHeightHalf, imageHeightHalf)
                    // --- PAN_LIMIT_CENTER implementation end ---
                    LogUtil.log(TAG, "offsetX = $offsetX, offsetY = $offsetY")
                }
            )
        }
    contentAlignment = Alignment.Center
) {
    // Image
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(uri)
            .listener(
                onSuccess = { _, result ->
                    val bitmap = result.drawable.toBitmap()
                    imageWidth = bitmap.width
                    imageHeight = bitmap.height
                    // TODO work with bitmap
                }
            )
            .build(),
        contentDescription = null,
        modifier = Modifier
            .graphicsLayer(
                scaleX = zoom,
                scaleY = zoom,
                translationX = offsetX,
                translationY = offsetY,
            )
    )
}

Could you help with integration into Telephoto?