Hi guys I want to apply the crop function to a zo...
# compose-android
a
Hi guys I want to apply the crop function to a zoomable image. The problem is that I lose the crop shape when the image scales up. So my question is, how can I maintain the crop shape without it being affected by zooming, like in the following video?
🧵 1
Here is my code and its result, I draw the crop shape inside
drawWithContent
modifier:
Copy code
Image(
    modifier = Modifier
        .fillMaxSize()
        .aspectRatio(1f)
        .onGloballyPositioned { coordinates ->
            imageSize = Size(
                width = coordinates.size.width.toFloat(),
                height = coordinates.size.height.toFloat()
            )
        }
        .graphicsLayer(
            scaleX = scale,
            scaleY = scale,
            translationX = offset.x,
            translationY = offset.y
        )
        .drawWithContent {
            drawContent()

            if (isCropping) {

                val cropPath = Path().apply {
                    val rect = Rect(
                        cropCorners.topLeft,
                        cropCorners.bottomRight
                    )
                    addRect(rect)
                }

                clipPath(cropPath, clipOp = ClipOp.Difference) {
                    drawRect(dimAnimatedColor) // Dim the rest of the image
                }

                // Draw the cropping rectangle (clear)
                drawCropRectangle(
                    topLeft = cropCorners.topLeft,
                    topRight = cropCorners.topRight,
                    bottomLeft = cropCorners.bottomLeft,
                    bottomRight = cropCorners.bottomRight,
                    isCustomRation = selectedRatio == AspectRatio.Custom
                )
            }
        }
        .transformable(transformState)
        .conditional(isCropping && selectedRatio == AspectRatio.Custom) {
            pointerInput(isCropping && selectedRatio == AspectRatio.Custom) {
                detectDragGesturesAfterLongPress(
                    onDragStart = { offset ->
                        isDragging = true
                        draggingCorner = when {
                            offset.isNear(cropCorners.topLeft) -> Corner.TopLeft
                            offset.isNear(cropCorners.topRight) -> Corner.TopRight
                            offset.isNear(cropCorners.bottomLeft) -> Corner.BottomLeft
                            offset.isNear(cropCorners.bottomRight) -> Corner.BottomRight
                            else -> null
                        }

                        draggingCenter = draggingCorner == null && Rect(
                            cropCorners.topLeft, cropCorners.bottomRight
                        ).contains(offset)
                    },
                    onDrag = { change, dragAmount ->
                        change.consume()

                        val imageWidth = size.width
                        val imageHeight = size.height
                        val minCropSize = 50f

                        cropCorners = when (draggingCorner) {
                            Corner.TopLeft -> {
                                val newTopLeft =
                                    cropCorners.topLeft + dragAmount
                                cropCorners.copy(
                                    topLeft = Offset(
                                        x = newTopLeft.x.coerceIn(
                                            0f,
                                            cropCorners.bottomRight.x - minCropSize
                                        ),
                                        y = newTopLeft.y.coerceIn(
                                            0f,
                                            cropCorners.bottomRight.y - minCropSize
                                        )
                                    ),
                                    topRight = cropCorners.topRight.copy(
                                        y = newTopLeft.y
                                    ),
                                    bottomLeft = cropCorners.bottomLeft.copy(
                                        x = newTopLeft.x
                                    )
                                )
                            }

                            Corner.TopRight -> {
                                val newTopRight =
                                    cropCorners.topRight + dragAmount
                                cropCorners.copy(
                                    topRight = Offset(
                                        x = newTopRight.x.coerceIn(
                                            cropCorners.topLeft.x + minCropSize,
                                            imageWidth.toFloat()
                                        ),
                                        y = newTopRight.y.coerceIn(
                                            0f,
                                            cropCorners.bottomLeft.y - minCropSize
                                        )
                                    ),
                                    topLeft = cropCorners.topLeft.copy(y = newTopRight.y),
                                    bottomRight = cropCorners.bottomRight.copy(
                                        x = newTopRight.x
                                    )
                                )
                            }

                            Corner.BottomLeft -> {
                                val newBottomLeft =
                                    cropCorners.bottomLeft + dragAmount
                                cropCorners.copy(
                                    bottomLeft = Offset(
                                        x = newBottomLeft.x.coerceIn(
                                            0f,
                                            cropCorners.bottomRight.x - minCropSize
                                        ),
                                        y = newBottomLeft.y.coerceIn(
                                            cropCorners.topLeft.y + minCropSize,
                                            imageHeight.toFloat()
                                        )
                                    ),
                                    topLeft = cropCorners.topLeft.copy(x = newBottomLeft.x),
                                    bottomRight = cropCorners.bottomRight.copy(
                                        y = newBottomLeft.y
                                    )
                                )
                            }

                            Corner.BottomRight -> {
                                val newBottomRight =
                                    cropCorners.bottomRight + dragAmount
                                cropCorners.copy(
                                    bottomRight = Offset(
                                        x = newBottomRight.x.coerceIn(
                                            cropCorners.bottomLeft.x + minCropSize,
                                            imageWidth.toFloat()
                                        ),
                                        y = newBottomRight.y.coerceIn(
                                            cropCorners.topRight.y + minCropSize,
                                            imageHeight.toFloat()
                                        )
                                    ),
                                    topRight = cropCorners.topRight.copy(
                                        x = newBottomRight.x
                                    ),
                                    bottomLeft = cropCorners.bottomLeft.copy(
                                        y = newBottomRight.y
                                    )
                                )
                            }

                            null -> if (draggingCenter) {
                                val newTopLeft =
                                    cropCorners.topLeft + dragAmount
                                val newBottomRight =
                                    cropCorners.bottomRight + dragAmount

                                if (newTopLeft.x >= 0 && newBottomRight.x <= imageWidth && newTopLeft.y >= 0 && newBottomRight.y <= imageHeight) {
                                    cropCorners.copy(
                                        topLeft = newTopLeft,
                                        topRight = cropCorners.topRight + dragAmount,
                                        bottomLeft = cropCorners.bottomLeft + dragAmount,
                                        bottomRight = newBottomRight
                                    )
                                } else {
                                    cropCorners // Return the current cropCorners if out of bounds
                                }
                            } else cropCorners
                        }
                    },
                    onDragEnd = {
                        isDragging = false
                        draggingCorner = null
                        draggingCenter = false

                        mainViewModel.setCropCorners(cropCorners)
                    }
                )
            }
        },
    bitmap = imageBitmap,
    contentScale = ContentScale.Crop,
    contentDescription = null
)
s
You probably need to place your crop overlay composable above the zoomable composable that contains the image. To get the correct crop rectangle on the bitmap, apply the inverted zoom/pan transformation to your crop rectangle.
👍 1
c
just came here to say that this looks super cool!
s
Agree. The original crop design stands out. I suppose that’s the famous “iOS design that needs to be replicated” 😅 But it’s definitely interesting.
a
Yah it's really impressive, and I ended up with something similar but not accurate like the IOS version 🫤
👍 1
s
Looking great, imo. You can still implement a blur effect using Haze, you just need to cut out the hole inside the blur.
🙏 1
c
Not sure if helpful, but I know @saket has a great library for zoomable photos where he hit a BUNCH of really interesting issues just getting that to work properly. just in case you're curious to see how it was implemented https://github.com/saket/telephoto
👍 1
💚 1
💡 1
s
I'd love to support image croppers as a use case for telephoto!
😍 1
@AmrJyniat let me know if you run into any issues with telephoto or have any feedback
❤️ 1