1

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?

rolandl
  • 1,769
  • 1
  • 25
  • 48
  • Do you create a convex rectangle by generating another coordinate based on existing three points or create a triangle and draw image inside a triangle? Would you mind showing a gif from your app of what you wish to achieve? – Thracian Sep 03 '22 at 05:04
  • Hello @Thracian! Love your work on github :D I have added an image of the expected result :) – rolandl Sep 03 '22 at 19:38
  • Hey @rolandl. Thanks it's really appreciated. When you touch the screen how do you make sure that y coordinate of top left and top right are equal? and same goes for x coordinate of top right and bottom right? If you can make sure that y coordinates of top points and x coordinates right points are equal this question is easy. Otherwise it's more like an algorithm question. When you can generate a forth point you get a rectangle. with a rectangle you can draw it any way you like. One would be drawing to canvas. you can use `dstOffset` and `dstSize` to draw where and which size in canvas – Thracian Sep 04 '22 at 07:40
  • If you are able get a rectangle drawing an image any way you like is easy. You can create an `Image`, use `Modifier.drawWithContent`or draw into Canvas. Challenge looks like creating a rectangle, right? – Thracian Sep 04 '22 at 07:44
  • Hello, I can easily find the missing coordinate of the last point of the rectangle. The issue is not here :) Once I have all the coordinates, I use a shape I apply to a Surface in which one I put my Image as the content. It works correctly. But maybe it is not the best way to do it? Using a canvas is a better option that the one I used? – rolandl Sep 04 '22 at 21:16
  • Canvas/DrawScope is another alternative. Image under the hood uses same thing, DrawScope to draw painter even if you use Image with `ImageBitmap` param. – Thracian Sep 05 '22 at 06:16

2 Answers2

0

You don't need to use Image. A Box clipped to a circle and with a grey-ish background would do. Of course, you'll need an Image to display the actual image you'll be dragging the points on. Here's a sample implementation:

val x by remember { mutableStateOf (Offset(...)) } // Initial Offset
val y by ...
val z by ...

// The image is occupying all of this composable, and these will be positioned ABOVE the image composable using order of positioning.

Layout(
  content = {
    Box(/*Copy From Later*/)
    Box(...)
    Box(...)
  }
) { measurables, constraints ->
  val placeables = measurables.map { it.measure(constraints) } // Default Constraining.
  layout(constraints.maxWidth, constraints.maxHeight){
    measurables[0].place(x.x, x.y)
    measurables[1].place(y.x, y.y)
    measurables[2].place(z.x, z.y)
  }
}

Layout the box like so,

Box(
  modifier = Modifier
               .clip(CircleShape)
               .size(5.dp) // Change per need
               .pointerInput(Unit) {
                  detectDragGestures { change, _ ->
                    x = change.position // similarly y for second, and z for the third box 
                  }
                }
)

This should track/update/position the points wherever you drag them. All the points are individually determined by their own state-holders. You'll need to add this pointerInput logic to every Box, but it would likely be better if you just created a single function to be invoked based on an index, unique to each Box, but that's not something required to be covered here.

Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42
0

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

Thracian
  • 43,021
  • 16
  • 133
  • 222