skydoves / Cloudy

☁️ Jetpack Compose blur effect library, which falls back onto a CPU-based implementation to support older API levels.
Apache License 2.0
789 stars 28 forks source link

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

Closed Vulama closed 1 month ago

Vulama commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

Can reproduce with a LazyColumn

nkhar commented 8 months ago

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:

https://github.com/skydoves/Cloudy/blob/db2bd225232ea4e1a7f72f8da608b023de7e7435/cloudy/src/main/kotlin/com/skydoves/cloudy/Cloudy.kt#L229-L247

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: https://github.com/skydoves/Cloudy/blob/db2bd225232ea4e1a7f72f8da608b023de7e7435/cloudy/src/main/kotlin/com/skydoves/cloudy/Cloudy.kt#L258-L287

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 commented 7 months ago

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 commented 6 months ago

Have the same problem

EvgenSuit commented 5 months ago

the issue still persists

jitash commented 1 month ago

+1

skydoves commented 1 month ago

Hey guys, the new version 0.2.0 has been released, and now you can use Modifier.cloudy modifier instead of the Cloudy composable function. Now, it can be used inside the scrollable components, such as LazyColumn or LazyRow. For more details, check out the reference: https://github.com/skydoves/cloudy?tab=readme-ov-file#maintaining-blurring-effect-on-responsive-composable