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

[Android] Fail to render a large image #10470

Closed
woniesong92 opened this issue Oct 20, 2016 · 19 comments
Closed

[Android] Fail to render a large image #10470

woniesong92 opened this issue Oct 20, 2016 · 19 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@woniesong92
Copy link

woniesong92 commented Oct 20, 2016

Issue Description

When I try to render a big image (5312 x 2988), the image container just shows an empty box. If the image is reasonably sized (~1024x1024) or comes directly from device instead from a remote url, it renders fine. Not sure if it's a memory or networking issue. There's another open issue(#7408) that seems to discuss a similar topic.

Steps to Reproduce / Code Snippets

Render a large image in a ListView. In a dev mode, the app will crash in a few seconds. In a production mode, the app just shows a white background in place of an image.

Inside a ListView I render an image like below:

<Image
  source={{uri: imageURI}}
  style={styles.canvas}
  resizeMode="cover"
/> : null

StyleSheet.create({
  canvas: {
    width: null,
    height: 300,
  },
})

<ListView
  dataSource={this.state.dataSource}
  renderRow={this.onRenderRow}
  enableEmptySections={true}
  initialListSize={5}
  pageSize={10}
/>

Expected Results

Image is rendered successfully.

Additional Information

  • React Native version: 0.35.0
  • Platform(s) (iOS, Android, or both?): Android 6
  • Operating System (macOS, Linux, or Windows?): MacOS

Update

Unfortunately, trying to resize a large picture using ImageEditor also fails because of an OutOfMemory exception. I used the photos that are captured by device's built-in camera.

10-20 16:38:47.511 19110 19154 E AndroidRuntime: FATAL EXCEPTION: AsyncTask #4
10-20 16:38:47.511 19110 19154 E AndroidRuntime: Process: com.kpopio, PID: 19110
10-20 16:38:47.511 19110 19154 E AndroidRuntime: java.lang.RuntimeException: An error occurred while executing doInBackground()
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at android.os.AsyncTask$3.done(AsyncTask.java:309)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at java.util.concurrent.FutureTask.run(FutureTask.java:242)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at java.lang.Thread.run(Thread.java:818)
10-20 16:38:47.511 19110 19154 E AndroidRuntime: Caused by: java.lang.OutOfMemoryError: Failed to allocate a 63489036 byte allocation with 16777216 free bytes and 53MB until OOM
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:882)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:858)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at com.facebook.react.modules.camera.ImageEditingManager$CropTask.crop(ImageEditingManager.java:299)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at com.facebook.react.modules.camera.ImageEditingManager$CropTask.doInBackgroundGuarded(ImageEditingManager.java:270)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at com.facebook.react.modules.camera.ImageEditingManager$CropTask.doInBackgroundGuarded(ImageEditingManager.java:199)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at com.facebook.react.bridge.GuardedAsyncTask.doInBackground(GuardedAsyncTask.java:34)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at com.facebook.react.bridge.GuardedAsyncTask.doInBackground(GuardedAsyncTask.java:22)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at android.os.AsyncTask$2.call(AsyncTask.java:295)
10-20 16:38:47.511 19110 19154 E AndroidRuntime:    at java.util.concurrent.FutureTask.run(FutureTask.java:237)

References:

  1. http://stackoverflow.com/questions/477572/strange-out-of-memory-issue-while-loading-an-image-to-a-bitmap-object/823966#823966
@krishbhattacharyya
Copy link

Hi,
I think the problem is the issue of memory ( I face same problem ). I am not sure but may be this is the reason that's why facebook send small images in device.

@woniesong92
Copy link
Author

Yes. From looking at the source code, RN uses fresco to not load large images in memory for Image components' resizeMode. In this case, I assume that's not enough because the initial image is so big and I should resize it myself before trying to render it.

I would appreciate any advice/suggestion!

@lacker
Copy link
Contributor

lacker commented Oct 25, 2016

I mean, at some point this is expected, right? - there's only so large of an image that you will be able to display before running out of memory. I think you just have to plan to display a thumbnail rather than a whole image if the image is large enough. That's how the underlying Image class in Android works so I think you would have this same problem even if you were not using React Native.

@shaneosullivan
Copy link
Contributor

I'm running into this too, and the images aren't crazy large at all, about 1000x1300.

The main issue is that it works on some devices but not others (and always works on iOS), but the onError handler is never invoked when it fails to render, just a blank box remains.

This means that we cannot have a graceful fallback to load a less detailed image - a thumbnail in my case is not sufficient since the entire purpose of the app is to display large images.

Can at least the onError be made work properly?

@lacker
Copy link
Contributor

lacker commented Oct 26, 2016

Making onError work properly seems like a good idea. Are you interested in putting together a pull request?

@shaneosullivan
Copy link
Contributor

Unlikely to happen soon - I'm on Parental Leave until Jan, and my knowledge of Android is basically nil :-) But this is what we have an awesome team in MPK for, I'm sure someone like Tim Yung could knock this out in ten minutes ;-)

@shaneosullivan
Copy link
Contributor

I've looked into this a bit, and it appears to be a widespread issue in the Android community.

