For last 10+ hours I try to get a large (40+) amount of images (in ImageButton format) on a single Android screen without out of memory errors. The activity I work on is an image picker for a coloring book app. Images are of various sizes in the range of (500 to 1200)x(500 to 1200), PNGs (if that matters).
I have tried:
Horizontal Scroll View with images added from the code. The result is slow (I do it on UI thread) and consumes large memory space.
Horizontal Scroll View with images added from the code via AsyncThread. The result is fast but still consumes large memory space. I like the user experience of this one the most!
Grid View and List View - both are very choppy (testing on first Nexus 7). Memory usage is better.
What I am considering
View Pager - first results look better than Grid View from performance perspective (I have not completed it to the moment to assess the memory usage but it should be better from what I understand). Yet, I dislike the user experience and prefer a scrollable list of all images.
Conversion of my resources to JPG (will that get rid of Transparency byte?)
Downsizing the images to max 500x500px
None of the solutions seems as good as the Android Photo Gallery app available on all devices. This experience is what I would love to mirror. No idea how this is done though :(
Two questions.
What is the best way to get such thing (40+ Images scrollable on single screen) done? Is it really ViewPager? ScrollView with AsyncTask and well thought images resolution? Something I have not tried yet?
What is the memory limit I should try to keep below? Looking at Android heap size on different phones/devices and OS versions it seems to be 256 MB, is that fair assumption?
Thanks. Have a great weekend!
PS. On iOS all works like charm when I add all the buttons into a scroll view :(
Some basic calculations reveals your problem:
40+ images at 1200x1200 = approx 57MB, the comments are correct you need to subsample (i.e. thumbnail) the images or use a library such as the Universal Image Loader. Converting to JPG doesn't matter. That is a compressed storage format, the memory required to display the pixels remains the same.
There is a Android Developers article on Loading Large Bitmaps Efficiently with sample code. The following steps are covered in more detail in the article Android Bitmap Loading for Efficient Memory Usage.
The basic steps in loading a large bitmap are:
Determine the required size (from a known size or determining the
size of the target View).
Use the BitmapFactory class to get the bitmap’s size (set inJustDecodeBounds in BitmapFactory.Options to true).
Calculate the subsampling value and pass it to the BitmapFactory.Options setting inSampleSize.
Use the BitmapFactory to load a reduced size bitmap.
Related
I'm taking whole screenshot of a WebView and display the bitmap on an ImageView. The bitmap can be 7 screen height. (E.g. 1440x14000 px)
I'm frequently face with
OutOfMemoryError.
I've seen this
This says that load a scaled down version into memory but I don't want to lose image quality. There are the same approaches on the web.
Is there any way to handle OutOfMemoryError without loading scaled down version?
The bitmap can be 7 screen height. (E.g. 1440x14000 px)
Note that this means that the user cannot see the whole image at once at full resolution.
I'm frequently face with OutOfMemoryError
On most devices, you will have a very difficult time loading an image that large, as you cannot get a single contiguous memory block that big.
This says that load a scaled down version into memory but I don't want to lose image quality
To some extent, you do not have much of a choice. If you want the user to see the full extent of the picture at once, the image has to be scaled to fit the screen.
Is there any way to handle OutOfMemoryError without loading scaled down version?
There are ImageView replacements that offer pan and zoom. Some of those, such as this one, handle loading in pieces of the image at a time, with whatever scaling is necessary for the current zoom level, to make it more likely that you will be able to show the user the entire image.
It is not a solution, of course, but I'm also not familiar with your exact needs, so maybe this may help you a little - you can try to play with bitmap options during decoding. Try to use Bitmap.Config.inPreferredConfig as RGB_565 - this will reduce size of your bitmap twice comparing to default ARGB_8888. But, of course, if you use complex images in your web page this may reduce their quality.
In my app I'm generating a bunch of Bitmaps at runtime to show in a GridView. The generated Bitmaps consist only of rectangular shapes and about five different colors.
If I make them big, they get scaled down nicely, but I get OutOfMemoryExceptions. But when I make them small, they're not scaled up to fit the column width. I think ImageView can't help me, because it doesn't know the final column with. Setting stretchMode to columnWidth in the GridView didn't help.
Setting adjustViewBounds to true on the ImageView helped with large Bitmaps, but it doesn't help for upscaling.
Is it somehow possible to scale the ImageView with the underlying Bitmap to the maximum column width of the GridView? This would be my preferred solution.
If not, can I determine the columnWidth of the GridView in advance to just generate the bitmap accordingly? (I don't like this solution that much, because I suspect that on devices with large screens I might run into OutOfMemoryExceptions again.)
You have two choices.
METHOD 1:
Optimize your images by using any online image compression sites . For example https://tinypng.com .TinyPNG uses smart lossy compression techniques to reduce the file size of your PNG files. By selectively decreasing the number of colors in the image, fewer bytes are required to store the data. The effect is nearly invisible but it makes a very large difference in file size!
METHOD 2:
Load your images using third party libraries like Universal Image Loader, Glide .. these libraries aims to provide a powerful, flexible and highly customizable instrument for image loading, caching and displaying. It provides a lot of configuration options and good control over the image loading and caching process.
Since you generate the bitmaps in your app, you can use libraries like Picasso to display them. Picasso will handle the memory on your behalf and you need not worry about OutOfMemory Exceptions.
So, I have been using this amazing library Glide for showing native images in my gallery app. I am using ViewPager with FragmentStatePagerAdapter to show full size images. The pager's off screen limit is 1 (default to save memory). I am using this code to load images into ViewPager in my fragment:
Glide.with(getActivity())
.loadFromMediaStore(uri)
.asBitmap()
.signature(new MediaStoreSignature(mimeType, dateModified,
.into(mImageView);
Now, I am facing some issues here like:
Images take quite some amount of time to load (if not cached). So, while user is scrolling through the viewpager blank screen is shown while image is loading which is what I want to avoid. Is there any way I can do this? Maybe by precaching images?
Sometimes, while scrolling through large size images (mainly Camera photos) OOM Exception is thrown and user is left with blank screen as no image is loaded. This also happens when I am shifting from potrait to landscape mode. So, I tried to use methods like atMost() -- which degrade the quality of image further as images are already loaded in RGB_565 and approximate() which is also causing OOM. How can I achieve maximum image quality without getting OOM exceptions?
For the second issue, I was thinking to load lesser quality images for the off screen items and then enhance quality when they come on-screen. Is it possible?
I have also tried to use ARGB_8888 but the result was same: OOM exception.
TL;DR
Make sure the ImageView has match_parent or fixed dp as dimensions
wrap_content makes Glide load full resolution Bitmaps.
.placeholder() shows an image instead of empty space while loading large bitmap
.thumbnail(float) loads a downsampled version fast while the bigger image is loading in the background
Also look around the Glide issues, maybe you find something helpful.
Details
I would be curious what the xml is for the ImageView, because my guess is it's wrap_content which results in loading images at full resolution into Bitmaps (using a lot of memory). If that's the case I would suggest using match_parent or fixed dp to lower the resolution. Note: you won't use detail, because currently the image is downsampled at render time anyway, just pulling that forward to decoding phase.
You also have to make sure that your app doesn't have constraints for memory usage. Can you load 3 (off screen limit = 1 means 1+current+1 pages) camera photos into Bitmaps without Glide? Again, assuming this is full resolution, it should be possible to store 3 screen size amount of bytes in memory with or without Glide, but you have to guide Glide to not load at full resolution.
You can load smaller sized image via .thumbnail(), it accepts a full Glide.with... not including .into() OR there's a shorthand parameter which is just a percentage (in 0.0 ... 1.0), try the latter first. It should decode the image much faster, especially with a really small number like 0.1 and then when higher quality one finishes it's replaced.
So the simpler option is to add .thumbnail() to your current load. A more convoluted one involves to start loading a lower resolution image with .sizeMultiplier() at the same time the Fragment's view is created and then start loading the high resolution one when the ViewPager has changed page. This helps with peeking the pages.
Alternatively you can just use a .placeholder() while the image is loading so it's not empty space, but "something" there.
Regarding using ARGB_8888 (32 bit per pixel): if you increase the memory consumed by Bitmaps (compared to RGB_565 (16 bit per pixel) don't expect to get run out of memory later. Once you get it working with 565 you can try increasing, but until then it's futile trying.
Also look around the Glide issues, maybe you find something helpful.
I am looking for a library or some idea on how I can load an image by parts from the disk or a url straight to the disk then in parts again to the ram. So the two ways I see that this can be done is loading the whole image onto the disk by reading and writing it from the url directly using the ram only for the buffer then when the image is on the disk some how creating bitmaps of only parts of the image, that way I DO NOT load it all and putting those in a ListView.
The issue is that I am dealing with extremely long images (10K + pixels long w/ a width of 4-600) and they hog up lots of ram if loaded all in one bitmap. I can not just scale them down like the Google android tutorial does in the handling large bitmaps section as that results in a width too small to deal with. So if I can somehow generate small bitmap slivers on the disk I can use them by loading them in a ListView preventing loading the image as a whole into ram.
Right now I am breaking the long images into slivers from a bitmap and I realized that that isn't really accomplishing what I am trying to do as the whole image is loaded into a bitmap in memory and is then broken up, then GC (So I am using up the full abmount of ram anyways). I am testing on a new top of the line android phone and the app works fine, but the heap size reaches 80mb+ with the larger images temporarily in ram as it breaks down the bitmap and this will be an issue with devices that have lower heap limits
You can try using this class, support from 2.3 http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html
If you are using Java you can work with InputStreamReader and OutputStreamWriter. Method read() accepts the buffer as one of the parameters, its length can be anything suitable. Of course you can create a new file for each buffer being written.
Is it what you're looking for?
edit
well its not. have you seen this Strange out of memory issue while loading an image to a Bitmap object ?
If you have control over the server from where you are fetching data, throw in another field in your response, such that it returns a thumbnail/smaller image. The server can then generate the required thumbnails for you, without you bothering about it.
Decoding bitmaps on the fly might be expensive, most of the times. If you can't change anything on the server, download and save the images, and after saving, generate their corresponding thumbnails and save them as well. In your list, use the thumbnails. Also, save the information about which images have been cached, and whose thumbnails you already have. This might look like a lot of work, but depending on the use case, this can be a better approach dealing with large images.
Problem with downsizing?
Well, you can come up with some kind of logic as to generate thumbnails, based on the original size of the image. For longer(vertically long images), you could use BitmpapRegionDecoder (http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html) as #Binh Tran has suggested.
Try maybe encoding the image to make the size small.
I have a bunch of images that are displayed in a list view (one per row). The image file I have is a pretty large resolution so I'll need to scale it down to be shown in the list view.
So, my questions is...is it faster to store two images (one regular and one thumbnail) so nothing has to be scaled on the fly OR is it faster to simply allow android to scale this larger image upon list creation?
Thanks!
Nick
The best approach depends on your particular use case. If you really want to resize the image to a thumbnail level (e.g. 150x150) and don't plan to have more than about a 30 images to display, the best approach would be to just keep a cache of the Bitmaps in memory without writing them to disk (at a cost of about 3 MB of memory for the 30 images). This way your ListView won't stutter while you scroll images in and out of the screen. You can of course keep the full version and display that when the user taps on the ListView item.
If you want to display a higher resolution version in the ListView, then you might have to get creative. Remember that you don't have much time to render each individual ListView cell to keep a smooth scrolling experience, and that decoding a large image file will take time, even if you're downsampling it and resizing it; the initial decoding time is the same, the only difference is the size in memory. In this case then it might be convenient to keep both the original and the smaller size version in disk to reduce the stuttering as you scroll.
Store two images to avoid your users having to wait to get the fullsize of every image even though all they're looking at are thumbnails, unless the images are bundled in with the app (i.e. not downloaded on the fly).