I've got a program which is loading resources from the resource file (by setting android:src="#drawable/image" in the layout file.
The image is 800 x 1280 pixels. When the program runs it allocates 16,384,000 bytes on the heap which is 16 BYTES per pixel.
I assumed that it should default to RGB8888 which only uses 4 bytes per pixel. Anyone know how I can change this to use less memory?
Your images could be stored in an inappropiate folder. If you have high resolution images in a low resolution folder, let's say, the images are 800 x 1280 and you're storing it in drawable or drawable-hdpi, Android will scale them up to match xhdpi (although they are already in xhdpi, so to say). The scaled image occupies more memory.
Solution, put them in drawable-xhdpi.
you can import them using BitmapFactory, Options, and inSampleSize to reduce the memory footprint.
Also, that seems like a very high res image to display on a mobile screen so you could also reduce the size?
Check the documentation about bytes per pixel: http://developer.android.com/reference/android/graphics/Bitmap.Config.html
You can try using Color.RGB_565, but the image may lose quality.
Related
While adding a ViewPager and running into some OutOfMemory errors, I realized that my default method of ImageView.setImageResource(resId) was causing 9x more RAM to be allocated than need be. In my example, I had a 1920 x 1080 image which at native resolution was slightly bigger than the ImageView object it was placed in. I could expect some downsizing of the image but would not expect more than 12.6 MB to be allocated (1920 x 1080 x 4 bytes). However, 74.6 MB was allocated per image, and with 4 images loaded this quickly blew the VM memory budget away.
To solve this, I changed the method to the slightly longer code below - now each image is allocated exactly the native amount of memory expected (12.6 MB), and the images still load quickly and look great.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inSampleSize = 1;
Bitmap bm = BitmapFactory.decodeResource(getResources(), resId, options);
mImageView.setImageBitmap(bm);
For reference, my device is a Nexus 5 with 1920 x 1080, xxhdpi, 3x scaling factor (dp to pixels). I imagine that internally the OS is scaling the image up 3x3 (=9x) to match the scaling factor, but that does not make sense since the original image is at native resolution of the full screen size.
What is the reason that they are initially allocating so much memory, and is there a proper way of setting the image resource to avoid this memory waste?
Drawables in the drawable folder are treated as mdpi (i.e., 1x). Therefore the system will automatically upscale the image to the native density, growing it 3 times in size in each direction, for a total of 9x the memory usage.
You should move the image to drawable-xxhdpi if you want the system to use it as its native resolution and the system will downscale it for lower density devices.
If instead you want a fixed number of pixels on all densities (i.e., a different physical size on screen for different density devices), you can use the drawable-nodpi folder.
I have a flags.png file that's 24480x160. I put this file in the "drawable" folder and loaded it into a bitmap on an emulator that's 420dpi (xhdpi). I generated a heap dump and checked the hprof. The bitmap drawable was occupying 8.7 MBs. The heap size was 72 MBs with 56 MBs allocated.
Doing the same exercise with the image in the xxxhdpi folder, the bitmap drawable remains the same size, but the heap size becomes 28 MBs with 17 MBs used.
I understand that "drawable" is considered to be mdpi and that Android will do some scaling when loading the image. It seems to be doing the same thing with hdpi and mdpi drawables on an xhdpi device as well. What I don't understand is what exactly happening that's resulting in the same 8.7 MB image but with different heap sizes. More specifically, if it's going to result in the same 8.7MB image, why scale in the first place and cause a large heap increase?
In my android app, the drawable folder has a total size of 1.1 MB (28 compressed pictures)
is that the MAX amount of memory that will be used by my pictures, or is it calculated differently?
thank you :)
is that the MAX amount of memory that will be used by my pictures
No.
is it calculated differently?
Yes. Generally speaking, each image will consume 4 bytes of heap space per pixel, based on the image's resolution. IOW, you are calculating things based on the size of compressed images (PNG, JPEG) on disk. What matters is their size in memory.
The calculations also depend upon whether a given drawable is used as-is, or whether is is down-sampled or up-sampled based on a density conversion. For example, if you have a 512x512 image in res/drawable-mdpi/ named foo.png, and you load R.drawable.foo on an -xhdpi device, and there is no other version of foo.png (e.g., in res/drawable-xhdpi/), then Android will up-sample your 512x512 image so that it is the same on-screen size on the -xhdpi device. That will turn the image into a 1024x1024 image, quadrupling the heap space usage.
I calculate inSampleSize to use Bitmap decode method to resize large png files.
When creating a this new bitmap and Log info it's width and height with .getWidth() and .getHeight(), its pixels count has grown by 3 relative to its original size.
code: from line 121-204
https://github.com/abisai1221/android-bitmaps/blob/master/png%20to%20bmp
all help is greatly appreciated.
I would bet on screen density being applied. When you load bitmaps from resources, you have to put them in right drawable folders, because Android will automatically scale them to match your phone's screen density. Each folder should contain only images for one specific density. For example, if you have a high density screen in your phone, you should put images in drawable-hdpi folder to get them without scaling. If you're getting 3 times larger images, you probably have a very dense screen, like Nexus 5 or recent Galaxy S phone. To disable scaling, put images in drawable-nodpi folder.
I saw another post of resizing issues and came across the ".inScaled" field of BitmapFactory.options.
This executes by default to scale bitmap to target density.
I set this field to false to prevent it from executing and the images come out perfectly, tho Im still confused as to why it scales the image by a factor of 3.
I have an Android app with 4 versions of the same image, in each of these folders:
drawable-ldpi/
drawable-mdpi/
drawable-hdpi/
drawable-xhdpi/
The only difference between the images is the size, I simply took the original large image and scaled it down according to the formula:
120dp lpdi, 1 dp=0.75 px
160dp mpdi, 1 dp=1 px
240dp hdpi, 1 dp=1.5 px
320dp xhpdi, 1 dp=2 px
The problem is I intend to have over 100 images, so to reduce the file size can't I just put the xhdpi versions in the drawables/ folder instead of having 4 versions of each image, then Android can scale the image as required? What is the disadvantage of doing it this way? Is it simply a trade off between performance and file size?
You can just put the xhdpi versions in the drawable resources. Then in your layout/xml files specify the images dimensions in dp. Android will downscale your images accordingly. As long as you are downscaling the drawable resources quality of the image wont change.
having only the xhdpi resource will degrade A LOT your app performance. Plus on low-range devices (small screens and small memory) it's very likely that the VM will run out of memory and crash whilst processing those large bitmaps.
You can develop everything on your high end device and use a simple photoshop automation to create the other resources but it's better to have a bigger APK than your app not working on mid and low range devices.
Another option might be to export several APK (one for each screen type) and use the manifest to specify which screen sizes it does support and let the market filter the right APK to the right device.
You can. The disadvantage may be slightly worse image quality: consider for hdpi display you need to scale 240/320 times, that is, 0.75, or to speak e.g. in 1D pixel line you make up 3 target pixels out of 4 source pixels. Most of the time it will look very good, but I imagine some pixel art may be an exception. I guess this may be slower as well, and use more memory than it could, but I didn't measure that so I can't tell for sure.
P.S. I'm using this method (providing xhdpi resources only) myself too.