The most promising doc I found is https://developer.android.com/training/displaying-bitmaps/load-bitmap.html , which suggests using the "inSampleSize" options when using the BitmapFactory to load an image. Unfortunately it looks like RN uses FBs own Fresco library to load it, which doesn't appear to use BitmapFactory, as best as I can quickly see.

Any thoughts on how to proceed? This is really a bad problem, any app that wants to show a gallery of non blurry, downsampled images is basically impossible.

@lacker
Copy link
Contributor

lacker commented Oct 29, 2016

I'm excited that you are investigating this during your parental leave ;-) Maybe @mkonicek has an idea for how to proceed here.

@shaneosullivan
Copy link
Contributor

My guess is that it's related to these lines
https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java#L372-L379

It only decided to do a resize if it's a local image (ReactImageView.shouldResize()). Android docs mention that if you do a resize before display, it uses less memory, but RN doesn't.

Thoughts?

@woniesong92
Copy link
Author

woniesong92 commented Oct 29, 2016

@lacker @shaneosullivan Hey! OP here. I found that <Image /> components crash the app with an OutOfMemory exception iff you try to render a big image component with an image source that's sufficiently large.

group_1

Like in @shaneosullivan's comment, I assumed this happens because FB's fresco tries to load big images into memory before resizing and rendering it. So I wrote a simple native module that resizes large images before rendering them. It uses inSampleSize to not load large images into memory while resizing them to avoid throwing exception. For me, this solution worked well but I'm not sure if this can be applied to all cases.

Also, I'd be happy to help resolve this issue! I can start with a small PR that makes onError work.

@lacker
Copy link
Contributor

lacker commented Oct 29, 2016

@woniesong92 Hey that is awesome that you can help resolve this! Making onError work seems like a straightforward step that makes this better. If we change the default image behavior in a more complicated way, it might have more tradeoffs, but I don't see why not to fix onError. If you ping me on the pull request & tag this issue then I will help make sure it gets reviewed.

@shaneosullivan
Copy link
Contributor

@lacker , getting onError to work is a good first step. However this is a fundamental failing in React Native, that it cannot display reasonable sized images on Android when other similar code can do it just fine.

Is there a way to escalate this internally? If we were using RN for our photo viewer this would be a UBN....

@lacker
Copy link
Contributor

lacker commented Nov 2, 2016

@shaneosullivan The internal consensus is that you should fix it 😜

@shaneosullivan
Copy link
Contributor

@lacker , surprising that :-) I'll happily swap you some fun Ads front end tasks that totally rewrite some of the logic core to everything we use to make money. Let's see which of us breaks the most stuff fastest :-) (hint: probably me)

@jcharbo
Copy link

jcharbo commented Mar 18, 2017

Has there been any progress on this?

@mr-ryan-james
Copy link

android:largeHeap="true" fixed the problem for our use cases (per #13600)

@hramos
Copy link
Contributor

hramos commented Jul 20, 2017

Hi there! This issue is being closed because it has been inactive for a while. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. Either way, we're automatically closing issues after a period of inactivity. Please do not take it personally!

If you think this issue should definitely remain open, please let us know. The following information is helpful when it comes to determining if the issue should be re-opened:

  • Does the issue still reproduce on the latest release candidate? Post a comment with the version you tested.
  • If so, is there any information missing from the bug report? Post a comment with all the information required by the issue template.
  • Is there a pull request that addresses this issue? Post a comment with the PR number so we can follow up.

If you would like to work on a patch to fix the issue, contributions are very welcome! Read through the contribution guide, and feel free to hop into #react-native if you need help planning your contribution.

@hramos hramos added the Icebox label Jul 20, 2017
@hramos hramos closed this as completed Jul 20, 2017
@vbuch
Copy link

vbuch commented Jul 25, 2017

This issue still needs fixing in RN 0.46.4. It fails with images not that big (< 1600px on one side)
Edit: Actually, when dealing with large images and using resizeMethod=resize all looks fine. The problem is that the heuristics the docs talk about happen in this piece of code and that's a check for local files.

facebook-github-bot pushed a commit that referenced this issue Sep 26, 2017
Summary:
The Android ImageEditingManager is inefficient and slow when cropping images. It loads the full resolution image into memory and then crops it. This leads to slow performance and occasional OutOfMemory Exceptions.
[BitmapRegionDecoder](https://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html) can be used to crop without needing to load the full resolution image into memory. Using it is much more efficient and much faster.

Relevant issue: #10470

Attempt to crop a very large image (2000x2000) on Android. With this change, the crop should happen almost instantly. On the master branch, it should take 2-3 seconds (and might run out of memory).

Please let me know if there's anything else I can provide.
Closes #15439

Differential Revision: D5628223

Pulled By: shergin

fbshipit-source-id: bf314e76134cd015380968ec4533225e724c4b26
@CrazyPython
Copy link

Genymotion Google Galaxy Nexus (API Level 16) - remote image not loading, works fine on iOS; resizeMethod={'resize'} doesn't work.

@facebook facebook locked as resolved and limited conversation to collaborators Jul 20, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 20, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests