Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
naikon33 opened this issue May 3, 2024 · 13 comments

Comments

@naikon33
Copy link

naikon33 commented May 3, 2024

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

@PatilShreyas
Copy link
Owner

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

@naikon33
Copy link
Author

naikon33 commented May 3, 2024

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

@naikon33
Copy link
Author

naikon33 commented May 3, 2024

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

@PatilShreyas
Copy link
Owner

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

@naikon33
Copy link
Author

naikon33 commented May 4, 2024

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

@PatilShreyas
Copy link
Owner

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

@naikon33
Copy link
Author

naikon33 commented May 4, 2024

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

@naikon33
Copy link
Author

naikon33 commented May 4, 2024

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()
                                }
                            )

@naikon33
Copy link
Author

naikon33 commented May 4, 2024

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"
}

@PatilShreyas
Copy link
Owner

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.

@naikon33
Copy link
Author

naikon33 commented May 5, 2024

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

@PatilShreyas
Copy link
Owner

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?

@naikon33
Copy link
Author

naikon33 commented May 9, 2024

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants