BitmapFactory.decodeStream and BitmapFactory.decodeFile behaviour with identical image resource - android

I have a strange behaviour by getting Bitmaps with the methods BitmapFactory.decodeStream(Resources res,int id) and BitmapFactory.decodeFile(String pathName). I want to set a bitmap to my appwidget remoteViews, it is an png image with 319x319px and 32kb, for my understanding, it is a very small image that should be handled easily.
Now, what´s happing is, if I put this image inside my drawables folder and want to load the bitmap with BitmapFactory.decodeResource, the app crashs and I get an error-message that the remoteViews exceeds the max bitmap memory usage.
What I find out is, that there is max memory usage to the following rule (from developer.google.com) :
The total Bitmap memory used by the RemoteViews object cannot exceed that
required to fill the screen 1.5 times, ie.
(screen width x screen height x 4 x 1.5) bytes.
Now to the strange part: If I load the identical image from sd card and decode it with BitmapFactory.decodeFile(), it works. Why is this happening?
The problem I face here is, that I have to set some default images, if no other one is on my folder in sd card. For this, I put them in to my drawables folder. If I scale them down to 119x119px, then it works and I get no error message. But this is very bad quality and looks totally blurred at the widget.
Can anybody explain, what exactly is the difference of the behaviour how the bitmaps are loaded? Is there any automatic scaling function in decodeFile() that is not in decodeResource()?
A part of code or the logcat output is irrelevant, so I don´t post it here, I just can´t understand this behaviour and don´t know how to handle it. Also, I searched a lot about this and read the API, but there is nothing I can find out.
EDIT
For now I fixed this with calculating the bitmap memory usage with the above formular and check the bitmap size like this:
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
double width = (double) metrics.widthPixels;
double height = (double) metrics.heightPixels;
double maximumUsage = width * height * 4.0 * 1.5;
double maximumImageSizeFaktor = (maximumUsage / 3) / 1.5 / 4;
double maximumImageSize = Math.sqrt(maximumImageSizeFaktor);
int maxSize = (int) maximumImageSize - 10; //just to get sure, minimize it for 10
return maxSize;
Then I know the maximal size for my bitmaps and can downsample or resize it. But this did not answer the strange behaviour.

If BitmapFactory is loading from drawables then it does also some scaling according the device screen. By loading from file that will not happen. If you put your images in assets folder it will not happen either.

Related

Create bitmap out of memory in android

I have the following code to create a canvas with a size of 8303 × 5540, but running that code produces a OutOfMemoryException.
scaledBitmap = Bitmap.createBitmap(8303, 5540, Bitmap.Config.ARGB_8888);
How can I resolve this problem?
Setting android:largeHeap="true" in AndroidManifest.xml helped me.
Well.. Creating a bitmap of that size, you would have to allocate about 183MB of memory. That will be a problem on most phones. You could try to set android:largeHeap="true" in your manifest, but still that will not give you enough memory on most phones.
If you are willing to accept a "subsampled" version of your image, and the image data is coming from file, you could take a look at http://developer.android.com/training/displaying-bitmaps/load-bitmap.html for loading subsamples of large images into memory. Basically, you can tell the BitmapFactory to load one out of every X pixels, thereby avoiding the requirement to have all 183MB of image data in memory.
http://codingaffairs.blogspot.com/2016/07/processing-bitmap-and-memory-management.html
Now here are tips which you can follow and can avoid out of memory exception in your Android Application.
Always use inSampleSize
Now what is inSampleSize ?
with the help of inSampleSize you are actually telling the decoder not to grab every pixel in memory, instead sub sample image.
This will cause less number of pixels to be loaded in memory than the original image. you can tell decoder to grab every 4th pixel or every second pixel from original image.
if inSampleSize is 4. decoder will return an Image that is 1/16 the number of pixels in original image.
so how much memory you have saved ? calculate :)
Read Bitmap Dimensions before loading into memory.
How reading bitmap dimensions before loading image into memory can help you avoid out of
memory error ? Let's Learn
use inJustBounds = true
here is technique with the help of which you can get image dimension beore loading it in memory
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Above code snippet will not give us any image/bitmap. it will return null for bitmap Object.
but it will definitely return width and height of that image. which is R.id.myimage.
Now you have width and height of Image. you can scale up or scale down image based on these factors:
ImageView size which will be used to display Image.
Available amount of memory. you can check available amount of memory using ActivityManager and getMemoryClass.
Screen size and density of device.
Use appropriate Bitmap Configuration
Bitmap configurations is color space/color depth of an Image. Default bitmap Configuration in Android is RGB_8888 which is 4 bytes per pixel.
If you use RGB_565 color channel which use 2 Bytes per pixel. half the memory allocation for same resolution :)
Use inBitmap property for recycling purpose.
Do not make static Drawable Object as it cannot be garbage collected.
Request large heap in in manifest file.
Use multiple processes if you are doing lot of image processing(memory intensive task) or use NDK (Native Development using c, c++)

Best way to resize and position bitmaps in a canvas so they look the same in different devices?

I am developing a game using a surface panel. I've done a lot of research about how to properly scale and position drawables in the canvas for multiple devices and I came up with a solution that is working fine on phones but has some flaws when I try it on tablets. I am aware that I can use different resources for tablets (and i might end up doing that) but let's assume for now that I don't want to do it, I want to use the same resources for every single different phone in the market.
All the resources that I have are located it in the hdpi folder, and they are properly sized for a 480x800 device.
My approach is similar to the one described here, please take a look on the explanation below, and I would like to know if there is a better solution for this problem!
I have a Galaxy S2 for testing my apps. So my first approach was to manually insert position everything directly in the canvas by trying and finding the best position for everything. Taking the first character position as an example:
draw_x = (float) (19);
draw_y = (float) (279);
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
When I first tested it in different devices, everything as a mess, out of scale. So digging around I thought about using the density for scaling the resources.
// I am dividing by 1.5 because my initial positions are on a high density device
// so when it goes for a medium density it should scale for 0.66 and a small density
// for 0.5 of my positions.
float scale = getResources().getDisplayMetrics().density /1.5;
draw_x = (float) (19) * scale;
draw_y = (float) (279) * scale;
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
And at first impression this worked like a charm. It all my characters were in the proper positions. But I noticed that if the device has a different scale widht/height compared to the Galaxy S2 that I am using the problems begin. Although everything was properly positioned part of the image was cut out of the screen, the canvas was calculated larger than the phone screen.
Galaxy S2 is 480x800. My background is also 480x800. When I tested it in the emulator on a small screen resolution 320x480 Android didn't scale my background correctly as I expected it to do so. Instead of scaling it for the right resolution it gave me a background larger than my canvas 320x533.
With some simple math we figure that 320x533 / 480x800 = 0.66. So instead of properly scaling the background in the canvas, it just scaled using the density of the devices.
So my workaround for this problem was the simplest I could think of. I know the resolution of my background, I know the resolution of the phone, so I can calculate the proportion I need and force a resize.
//Set the proportions for scaling in multiple devices
public void setProportions(float screenWidth,float ScreenHeight,Bitmap background){
this.heightProportion = ScreenHeight/background.getHeight();
this.widthProportion = screenWidth/background.getWidth();
}
public Bitmap scaleBitmaps(Bitmap bitmap) {
Bitmap out = Bitmap.createScaledBitmap(bitmap, (int) (bitmap.getWidth() * widthProportion),
(int) (bitmap.getHeight()*heightProportion), false);
return out;
}
That worked fine for the sizes of the drawables, so I just needed to do the same for the positions, using the scale and this new Proportion I was able to calculate using a fixed size background image
public float convertX(float x){
return x* scale * widthProportion;
}
public float convertY(float y){
return y* scale * heightProportion;
}
//calculate the positions applying the scale and the proportion
draw_x = convertX((float) (19));
draw_y = convertY((float) (279));
//draw the bitmap with the scaled position
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
Long story short, to properly position the drawables I manually set the desired position in my device, calculated a scale between the densities and a porportion between the background image size and the screen size.
To re-size the drawables I just used the proportion because android automatically applies the density scale.
I tested in several different phones and tablets and this approach works perfectly for phones. On tablets it gives me some minor mistakes in the re-sizing of the drawables.
Finally after all this, my question is, what is the problem with this solution? Can I make it work on every phone regardless of the size or there is a better solution for this?
Please note that this strict to Canvas. The same background is re-sized correctly for every phone if I use it in the XML layout. If I wasn't clear or I should give more information please let me know!
The first thing you have to know before solve this problem is about device phone running system. Though you suggest the phone will choose either hdpi or other versions, it depends on each phone running system.
A. Size fitting problem
The problem is how do you process the bitmap. Though you re-scale the bitmap with any math formula, the size of original bitmap will have different output for each different phone. To solve this, you have to set inScaled of bitmap to false.
B. Position fitting problem
Thus you have the problem in fitting the size of bitmap, the position of bitmap will synchronize the position depends on your phone screen size. You should define the background object and positioning the object x and y based on the background. For example if you want to put an object in the middle of phone screen no matter what phone we use it, the code must be `
canvas.drawBitmap(toDrawBitmap, background.getwidth()/2, background.getheight()/2, null);
to solve the fitting position problem.
Let me know what happen.

Something going wrong with drawBitmap - with source and dest rectangles

I have a game in which, during one of the levels, sheep can get blown up by mines. The explosion animation is controlled by having a png containing a 4x4 array of explosion images within a 512x515 png file... see below.
I then animate the explosion using the following code:
exp_bitmap_number_to_draw = (int)(time_since_death / 100);
if (exp_bitmap_number_to_draw < 16)
{
explosion_dst_rect.left = b2sx(sheep_x[i]) - sheep_radius * 5;
explosion_dst_rect.right = b2sx(sheep_x[i]) + sheep_radius * 5;
explosion_dst_rect.bottom = b2sy(sheep_y[i]) + sheep_radius * 5;
explosion_dst_rect.top = b2sy(sheep_y[i]) - sheep_radius * 5;
explosion_src_rect.left = 128 * (exp_bitmap_number_to_draw % 4);
explosion_src_rect.top = 128 * (exp_bitmap_number_to_draw / 4);
explosion_src_rect.right = explosion_src_rect.left + 128;
explosion_src_rect.bottom = explosion_src_rect.top + 128;
canvas.drawBitmap(explosion_bitmap, explosion_src_rect, explosion_dst_rect, null);
}
Where explosion_src_rect is a Rect and explosion_dst_rect is a RectF. b2sx() and b2sy() are functions which convert from the absolute coordinates on the sheep on the "playing field" to the coordinates on the screen - its simply adding an offset.
The code works perfectly on several phones I've tried, including a nexus S and a Galaxy S II. But just now a friend tried the code on a samsung galaxy tab 8.9 and found the explosions appeared goofey. He sent me this partial screen grab:
Any idea what could be causing this?
If resources are not available in the correct density, the system loads the default resources and scales them up or down as needed to match the current screen's density.
There are some situations in which you might not want Android to pre-scale a resource. The easiest way to avoid pre-scaling is to put the resource in a resource directory with the nodpi configuration qualifier. For example:
res/drawable-nodpi/icon.png. So you have to put your bitmaps into `res/drawable-nodpi/ folder.
Have a look at: http://developer.android.com/guide/practices/screens_support.html
the width of your destination rectangle, instead of being the size of one of your explosion frames, as you said, is being set to the width of the canvas / 4. Phones are typically of similar size, but the larger tablet has a much larger screen size - As it says in the documentation:
Draw the specified bitmap, scaling/translating automatically to fill the destination rectangle [...]
This function ignores the density associated with the bitmap. This is because the source and destination rectangle coordinate spaces are in their respective densities, so must already have the appropriate scaling factor applied.
So your bitmap, not scaled to the canvas size, is getting drawn starting with the src Rect, but expands beyond the bounds of the src because it's been told to fill the DST, ignoring the densities of the two locations.
As to solution, I'm not sure, exactly - would probably be better to just have images of all sizes raring to go in your /res. Depending on how you load the bitmap, you might be able to get the bitmap to scale, but then your fixed width translation across the bitmap would have to become more dynamic.
Hope that helps somehow :)
I had the same problem which I asked on here and the answer for me was to load my bitmaps like this, rather than the normal way:
getResources().openRawResource(imageId)

android (Advanced)Saving a Bitmap Canvas with DrawText but textsize calculation needed

I have a Canvas that i draw text on.
(this is quite advanced i think hope you can follow me)
See attached image below.
The functionality is that I can tap on the screen to change the textsize.
Tap left screen = smaller Font.
Tap right Screen = bigger Font.
I can then also move the text on the screen.
When textsize is ok and i have moved the text where i want it,
Then I want to save it back to the original Bitmap.
I use options.inSampleSize = 4; to initially load the Bitmap
The ImageView that have the Bitmap is of course smaller then the original Image.
Some kind of calculation is needed.
This tends to be quite difficult to do.
I have the options.inSampleSize = 4 Bitmaps Ratio.
It's 0.59, 0.69 something depending on Landscape or portrait.
Im playing around with that to somehow change the new BitmapsetTextSize
to look the same as the ImageView smaller Bitmap.
What could i do here?
I have a feeling that since one never know what size an image have.
I have to somehow scale/constrain the Loaded Bitmap Ratio to a fixed Ratio.
Then i need to using percentage or something to transfer the text location
to the bigger image.
I can kind of do that when it comes to initial
(red small ball on picture) location. Hence, the starting point of the text.
But i dont know how long the text is so im stuck so so speak and asking for advice
One way i tried was to divide paint.getTextSize() with the Ratio something like 0.59. That looked like a solution at first. But the image ratio is not fixed and the Font size is not fixed something else is needed.
Here are two pictures showing the problem.
On phone Bitmap:
The saved new Bitmap:
I'm not 100% clear that I understand what you mean, but here's a go. It sounds like you were close to the right approach. Instead of using a fixed ratio, you need to calculate the ratio that the image is scaled by to fit in the view on the phone, then you can scale the text by the inverse ratio. So in steps:
Measure the width of the original image (height would do just as well, but we just need one dimension)
Measure the width of the scaled image
Calculate ratio (ratio = original / scaled)
Let the user type their text
You can then get the text size using something like: float paintSize = paint.getTextSize();
For rendering on the final image, use paint.setTextSize(paintSize / ratio);.

Custom rendered Android app widget

I'm making an app widget for Android, which due to being composed of custom elements such as graphs, must be rendered as a bitmap.
However, I've run into a few snags in the process.
1) Is there any way to find the maximum available space for an app widget? (OR: Is it possible to calculate the dimensions correctly for the minimum space available in WVGA (or similar wide) cases?
I don't know how to calculate the maximum available space for an app widget. With a conventional app widget it is possible to fill_parent, and all the space will be used. However, when rendering the widget as a bitmap, and to avoid stretching, the correct dimensions must be calculated. The documentation outlines how to calculate the minimum dimensions, but for cases such as WVGA, there will be unused space in landscape mode - causing the widget to look shorter than other widgets which stretch naturally.
float density = getResources().getDisplayMetrics().density;
int cx = ((int)Math.ceil(appWidgetInfo.minWidth / density) + 2) / 74;
int cy = ((int)Math.ceil(appWidgetInfo.minHeight / density) + 2) / 74;
int portraitWidth = (int)Math.round((80.0f * cx - 2.0f) * density);
int portraitHeight = (int)Math.round((100.0f * cy - 2.0f) * density);
int landscapeWidth = (int)Math.round((106.0f * cx - 2.0f) * density);
int landscapeHeight = (int)Math.round((74.0f * cy - 2.0f) * density);
Calculating cx and cy gives the number of horizontal and vertical cells. Subtracting - 2 from the calculated dpi (e.g. 74 * cy - 2) is to avoid cases where the resulting number of pixels is rounded down. (For example in landscape mode on Nexus One, the height is 110, not 111 (74 * 1.5).
2) When assigning a bitmap to an ImageView which is used as part of the RemoteViews to view the image, there are 2 methods:
2.1) By using setImageViewUri, and saving the bitmap to a PNG file. The image is then served using an openFile() implementation in a ContentProvider:
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException
// Code to set up imageFileName
File file = new File(getContext().getCacheDir(), imageFileName);
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
This works, and it's the approach I'm currently using. However, if I set the scaleType of the ImageView to "center", which by the documentation is supposed to "Center the image in the view, but perform no scaling.", the image is incorrectly scaled. Setting the density of the bitmap to DENSITY_NONE or getResources().getDisplayMetrics().densityDpi doesn't make any difference when saving the bitmap to PNG, it seems to be ignored when the file is loaded by the ImageView. The result is that the image is scaled down, due to some dpi issue. This seems to describe the case:
http://code.google.com/p/android/issues/detail?id=6957&can=1&q=widget%20size&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars
Because it is not possible to use scaleType:center, the only way I've found to work is to set the layout_width and layout_height of the ImageView statically to a given number of dpis, then rendering the bitmap to the same dpi. This requires the use of scaleType:fitXY. This approach works, but it is a very static setup - and it will not work for resizable 3.1 app widgets (I haven't tested this yet, but unless onUpdate() is called on each resize, this is true).
Is there any way to load an image to an ImageView unscaled, or is this impossible due to a bug in the framework?
2.1) By using setImageViewBitmap directly. Using this method with the Bitmap.DENSITY_NONE setting on the bitmap, the image can be shown without scaling correctly. The problem with this approach is that there is a limitation to how large images can be set through the IPC mechanism:
http://groups.google.com/group/android-developers/browse_thread/thread/e8d84920b999291f/d12eb1d0eaca93ac#01d5c89e5e7b4060
(not allowed more links)http://groups.google.com/group/android-developers/browse_thread/thread/b11550601e6b1dd3#4bef4fa8908f7e6a
I attempting a bit of a hack to get past this issue, by splitting the widget into a matrix of images which could be set in 100x100 pixel blocks. This did allow for larger widgets to work, but ended up being very heavy and failed on large widgets (4x4).
Sorry for a very long post. I've tried to explain a few of the different issues when attempting to use a bitmap rendered app widget. If anyone has attempted the same and have found any more solutions to these issues, or have any helpful comments, this will be highly appreciated.
An approach that worked for us for a similar situation was to generate our graph as a 9-patch png, with the actual graph part as the scalable central rectangle, and the caption text and indication icons (which we did not want all stretched out of shape) and border effects, placed on the outer rectangles of the image.

Categories

Resources