I want to load images in Android, but if the image is just too large I wanted to resize it prior to loading . I learned that one can use the BitmapFactory to get just the size of the image, then one can figure out an appropriate scaling factor and use BitmapFactory to load the image with the required dimensions (per the very competent suggestions I found in this related thread).
So I tried that and it refused to work. I spent the last hour picking through the code trying to figure out why such a simple operation wasn't having any effect whatsoever (the scaling factor was being utterly ignored!)
Then I stumbled upon Android issue 3072... turns out this has been identified as broken for GIF files for well over two years. I realize GIF isn't exactly modern but it's still out there and in wide use (my test set has a lot of them, which is why it seemed uniformly broken until I found that bug report).
My question is, what can I use as an alternative to BitmapFactory that will correctly read and resize a GIF file when a scaling factor is employed without silently ignoring it? Memory constraints preclude loading large images directly for further processing, so this would need to be a streaming solution.
My other question is, how many others run into old, unfixed bugs like this and either don't realize it or worse, rely on the API to accurately perform the requested function and end up with memory leaks and other strange surprises?
I ended up using the solution posted here.
options.inSampleSize = 2;
bitmap = BitmapFactory.decodeStream(new URL(url).openStream(),null, options);
Bitmap scaledBitmap = scaleDown(bitmap, 1280, true);
bitmap = scaledBitmap;
Where scaleDown is:
public static Bitmap scaleDown(Bitmap realImage, float maxImageSize,
boolean filter) {
float ratio = Math.min(
(float) maxImageSize / realImage.getWidth(),
(float) maxImageSize / realImage.getHeight());
int width = Math.round((float) ratio * realImage.getWidth());
int height = Math.round((float) ratio * realImage.getHeight());
Bitmap newBitmap = Bitmap.createScaledBitmap(realImage, width,
height, filter);
return newBitmap;
}
Related
I'm trying to resize a bitmap using inDensity and inTargetDensity following #colt-mcanlis' instructions explained at 1, 2 and 3.
So far so good, good documentation, great video. The problem is that the resulting sizes for the image makes no sense to me.
For example if I use following values:
srcWidth is 11774px and srcHeight is 6340px
dstWidth is 1440px and dstHeight is 2392px
The code I'm using is:
options.inScaled = true;
options.inSampleSize = 8;
options.inDensity = srcWidth;
options.inTargetDensity = dstWidth * 8;
options.inSampleSize;
imageBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.image, options);
And the resulting image has width 70px and height 38px, instead 1440x2393.
I tried without using inSampleSize, and I get a very similar result. Then I assume the problem is with inTargetDensity and inDensity.
I went to the documentation and found the following:
inDensity
int inDensity
The pixel density to use for the bitmap...
As far as I know, to calculate a density I need a width, height and a display size but a display size doesn't make sense to me in this context, since I just want to calculate inDensity and inPixelDensity independent of a display size.
So, what am I doing wrong here ?
I was following Loading Large Bitmaps Efficiently by the book, but was running into the problem that the decoded bitmap ended up having way larger dimensions even than the original image (options.outWidth / options.outHeight).
I noticed that after the "decode bounds" step, inTargetDensity had a larger value than inDensity, and ultimately found that to be the cause of the larger decoded bitmap. Not sure exactly when playing with anything different on this fields would be useful...
But setting options.inTargetDensity = options.inDensity after the "decode bounds" step, worked for having the bitmap be decoded at the expected size (according to the inSampleSize you calculate).
Looking forward to the "more straightforward" API that Romain Guy announced in Google I/O (2018) :D
If you just want to resize an image while decoding, inSampleSize option is enough, but, because the aspect ratio of original and target images are not the same, you can't get the expected result through inSampleSize option directly, you need to do some extra crop operations after resizing. You can refer to the following guide for details:
http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/
I have no 2D graphics and gaming experience. I taught myself by hundreds of mistakes and tens of lost hours. I started with simple Dress Up game. I used Nexus 5x for development where a screen looked OK. When I finished one milestone I tried game on big Lenovo tablet and tiny Samsung Mini phone. It looked horribly.
An original vector PSD file looks perfect, PNG export does not have any issues either. But when I scale the picture it is bumpy. I know it is bitmap. But there is another picture I scale in other place and it looks fine (both pictures are 32 bit):
When I play some game from Google Play they never have scaling issues. How are they implemented? Do they use vectors? Is there some trick?
I put everything into assets folder and though it took 1 MB. I decompiled some apk and they do not have set of variants for each resolution. Though they scale pictures nicely.
Some source code snippets:
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.w = w;
this.h = h;
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
protected void onDraw(Canvas canvas) {
if (baseBitmap != null) {
double scale = Math.min((double) w / (double) figure.getW(), (double) h / (double) figure.getH());
figure.setScaleRatio(scale);
int sw = (int) (scale * figure.getW());
int x = ((w - sw) >> 1);
figure.setX(x);
paintDressPart(canvas, figure, figure.getMain());
if (displayedParts != null) {
for (DressPart dressPart : figure.getParts()) {
if (displayedParts.contains(dressPart.getId())) {
paintDressPart(canvas, figure, dressPart);
}
}
}
}
}
private void paintDressPart(Canvas canvas, Figure figure, DressPart part) {
double scale = figure.getScaleRatio();
int sh = (int) (scale * part.getH());
int sw = (int) (scale * part.getW());
int sdx = (int) (scale * part.getDestX());
int sdy = (int) (scale * part.getDestY());
scaledRect.set(figure.getX() + sdx, sdy, figure.getX() + sw + sdx, sh + sdy);
Rect partRect = part.getRect();
canvas.drawBitmap(baseBitmap, partRect, scaledRect, canvasPaint);
}
And if you want to see it yourself I preparred complete project on GitHub so you can download it and run it yourself:
https://github.com/literakl/DressUp
Update
Downscaling sucks as well. But thanks to Paavnet's comment I realized that Paint matters. See difference on a picture:
I ran demo app on 3,2" AVD image. The picture 278x786 is scaled to 107x303.
October Update
I rewrote the app to use resource folder instead of assets. Android scales pictures and then I rescale it again. Though it looks better than when I do not use Android resources scaling. Resources scaled picture looks usually better than unscaled nodpi / assets picture.
I found that mdpi works best. I even had xxhdpi pictures and it looked worse than mdpi. I think that even on xxhdpi device! But it may be a trouble of the picture that it was not painted well. Android resource scaling may smooth edges on lines.
I faced same issue .. that scaling not that smooth .. best standard method
Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);
You can find more optimized size-convertor in web .. but actually best solution I ended with use :
1st. Use Vector SVG resource .. this is 100% working with perfect quality. just be sure that SVG elements are compatible with android ( see : https://developer.android.com/studio/write/vector-asset-studio.html )
pros:
best quality
efficient size
editable easily
cons:
not all elements are supported.
Or
2nd. provide high resolution image (one is enough in xxxhdpi )
pros:
good quality
cons:
might lead to performance issue
I hope that may help,'.
Do they use vectors?
SVG/Vector is a handy solution but vector art requires extremely high precision, making it unsuitable for many art styles.It's easy to use vector art with basic shapes but it can be hard to add a simple detail with SVG symbolic styles although it can added easily with paint with png,jpeg. You can watch performance pattern videos on SVG where googlers suggest SVG for icon designs.Most games don't use it for rich and complex images.
So i won't go for SVG specially where small detailing matter a lot in games.
How are they implemented? Is there some trick?
One way is just publish the app with a single regular assets support (MDPI,HDPI) and if the screen density required high resolution then show a dialog to the user, asking him if he wants to download the high-resolution assets.
You can have a higher density images and scale it down at run-time based on phone density requirement using DisplayMetrics.Ideally you should keep the same aspect ratio.Fitting a game made for a 4:3 display looks hideous on a 16:9, but fitting a 16:9 on to a 16:10, nobody might notice.read for more.
You can use this way to easily get the height and width of screen(considering you are using SurfaceView as best practice )
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenHeight = metrics.heightPixels;
int screenWidth = metrics.widthPixels;
To get new width-height on rotation :
surfaceChanged(SurfaceHolder holder, int format, int width, int height)
That method gives you the width/height of your surface, so you could now use:
Bitmap.createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)
From the Bitmap class to re-size your images in proportion to the size of the surface you're drawing onto.You can look into some technique to find appropriate scaling factors.
OpenGL,kotlin can give you advantage for creating rich graphic app.With these you can control the memory and GPU performance which basically are the back-bone of most games plus you can take advantage of controlling the rendering more efficiently with GPU and native memory space plus there is lot more you can think of, it will take some time if you not familiar with these but once you get it they can do many magical tricks.
Webp will help you to reduce size of large images to a considerable difference.Should try webp. (suggestion , webp has a little issue for alpha channel of image on some devices but too good for image size compression)
Conclusion : Implements best practices using OpenGL, SurfaceView,NDK plus loading images efficiently using scaling factors docs link and should try this too.
you can also take a look into 9patch images where you can put constraints to just scale some portion of the image instead of whole.it's quite useful.
Update : As suggested , if you are going to use higher resolution image then also look into this and this to disable scaling or manually calculation scaling factor.Good Luck
Note: Guys feel free to edit this answer with valid info if you like.
Good morning everybody,
I have a big problem with my Android app regarding to memory usage. My app offers (at the moment) 22 card games. Each game is an activity with own specific layout. They are in res/layout folder and all contained views have width and height = 0dp. After loading a layout I calculate the max possible size of cards (view), according to screen size, preferred deck-type (7 types are available) and layout itself and I set the calculated size to each displayed cards (in the worst case there are 52 cards displayed at the same time). Card images (about 100kb for single image) are all in asset folder (assets/${deck_type}/a1.png ...assets/${deck_type}/a52.png).
So, each game has its layout with its specific card-size (known just at runtime). Now my problem is that after playing about 6/7 different games, I encounter an OutOfMemory while loading bitmaps or while loading a new game layout. If you change the deck-type, the OOM arrives about 3/4 games.
Initially I loaded all card images of preferred deck-type in a LruCache (and reload it only after user deck-type changing), and call
cardView.setImageDrawable(cardImages.get(card));
when cardView is displayed. But with this solution OOM arrives almost soon.
So I have kept this approach but using Bitmap (instead of Drawable), loaded with com.nostra13.universalimageloader.core.ImageLoader: little bit better but not well yet. So I've stopped to use a fully preloaded LruCache and sync load bitmap at needing, scaling them according to game-card-size
private void scaleImage(int newWidth) {
int width = scaled.getWidth();
int height = scaled.getHeight();
float scaleWidth = ((float) newWidth) / width;
float ratio = ((float) scaled.getWidth()) / newWidth;
int newHeight = (int) (height / ratio);
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
System.gc();
scaled = Bitmap.createBitmap(scaled, 0, 0, width, height, matrix, true);
}
and cache them in a LruCache, evicted at the end of game and in OnDestroy of my activity game I call
System.gc()
but seems has no effects.
More little bit better but OOM is not avoided.
I have also studied all five chapters https://developer.android.com/training/displaying-bitmaps/index.html
but I wasn't able to adapt them for my need.
How can I solve this problem? Do you know an alternative approach?
Thank you very very much.
I want to crop image without getting OutOfMemory exception.
it means i have x, y, width and height of cropped image and want to crop original image without bringing it to memory.
Yes i know that BitmapRegionDecoder is good idea but maybe the cropped image would be too large for bringing it to memory.
In fact i don't want copped bitmap, just want to write cropped image from source file to destination file.
EDIT : I want to save cropped image not just showing it in an ImageView
I want to save it in a new file without losing dimensions
This is the example
in this situation cropped image resolution is 20000x20000 and code below wont work cause of OOM:
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);
using inSampleSize to decrease the original picture size is good but the result i save is no longer 20000x20000.
How can i crop the 25000x25000 and save the 20000x20000 part of image in a file?
Simply put, it requires lots of low level programming and optimizations.
as you can see, lots of answers in this region are pointing to generic concepts of bitmap compression, etc which are indeed applicable in most issues but not specifically yours.
Also BitmapRegionDecoder as suggested in answers won’t work well. It sure prevents loading the whole bitmap in RAM but what about the cropped image? after cropping an image it gives you a giant bitmap which no matter what, gives you an OOM.
Because your problem as you described, needs Bitmaps to get written or get read from disk just as they get written or read from memory; something called a BufferedBitmap (or so) which efficiently handles the memory it requires by saving little chunks of a bitmap to disk and using them later, thus, avoiding OOM.
Any other solution which wants to tackle the problem with scaling only do half of the work. why? because cropped image itself can be too big for memory (as you said).
However, solving the problem by scaling isn’t that bad, if you don’t care about the quality of the cropped image compared to the quality user had seen when she was cropping it. that’s what the Google Photos do, it simply reduces the quality of cropped image, very simple!
I haven’t seen any BufferedBitmap classes around (but if there are, it would be awesome). They sure become handy for solving similar problems.
You can check Telegram messaging app which comes with an open-source implementation of image cropping facilities; you guess right, it handles all the similar nasty works with good old C... Hence, we might conclude that a good global solution (or better said, ONE OF THE SEVERAL APPLICABLE SOLUTIONS) appears to be low-level programming to handle disk and memory yourself.
I know my answer failed to give any copy-paste-ish solution to your problem but at least I hope it gave you some ideas my friend.
Did you checked BitmapRegionDecoder? It will extract a rectangle out of the original image.
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);
http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html
You can solve this using BitmapFactory. To determinate the original bitmap size without putting it in to memory, do the fallowing:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(..., options);
int originalImageWith = options.outWidth;
int originalImageHeight = options.outHeight;
Now you can use options.inSampleSize
If set to a value > 1, requests the decoder to
subsample the original image, returning a smaller image to save
memory. The sample size is the number of pixels in either dimension
that correspond to a single pixel in the decoded bitmap. For example,
inSampleSize == 4 returns an image that is 1/4 the width/height of the
original, and 1/16 the number of pixels. Any value <= 1 is treated the
same as 1. Note: the decoder uses a final value based on powers of 2,
any other value will be rounded down to the nearest power of 2.
Now it's not a perfect solution but you can do math to find what is the closest factor of 2 that you can use on options.inSampleSize to save memory.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
Bitmap bitmap = BitmapFactory.decodeResource(..., options);
BitmapRegionDecoder is the good way to crop big or large Images, but it's available from API 10 and above.
There is a class called BitmapRegionDecoder which might help you, but it's available from API 10 and above.
If you can't use it :
Many image formats are compressed and therefore require some sort of loading into memory.
You will need to read about the best image format that fits your needs, and then read it by yourself, using only the memory that you need.
a little easier task would be to do it all in JNI, so that even though you will use a lot of memory, at least your app won't get into OOM so soon since it won't be constrained to the max heap size that is imposed on normal apps.
Of course, since android is open source, you can try to use the BitmapRegionDecoder and use it for any device.
Reference :
Crop image without loading into memory
Or you can find some other way on below that might be helpful to you:
Bitmap/Canvas use and the NDK
I am trying to reduce the size of images retrieved form the camera (so ~5-8 mega pixels) down to one a a few smaller sizes (the largest being 1024x768). I Tried the following code but I consistently get an OutOfMemoryError.
Bitmap image = BitmapFactory.decodeStream(this.image, null, opt);
int imgWidth = image.getWidth();
int imgHeight = image.getHeight();
// Constrain to given size but keep aspect ratio
float scaleFactor = Math.min(((float) width) / imgWidth, ((float) height) / imgHeight);
Matrix scale = new Matrix();
scale.postScale(scaleFactor, scaleFactor);
final Bitmap scaledImage = Bitmap.createBitmap(image, 0, 0, imgWidth, imgHeight, scale, false);
image.recycle();
It looks like the OOM happens during the createBitmap. Is there a more memory efficient way to do this? Perhaps something that doesn't require me to load the entire original into memory?
When you decode the bitmap with the BitmapFactory, pass in a BitmapFactory.Options object and specify inSampleSize. This is the best way to save memory when decoding an image.
Here's a sample code Strange out of memory issue while loading an image to a Bitmap object
Are you taking the picture with the camera within your application? If so, you should set the picture size to a smaller one in the first place when they're being captured via android.hardware.Camera.Parameters.setPictureSize
Here is a similar question that I answered and show how to go about dynamically loading the proper image size.
out of memory exception + analyzing hprof file dump