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

App crashes when Cloudy is used in the Column with verticalScroll #7

Open
Vulama opened this issue Jan 11, 2023 · 7 comments
Open

App crashes when Cloudy is used in the Column with verticalScroll #7

Vulama opened this issue Jan 11, 2023 · 7 comments
Assignees
Labels
bug Something isn't working

Comments

@Vulama
Copy link

Vulama commented Jan 11, 2023

  • Library Version: 0.1.1
  • Tested on:
    -- API 29 - Android 10 (Pixel 5 Android Studio emulator)
    -- API 33 - Android 13 (OnePlus 8 physical device)

Describe the Bug:
When we try to use Cloudy in any structure that uses vertical scroll in any of the parents, the app crashes.

Error -> java.lang.RuntimeException: Failed to copy pixels of the given bitmap!
Example of the code that crashes (this is tested on the blank project):

Column(
    modifier = Modifier.verticalScroll(rememberScrollState())
) {
    Spacer(Modifier.height(1000.dp))

    Cloudy {
        Box(
            modifier = Modifier.size(200.dp)
        )
    }
}

Expected Behavior:
This should work in the scrollable structure.

@Tomaswin
Copy link

yep, i have the same problem. Im with a lazycolumn and i see a strange effect when you start to scroll the images or a texts. Anyone know how to fix it?

@DzartXStudio
Copy link

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mycompany.myapp, PID: 13052
java.lang.RuntimeException: Failed to copy pixels of the given bitmap!
at com.skydoves.cloudy.CloudyKt.drawBitmapWithPixelCopy$lambda$7(Cloudy.kt:281)
at com.skydoves.cloudy.CloudyKt.$r8$lambda$X2Nw-BPO6_ca_Hnlrx5we4hyARg(Unknown Source:0)
at com.skydoves.cloudy.CloudyKt$$ExternalSyntheticLambda0.onPixelCopyFinished(Unknown Source:6)
at android.view.PixelCopy$1.run(PixelCopy.java:191)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:233)
at android.app.ActivityThread.main(ActivityThread.java:7225)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:499)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:962)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@157fa8e, androidx.compose.runtime.BroadcastFrameClock@8a122af, StandaloneCoroutine{Cancelling}@78746bc, AndroidUiDispatcher@d8e9645]#

with just normal Column

@rushiiMachine
Copy link

rushiiMachine commented Jul 1, 2023

Seems to occur when you scroll at anything but a turtle's pace

Can reproduce with a LazyColumn

@nkhar
Copy link

nkhar commented Dec 28, 2023

TL;DR this is caused by PixelCopy, can't copy scrollable content, because they are out of bounds of view containing Window. View.drawToBitmap might work, but will have to disable hardware acceleration for App, Activity or Glide Request(more about glide here

Hello the root cause of your problem is hardware acceleration.
After about Android Oreo bitmaps are drawn directly to hardware. If we want to copy pixels from a view using canvas.draw, view's cache or other software methods won't work we have to use PixelCopy. Here is the code in Cloudy.kt that does just that:

private suspend fun View.drawToBitmapPostLaidOut(
window: Window,
layoutInfo: LayoutInfo
): Bitmap? {
return suspendCoroutine { continuation ->
doOnLayout {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
drawBitmapWithPixelCopy(
window = window,
layoutInfo = layoutInfo,
onSuccess = { bitmap -> continuation.resume(bitmap) },
onError = { error -> continuation.resumeWithException(error) }
)
} else {
continuation.resume(this.drawToBitmap())
}
}
}
}

You can see contents of drawToBitmap here:
https://android.googlesource.com/platform/frameworks/support/+/android-room-release/core/ktx/src/main/java/androidx/core/view/View.kt#188

Now let's see how drawBitmapWithPixelCopy works:

private fun drawBitmapWithPixelCopy(
window: Window,
layoutInfo: LayoutInfo,
onSuccess: (Bitmap) -> Unit,
onError: (Throwable) -> Unit
) {
val rect = Rect(
layoutInfo.xOffset,
layoutInfo.yOffset,
layoutInfo.xOffset + layoutInfo.width,
layoutInfo.yOffset + layoutInfo.height
)
val bitmap = Bitmap.createBitmap(layoutInfo.width, layoutInfo.height, Bitmap.Config.ARGB_8888)
PixelCopy.request(
window,
rect,
bitmap,
{ copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
onSuccess.invoke(bitmap)
} else {
onError.invoke(
RuntimeException("Failed to copy pixels of the given bitmap!")
)
}
},
Handler(Looper.getMainLooper())
)
}

As you can see the exception: RuntimeException("Failed to copy pixels of the given bitmap!") is thrown when PixelCopy.request is not successful.
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/view/PixelCopy.java;l=254-282;drc=9bdd2e6151aa7a70bb4a12e91e7f68959b6334cb

If we look inside the request function, we can see that the new surface object is created from source Window:

final ViewRootImpl root = source.peekDecorView().getViewRootImpl();
        if (root != null) {
            surface = root.mSurface;

You can check window's width and height to see that it is close to the screen dimensions, it does not matter if you have scrolling content inside of that window.
If you have some image or some other view inside of Cloudy composable that is out of the bounds of Window you will get an Exception, because the content will say that its location is at x=1500, y= 3000, when bounds of your window are at x=1080, y = 2800. so when srcRect is passed with (1500, 3000) PixelCopy will return an integer 1 which is constant ERROR_UNKNOWN: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/view/PixelCopy.java;l=46?q=PixelCopy

It is not actually PixelCopy class that does the actual copying of Pixels, the chain of method calls goes deep into the native code starting fromThreadedRenderer.copySurfaceInto(source, srcRect, dest);
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/view/PixelCopy.java;l=187?q=PixelCopy

copySurfaceInto is actually not a method of ThreadedRenderer it is from its parent HardwareRenderer as you can see:
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ThreadedRenderer.java;l=63?q=ThreadedRenderer

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/graphics/java/android/graphics/HardwareRenderer.java;l=1132?q=HardwareRenderer

HardwareRenderer calls native method:
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/graphics/java/android/graphics/HardwareRenderer.java;l=1588?q=HardwareRenderer

Here as you can see inside of HardwareRenderer.cpp copySurfaceInto call is delegated to RenderProxy:
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/graphics/java/android/graphics/HardwareRenderer.java;l=1132?q=HardwareRenderer

RenderProxy gets an Instance of RenderThread, then gets a ReadBack field from RenderThread Instance and calls copySurfaceInto function:
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/renderthread/RenderProxy.cpp;l=374?q=RenderProxy

here is the method to get ReadBack:
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/renderthread/RenderProxy.cpp;l=374?q=RenderProxy

And here we have the actual implementation of copySurfaceInto function in ReadBack class:
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/Readback.cpp;l=56-268?q=ReadBack

Because this is native code deep within AOSP, I have not fully grasped it nor do I have a way to debug it. However here is the list of all lines that return Unknown Error:

  1. https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/Readback.cpp;l=114?q=ReadBack
  2. https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/Readback.cpp;l=167?q=ReadBack
  3. https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/Readback.cpp;l=167?q=ReadBack
  4. https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/Readback.cpp;l=261?q=ReadBack
    There are 4 more, but they are in different functions of the ReadBack class.

You can read what software bitmap functions no longer work, because of hardware bitmaps:
Glide doc

@cevlikalprn
Copy link

The same problem is happening on my side.

Here is the crash :
Fatal Exception: java.lang.RuntimeException: Failed to copy pixels of the given bitmap!
at com.skydoves.cloudy.CloudyKt.drawBitmapWithPixelCopy$lambda$7(Cloudy.kt:281)
at android.view.PixelCopy$1.run(PixelCopy.java:191)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:8056)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)

@bybozyurt
Copy link

Have the same problem

@EvgenSuit
Copy link

the issue still persists

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

9 participants