androidandroid-jetpack-composeandroid-compose-image

How to transform Image Composable to match 3 touch points in Compose


I am currently playing with my old Instant Lab device and I am trying to recreate parts of the old app in jetpack compose.

A feature of the device is to detect 3 touch points on the screen in order to create the border of the image to display.

I was able to dectect the 3 touche points using jetpack compose and find the coordinate (x, y) of each touch points :

enter image description here

Now I would like to display my image between these touch points. I know that I need to use the Image Composable in order to display. But I do not know how to apply the right transformation in order to display this composable between these 3 points using rotation and absolute position (?).

Expected result:

enter image description here

Thank you in advance for your help.

Edit:

I tried using a custom shape I apply to a surface with the following composable :

 @Composable
  private fun Exposing(pointersCoordinates: PointersCoordinates)
  {   
    val exposureShape = GenericShape { _, _ ->
      moveTo(pointersCoordinates.xTopLeft(), pointersCoordinates.yTopLeft())
      lineTo(pointersCoordinates.xTopRight(), pointersCoordinates.yTopRight())
      lineTo(pointersCoordinates.xBottomRight(), pointersCoordinates.yBottomRight())
      lineTo(pointersCoordinates.xBottomLeft(), pointersCoordinates.yBottomLeft())
    }

    Box(
      modifier = Modifier
        .fillMaxSize()
        .background(Color.Black)
    ) {
      Surface(
        modifier = Modifier.fillMaxSize(),
        shape = exposureShape,
        color = Color.Yellow,
        border = BorderStroke(1.dp, Color.Red)
      ) {
        Image(
          modifier = Modifier.fillMaxSize(),
          bitmap = viewModel?.bitmap?.asImageBitmap() ?: ImageBitmap(0, 0),
          contentDescription = "photo"
        )
      }
    }
  }

It's working correctly :) But is it the best way to do it?


Solution

  • Since you are able to get a Rect from touch points you can use Canvas or Modifier.drawWithContent{}.

    Clip image and draw

    If you wish to clip your image based on rectangle you can check out this answer. Whit BlendModes you can clip not only to rectangle or shapes that easy to create but shapes you get from web or image

    How to clip or cut a Composable?

    Another approach for clipping is using clip() function of DrawScope, this approach only clips to a Rect.

    Also you can use Modifier.clip() with custom shape to clip it as required as in this answer

    Draw without clippin

    If you don't want to clip your image draw whole image insider rect you can do it with dstOffset with dstSize or translate with dstSize

    @Composable
    private fun DrawImageWithTouchSample() {
    
        val rect = Rect(topLeft = Offset(100f, 100f), bottomRight = Offset(1000f, 1000f))
    
        val modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectTapGestures {
                    // Tap here to get points
                }
            }
        val image = ImageBitmap.imageResource(id = R.drawable.landscape5)
        Canvas(modifier = modifier) {
    
            // Clip image
            clipRect(
                left = rect.left,
                top = rect.top,
                right = rect.right,
                bottom = rect.bottom
    
            ){
                drawImage(image = image)
            }
            // Not clipping image
    //        drawImage(
    //            image = image,
    //            dstOffset = IntOffset(x = rect.left.toInt(), y = rect.top.toInt()),
    //            dstSize = IntSize(width = rect.width.toInt(), height = rect.height.toInt())
    //        )
    //
    
            translate(
                left = rect.left,
                top = rect.top + 1000
            ){
                drawImage(
                    image = image,
                    dstSize = IntSize(width = rect.width.toInt(), height = rect.height.toInt())
                )
            }
        }
    }
    

    Image on top is clipped with clipRect while second one is scaled to fit inside rect because of dstSize

    enter image description here