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

Canvas: trying to draw too large bitmap. #3775

Closed
franvis opened this issue Jul 23, 2019 · 33 comments
Closed

Canvas: trying to draw too large bitmap. #3775

franvis opened this issue Jul 23, 2019 · 33 comments

Comments

@franvis
Copy link

franvis commented Jul 23, 2019

Glide Version: 4.9.0

Device/Android Version: Fails in several Samsung devices (95%) and a few of other brands (5%) (I managed to reproduce it in a Samsung Galaxy S8 and in a Google Pixel C tablet)

Issue details / Repro steps / Use case background:
We are currently having several crashes with

Canvas: trying to draw too large(156128256bytes) bitmap.
android.view.DisplayListCanvas.throwIfCannotDraw

This happens when loading images in AppCompatImageViews that are in a ViewPager. We managed to solve this disabling disk cache by adding .apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.NONE))

Glide load line / GlideModule (if any) / list Adapter code (if any):

val thumbnailRequest = Glide
                .with(context)
                .load(lowResUrl)

            Glide.with(context)
                .load(highResUrl)
                .transition(withCrossFade())
                .thumbnail(thumbnailRequest)
                .into(this@ProductImageAtom)

Why do we have to disable disk cache to prevent this crash? Is there another way of fixing this crash in our case?

Thanks!

@liufsd
Copy link

liufsd commented Jul 24, 2019

+1

@sjudd
Copy link
Collaborator

sjudd commented Jul 25, 2019

If you're able to reproduce it in a test or a sample app let me know. Other than changing the inputs to the logic in Downsampler I'm not sure that disabling the disk cache should matter.

How large is the actual view?

@franvis
Copy link
Author

franvis commented Jul 26, 2019

@sjudd Thanks for the answer. I'm able to reproduce it with our app. And definitely happens only when we have the disk cache enabled. As soon as I disabled it, goes smooth. I will try to make a sample app where you can also reproduce it, and will try upload also a video in the course of the following days. Will also give you more info about the view and the dimensions of the image itself when uploading the video as well.

@franvis
Copy link
Author

franvis commented Jul 26, 2019

@sjudd Here I have a sample app that crashes with my case: https://github.com/franvis/glide-crash-sample
I tried it now with a Pixel 3XL emulator. Here you can also see a record of the app crashing, and also not crashing when I disable disk cache

Crashing

Not crashing

@liufsd
Copy link

liufsd commented Jul 29, 2019

@franvis yes i have test your code and then crash on Pixel 3XL emulator.
image

@liufsd
Copy link

liufsd commented Aug 1, 2019

@sjudd hello ~ How to fix this issue ???

@stale
Copy link

stale bot commented Aug 8, 2019

This issue has been automatically marked as stale because it has not had activity in the last seven days. It will be closed if no further activity occurs within the next seven days. Thank you for your contributions.

@stale stale bot added the stale label Aug 8, 2019
@franvis
Copy link
Author

franvis commented Aug 8, 2019

@sjudd Hello!! Can you tell us a bit more about this?

@stale stale bot removed the stale label Aug 8, 2019
@sjudd
Copy link
Collaborator

sjudd commented Aug 14, 2019

Unfortunately I'm not able to reproduce the issue with the sample app. I don't see any difference in image sizes decoded with or without disk caching enabled.

My recollection is also that throwIfCannotDraw is not a crash, it just means the image won't render. Do you have a full stack trace for the crash?

@liufsd
Copy link

liufsd commented Aug 15, 2019

@sjudd Pixel 3XL emulator will be crash . i have test when you swipe image to the last item.

@stale
Copy link

stale bot commented Aug 22, 2019

This issue has been automatically marked as stale because it has not had activity in the last seven days. It will be closed if no further activity occurs within the next seven days. Thank you for your contributions.

@stale stale bot added the stale label Aug 22, 2019
@franvis
Copy link
Author

franvis commented Aug 26, 2019

@sjudd Hello! I'm still able to reproduce it with the sample app, by simply swiping it just crashes. Here you have a stacktrace of it:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.glidecrashsample, PID: 18529 java.lang.RuntimeException: Canvas: trying to draw too large(354553696bytes) bitmap. at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:280) at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:88) at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:548) at android.graphics.drawable.TransitionDrawable.draw(TransitionDrawable.java:233) at android.widget.ImageView.onDraw(ImageView.java:1434) at android.view.View.draw(View.java:21421) at android.view.View.updateDisplayListIfDirty(View.java:20298) at android.view.View.draw(View.java:21153) at android.view.ViewGroup.drawChild(ViewGroup.java:4388) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4149) at android.view.View.draw(View.java:21424) at androidx.viewpager.widget.ViewPager.draw(ViewPager.java:2426) at android.view.View.updateDisplayListIfDirty(View.java:20298) at android.view.View.draw(View.java:21153) at android.view.ViewGroup.drawChild(ViewGroup.java:4388) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4149) at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw(ConstraintLayout.java:2023) at android.view.View.updateDisplayListIfDirty(View.java:20289) at android.view.View.draw(View.java:21153) at android.view.ViewGroup.drawChild(ViewGroup.java:4388) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4149) at android.view.View.updateDisplayListIfDirty(View.java:20289) at android.view.View.draw(View.java:21153) at android.view.ViewGroup.drawChild(ViewGroup.java:4388) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4149) at android.view.View.updateDisplayListIfDirty(View.java:20289) at android.view.View.draw(View.java:21153) at android.view.ViewGroup.drawChild(ViewGroup.java:4388) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4149) at android.view.View.updateDisplayListIfDirty(View.java:20289) at android.view.View.draw(View.java:21153) at android.view.ViewGroup.drawChild(ViewGroup.java:4388) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4149) at android.view.View.updateDisplayListIfDirty(View.java:20289) at android.view.View.draw(View.java:21153) at android.view.ViewGroup.drawChild(ViewGroup.java:4388) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4149) at android.view.View.draw(View.java:21424) at com.android.internal.policy.DecorView.draw(DecorView.java:801) at android.view.View.updateDisplayListIfDirty(View.java:20298) at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:575) at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:581) at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:654) at android.view.ViewRootImpl.draw(ViewRootImpl.java:3601) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3409) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2746) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1714) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7587) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966) at android.view.Choreographer.doCallbacks(Choreographer.java:790) at android.view.Choreographer.doFrame(Choreographer.java:725) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7343) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:933)

@stale stale bot removed the stale label Aug 26, 2019
@stale
Copy link

stale bot commented Sep 2, 2019

This issue has been automatically marked as stale because it has not had activity in the last seven days. It will be closed if no further activity occurs within the next seven days. Thank you for your contributions.

@stale stale bot added the stale label Sep 2, 2019
@franvis
Copy link
Author

franvis commented Sep 2, 2019

@sjudd Any news again?

@stale stale bot removed the stale label Sep 2, 2019
@liufsd
Copy link

liufsd commented Sep 3, 2019

nothing ?

@shpasha
Copy link

shpasha commented Sep 4, 2019

I faced with this case too. When the image is narrow, in onResourceReady callback we can see, that resource have too large width and height. So possible solution its just use .override(screenWidth)

@stale
Copy link

stale bot commented Sep 15, 2019

This issue has been automatically marked as stale because it has not had activity in the last seven days. It will be closed if no further activity occurs within the next seven days. Thank you for your contributions.

@stale stale bot added the stale label Sep 15, 2019
@liufsd
Copy link

liufsd commented Sep 16, 2019

@shpasha But. i need show origin image ~ ~ ~

@stale stale bot removed the stale label Sep 16, 2019
@hike2008
Copy link

How to fix this issue ?

@sjudd
Copy link
Collaborator

sjudd commented Sep 17, 2019

Ok on a Pixel 3XL I was able to reproduce, here's the Downsampler logs:

09-16 17:52:23.823 V/Downsampler( 5636): Calculate scaling, source: [17x500], target: [1328x882], power of two scaled: [17x500], exact scale factor: 78.117645, power of 2 sample size: 1, adjusted scale factor: 78.11764526367188, target density: 2147483647, density: 27490379
09-16 17:52:23.824 V/Downsampler( 5636): Calculated target [1328x39059] for source [17x500], sampleSize: 1, targetDensity: 2147483647, density: 27490379, density multiplier: 78.11764
09-16 17:52:24.307 V/Downsampler( 5636): Decoded [1328x39059] ARGB_8888 (207481408) from [17x500] image/jpeg with inBitmap [1328x39059] ARGB_8888 (207481408) for [1328x882], sample size: 1, density: 27490379, target density: 2147483647, thread: glide-disk-cache-thread-0, duration: 484.04900299999997

@sjudd
Copy link
Collaborator

sjudd commented Sep 17, 2019

Ah ok the disk cache strategy is red herring, it just makes it more likely that you'll have a race condition where the thumbnail request finishes more quickly than the non-thumbnail request. You can reproduce the crash with any disk cache strategy, it's just more likely with one than the other.

The actual issue is:

  1. The thumbnail and primary requests are using different transformations. The thumbnail is using the default CENTER_OUTSIDE strategy, which aggressively upscales so that the image is larger in both dimensions than the requested image. The primary request is using FIT_CENTER based on the ImageView's scale type.
  2. The low res url matches an image that's wildly out of proportion (17x500). To scale 17 up to the requested 1328 requires a very large scale factor, which, when mulitplied by 500, produces a very large image.

The simplest fix is to either use a more proportional image, or to explicitly set a more reasonable downsample strategy (FIT_CENTER, or even CENTER_INSIDE).

There's some argument to be made that the thumbnails should also use the transformation based on the ImageView's scale type, but we've never done so in the past and it might be a subtle breaking change to start doing so now.

I'll leave this open to consider making the thumbnail inherit the ImageView's transformation as well if a transformation isn't otherwise specified. #2964 already discusses avoiding upscaling by default.

Here's the Downsampler logs using FIT_CENTER:

09-16 18:14:22.080 V/Downsampler( 6736): Calculate scaling, source: [17x500], target: [1328x882], power of two scaled: [17x500], exact scale factor: 1.764, power of 2 sample size: 1, adjusted scale factor: 1.7640000581741333, target density: 2147483647, density: 1217394318
09-16 18:14:22.080 V/Downsampler( 6736): Calculated target [30x882] for source [17x500], sampleSize: 1, targetDensity: 2147483647, density: 1217394318, density multiplier: 1.764
09-16 18:14:22.082 V/Downsampler( 6736): Decoded [30x882] ARGB_8888 (105840) from [17x500] image/jpeg with inBitmap [30x882] ARGB_8888 (105840) for [1328x882], sample size: 1, density: 1217394318, target density: 2147483647, thread: glide-source-thread-3, duration: 2.780125

@sjudd
Copy link
Collaborator

sjudd commented Sep 17, 2019

Actually I'm going to file a new issue for the transformation on thumbnails, will close this as otherwise broken as expected.

@zhanghai
Copy link

zhanghai commented Sep 23, 2019

I am using the default CENTER_OUTSIDE strategy and I still want to use it because of design reasons.

Alternatively, can we add a reasonable limit for the DownSampler? Because loading a 39059 px tall bitmap just doesn't make sense and will certainly fail anyway.

@sjudd
Copy link
Collaborator

sjudd commented Sep 24, 2019

You could avoid CENTER_OUTSIDE, use a non-scaling strategy, and upscale in the view itself? Or you can write your own DownsampleStrategy to meet whatever your requirements are.

It's going to be hard to define a reasonable safe limit that applies to all loads in all applications.

@franvis
Copy link
Author

franvis commented Oct 10, 2019

Hello @sjudd! I tried your possible solutions and changing the downsample strategy to centerInside() like

Glide.with(context)
                .load(highResUrl)
                .transition(withCrossFade())
                .thumbnail(thumbnailRequest)
                .centerInside()

Still gives me a crash in the sample app I shared with you and we are having quite a lot of crashes in our production app as well... Any other suggestion or possible solution?

@franvis
Copy link
Author

franvis commented Oct 18, 2019

Hello @sjudd! I tried your possible solutions and changing the downsample strategy to centerInside() like

Glide.with(context)
                .load(highResUrl)
                .transition(withCrossFade())
                .thumbnail(thumbnailRequest)
                .centerInside()

Still gives me a crash in the sample app I shared with you and we are having quite a lot of crashes in our production app as well... Any other suggestion or possible solution?

@sjudd Moving to fitCenter() made the fix. Thanks!!

@VeerHan
Copy link

VeerHan commented Oct 7, 2020

Hello @sjudd! I tried your possible solutions and changing the downsample strategy to centerInside() like

Glide.with(context)
                .load(highResUrl)
                .transition(withCrossFade())
                .thumbnail(thumbnailRequest)
                .centerInside()

Still gives me a crash in the sample app I shared with you and we are having quite a lot of crashes in our production app as well... Any other suggestion or possible solution?

@sjudd Moving to fitCenter() made the fix. Thanks!!
Moving to fitCenter() does not work for me.

@Hamza417
Copy link

Hamza417 commented May 6, 2021

Do not use android:adjustViewBounds="true" and wrap_content as width and height parameter, on doing so Glide will draw full size image irrespective of the ImageView's dimension which will ultimately cause this error.

@bhavin-qfonapp
Copy link

Do not use android:adjustViewBounds="true" and wrap_content as width and height parameter, on doing so Glide will draw full size image irrespective of the ImageView's dimension which will ultimately cause this error.

@Hamza417 what if we need that? In my case I don't have height-width coming from api only image url and i have like image -text- gif 3 view types so i have to use android:adjustViewBounds="true" and wrap_content . so what to do in this scenario ?

Lmh170 added a commit to Lmh170/Android-Gallery-App that referenced this issue Jun 3, 2022
@sucicf1
Copy link

sucicf1 commented Aug 11, 2022

Hello, in my case I have solved that by doing the following:

  1. added proguard rules
  2. Changing from fragments or activity's context to imageview context inside glide command
  3. and last but important in recyclerview adapter if you call getheight() or getwidth() on imageview then it returns 0 and used that in glide command. In my case imageviews size depends on other view in itemviews(they size is determined by wrap content). That causes something like Canvas: trying to draw too large bitmap. #3775 (comment) Solved by setting width and height in glide command as fixed values (calculated by screen width or screen height and other hardcoded). My imageview continues to have variable size but now no out of memory

@yoviekaputra
Copy link

hi @franvis , summary to solve this problem is implement like this, right?

Glide.with(context)
                .load(highResUrl)
                .transition(withCrossFade())
                .thumbnail(thumbnailRequest)
                . fitCenter()

btw, i couldn't access this link https://github.com/franvis/glide-crash-sample, error 404

@guppy-jonathan
Copy link

Do not use android:adjustViewBounds="true" and wrap_content as width and height parameter, on doing so Glide will draw full size image irrespective of the ImageView's dimension which will ultimately cause this error.

I can reproduce the same crash with the following parameters.

  1. The setting of the imageView is exactly same as mentioned in quote
  2. The crash only happens in the device under Android 8 (a user report that it fixes after upgrading to Android 9) and 90% of crash happened in specific models of Sharp and one model of Samsung (and emulator).
  3. The above solution (except the one mentioned proguard rules) didn't work for me. And the other devices works well without crashes.

@divikiran
Copy link

I use xamarin, I had to scale down images. Its the height and width of the images are the problem to Canvas

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