PatilShreyas/Capturable

edges are cut off and there is a white background when capturing in devices below API 29

Opened this issue · 13 comments

I use Capturable to capture the canvas and save it in the gallery. The problem occurs on devices with API<29 when I zoom out from the canvas with the graphicsLayer to add additional pictures. Distant parts are simply cut off and so on only in devices with API<29, but on devices above everything is captured perfectly without cutting, etc. How can this be fixed?
photo_2024-05-02_01-16-59

@naikon33 can you show a example of how you're using a modifier?

yes , here is part of the code that is responsible for zooming out and zooming in

`Capturable(
controller = captureController,
modifier = Modifier
.size(capturableW.value, capturableH.value)
.graphicsLayer(
translationX = offsetCanvasX,
translationY = offsetCanvasY,
),
onCaptured = { bitmap, error ->
if (bitmap != null) {
saveToFile(
context = context,
bitmap = bitmap.asAndroidBitmap()
) { filePath ->
onSave(filePath)
}
}

                                if (error != null) {
                                    // Error occurred. Handle it!
                                }
                            }
                        ) {

                            BoxWithConstraints(
                                Modifier
                                    .size(646.dp, 318.dp)
                                    .clip(RoundedCornerShape(16.dp))
                                    .background(Color.White)
                                    .graphicsLayer(
                                        translationX = offsetCanvasX,
                                        translationY = offsetCanvasY,
                                        scaleX = contentScale,
                                        scaleY = contentScale
                                    )
                                    .onGloballyPositioned { layoutCoordinates ->
                                        drawingAreaSize.value = layoutCoordinates.size
                                        /*selectedDrawables.forEach { drawableState ->
                                        val drawable =
                                            ContextCompat.getDrawable(context, drawableState.id)
                                        drawable?.let {
                                            val width = it.intrinsicWidth * drawableState.scale
                                            val height =
                                                it.intrinsicHeight * drawableState.scale
                                            drawable.setBounds(
                                                0,
                                                0,
                                                width.toInt(),
                                                height.toInt()
                                            )
                                        }
                                    }*/
                                    }`

I can’t understand why saving with distance doesn’t work on devices with API<29 but it works on devices with API>29

I'm using the library version: implementation "dev.shreyaspatil:capturable:1.0.3"

Can you try with the latest version please and with the latest modifier API?

I used the latest versions and even installed the latest version implementation "dev.shreyaspatil:capturable:2.1.0" . But this did not help, everything is also cut off in API below 29

Are you maintaining modifier order properly with the latest version's implementation?

yes, if this were not so, then saving with distance would not work correctly in API>29

This is my order:


Capturable(
                                controller = captureController,
                                modifier = Modifier
                                    .size(capturableW.value, capturableH.value)
                                    .graphicsLayer(
                                        translationX = offsetCanvasX,
                                        translationY = offsetCanvasY,
                                    ),
                                onCaptured = { bitmap, error ->
                                    // This is captured bitmap of a content inside Capturable Composable.
                                    if (bitmap != null) {
                                        saveToFile(
                                            context = context,
                                            bitmap = bitmap.asAndroidBitmap()
                                        ) { filePath ->
                                            onSave(filePath)
                                        }
                                        // Bitmap is captured successfully. Do something with it!
                                    }

                                    if (error != null) {
                                        // Error occurred. Handle it!
                                    }
                                }
                            ) {
                         BoxWithConstraints(
                                    Modifier
                                        .size(646.dp, 318.dp)
                                        .clip(RoundedCornerShape(16.dp))
                                        .background(Color.White)
                                        .graphicsLayer(
                                            translationX = offsetCanvasX,
                                            translationY = offsetCanvasY,
                                            scaleX = contentScale,
                                            scaleY = contentScale
                                        )
                                        .onGloballyPositioned { layoutCoordinates ->
                                            drawingAreaSize.value = layoutCoordinates.size
                                            /*selectedDrawables.forEach { drawableState ->
                                            val drawable =
                                                ContextCompat.getDrawable(context, drawableState.id)
                                            drawable?.let {
                                                val width = it.intrinsicWidth * drawableState.scale
                                                val height =
                                                    it.intrinsicHeight * drawableState.scale
                                                drawable.setBounds(
                                                    0,
                                                    0,
                                                    width.toInt(),
                                                    height.toInt()
                                                )
                                            }
                                        }*/
                                        }
                                        .pointerInput(Unit) {
                                            detectTapGestures { offset ->
                                                if (selectedSegment == TabItem.Text) {
                                                    isTextMode = true
                                                    textFields = textFields + TextFieldData(
                                                        "",
                                                        offset.x,
                                                        offset.y,
                                                        editable = true
                                                    )

                                                } else {
                                                    val touchedLines =
                                                        lines.firstOrNull { lineGroup ->
                                                            lineGroup.any { line ->
                                                                isPointCloseToLine(
                                                                    offset,
                                                                    line
                                                                )
                                                            }
                                                        }

                                                    if (touchedLines != null) {
                                                        selectedLines =
                                                            touchedLines.also { lineGroup ->
                                                                lines.forEach {
                                                                    it.forEach { line ->
                                                                        line.isSelected.value =
                                                                            false
                                                                    }
                                                                }
                                                                lineGroup.forEach {
                                                                    it.isSelected.value = true
                                                                }
                                                            }
                                                    } else {
                                                        lines.forEach {
                                                            it.forEach { line ->
                                                                line.isSelected.value = false
                                                            }
                                                        }
                                                        selectedLines = null
                                                    }
                                                }
                                                selectedDrawables.forEachIndexed { i, ds ->
                                                    ds.isSelected.value = false
                                                }
                                                lastMovedIndex = -1
                                            }
                                        }
                                        .pointerInput(Unit) {
                                            if (selectedSegment != TabItem.Text) {
                                                detectTransformGestures { centroid, pan, zoom, rotation ->
                                                    scope.launch {
                                                        val newScale = contentScale * zoom
                                                        contentScale = newScale.coerceIn(0.43f, 5f)

                                                        val centroidOffsetX =
                                                            centroid.x - (1041 / 2)
                                                        val centroidOffsetY = centroid.y - (603 / 2)

                                                        val proposedOffsetX =
                                                            offsetCanvasX + pan.x - (zoom - 1) * centroidOffsetX
                                                        val proposedOffsetY =
                                                            offsetCanvasY + pan.y - (zoom - 1) * centroidOffsetY

                                                        val maxOffsetX =
                                                            max(0f, 1041 * (contentScale - 1) / 2)
                                                        val maxOffsetY =
                                                            max(0f, 603 * (contentScale - 1) / 2)

                                                        offsetCanvasX = proposedOffsetX.coerceIn(
                                                            -maxOffsetX,
                                                            maxOffsetX
                                                        )
                                                        offsetCanvasY = proposedOffsetY.coerceIn(
                                                            -maxOffsetY,
                                                            maxOffsetY
                                                        )
                                                    }
                                                }
                                            }
                                        }
                                        .pointerInput(selectedLines) {
                                            if (selectedSegment == TabItem.Lines) {
                                                detectTransformGestures { centroid, pan, zoom, rotation ->
                                                    val groupCenter =
                                                        selectedLines?.let { findGroupCenter(it) }
                                                            ?: return@detectTransformGestures
                                                    val degrees =
                                                        rotation * (180.0 / Math.PI).toFloat() * 0.1f


                                                    selectedLines?.forEach { line ->
                                                        line.offsetX.value += pan.x
                                                        line.offsetY.value += pan.y
                                                        line.rotateAround(groupCenter, degrees)
                                                    }
                                                }
                                            }
                                        }
                                ) {


ActionButton(
                                modifier = Modifier
                                    .width(156.dp)
                                    .height(40.dp),
                                title = stringResource(R.string.save).toUpperCase(Locale.current),
                                isFilled = true,
                                onClick = {
                                        captureController.capture()
                                }
                            )

and here's my implementation:

dependencies {
  implementation "androidx.appcompat:appcompat:1.6.1"
 implementation "androidx.drawerlayout:drawerlayout:1.1.1"
 implementation 'androidx.core:core-ktx:1.13.1'
 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
 implementation 'androidx.activity:activity-compose:1.9.0'
 implementation "androidx.compose.ui:ui:1.6.7"
 implementation "androidx.compose.ui:ui-tooling-preview:1.6.7"
 implementation "io.coil-kt:coil-compose:2.4.0"
 implementation 'androidx.compose.material:material:1.6.7'

 implementation "dev.shreyaspatil:capturable:2.1.0"

 testImplementation 'junit:junit:4.13.2'
 androidTestImplementation 'androidx.test.ext:junit:1.1.5'
 androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
 androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.6.7"
 debugImplementation "androidx.compose.ui:ui-tooling:1.6.7"
 debugImplementation "androidx.compose.ui:ui-test-manifest:1.6.7"
}

Don't use Capturable composable function which is deprecated now.

Use capturable() modifier on the exact component which you need to capture and see if that works or not.

I did as in the example in a new way, it didn’t help, the edges are still cut off:

 Column(
                                modifier = Modifier.capturable(captureController)
                            ) {

                                BoxWithConstraints(
                                    Modifier
                                        .size(646.dp, 318.dp)
                                        .clip(RoundedCornerShape(16.dp))
                                        .background(Color.White)
                                        .graphicsLayer(
                                            translationX = offsetCanvasX,
                                            translationY = offsetCanvasY,
                                            scaleX = contentScale,
                                            scaleY = contentScale
                                        )
                                        .onGloballyPositioned { layoutCoordinates ->
                                            drawingAreaSize.value = layoutCoordinates.size
                                           
                                        }
                                        .pointerInput(Unit) {
                                           
                                        }
                                ) {
ActionButton(
                                modifier = Modifier
                                    .width(156.dp)
                                    .height(40.dp),
                                title = stringResource(R.string.save).toUpperCase(Locale.current),
                                isFilled = true,
                                onClick = {
                                        scope.launch {
                                            val bitmapAsync = captureController.captureAsync()
                                            try {
                                                val bitmap = bitmapAsync.await()
                                                saveToFile(
                                                    context = context,
                                                    bitmap = bitmap.asAndroidBitmap()
                                                ) { filePath ->
                                                    onSave(filePath)
                                                }
                                        
    } catch (error: Throwable) {
                                                // Error occurred, do something.
                                            }
                                    }
                                }
                            )

here is a photo of it still being cropped:
Screenshot_20240506_100136

And this is what it looks like on canvas:

Screenshot_20240506_100540

I'm not sure on this, but I think this is happening because of translation and scale applied via graphicsLayer() modifier. Maybe that's causing cropping of the captured area. Just for the test, can you try removing that graphicsLayer() for instance?

Well, graphicsLayer() is needed to zoom out and these distant places are cut off.P.s it turns out that the edges are cut off only in API 28; in other versions everything works correctly. Then why is the problem with API 28?