Glide: Canvas: trying to draw too large bitmap.

Created on 23 Jul 2019  路  27Comments  路  Source: bumptech/glide

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!

Most helpful comment

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

All 27 comments

+1

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?

@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.

@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

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

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

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.

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

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?

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

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.

@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)

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.

@sjudd Any news again?

nothing ?

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)

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.

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

How to fix this issue ?

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

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

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

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.

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.

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?

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!!

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

StefMa picture StefMa  路  3Comments

billy2271 picture billy2271  路  3Comments

ghost picture ghost  路  3Comments

sant527 picture sant527  路  3Comments

Tryking picture Tryking  路  3Comments