Can someone suggest me a library that can do simplest operations like scale, crop, rotate without loading image fully into memory?
The situation: I need to scale image down from a very large size, but the scaled down image is still too large to be allocated in memory (if we use standard android tools). Since I only need to upload scaled down version, I thought of scaling it through native library and upload it through FileInputStream.
I've tried to use ImageMagic and it does the job, but performance is very poor (maybe there is a way to speed things up?)
Might want to check out OpenCV for Android
You can use the original Android Bitmap functionality by pulling the image into memory but allowing Android to sample the image before it is loaded.
For example:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap myBitmap = BitmapFactory.decodeStream(inputstream,null,options);
This will load your bitmap into memory with half the memory footprint of the full image. You can experiment with changing the inSampleSize to get a good fit for your application.
You can also calculate the sample size on the fly, if you know the final image size you are aiming for, you can get the current file size of the image before you load it into memory and calculate the sample size using the equation inSampleSize = OriginalSize/RequiredSize. Though sample size is best used when it is a power of 2, so you can make adjustments for this.
Edit:
A great example here https://stackoverflow.com/a/823966/637545
Related
I have some local images with unknown size. Format is jpeg.
The task is resize it to fit max size 1280x960 keeping original aspect ratio and save it to new file with low memory consumption. I found out very useful method in Fresco lib: JpegTranscoder.transcodeJpeg() operating streams. It has scaleNumerator param that rules downsampling. How can I get original bitmap size with Fresco without full bitmap loading to memory to calculate scaleNumerator value? Something like inJustDecodeBounds in android BitmapFactory.Options class
You should be able to use Fresco's BitmapUtil.decodeDimensions(...) for this.
I’m building an image-intensive social app where images are sent from the server to the device. When the device has smaller screen resolutions, I need to resize the bitmaps, on device, to match their intended display sizes.
The problem is that using createScaledBitmap causes me to run into a lot of out-of-memory errors after resizing a horde of thumbnail images.
What’s the most memory efficient way to resize bitmaps on Android?
This answer is summarised from Loading large bitmaps Efficiently
which explains how to use inSampleSize to load a down-scaled bitmap
version.
In particular Pre-scaling bitmaps explains the details of various
methods, how to combine them, and which are the most memory efficient.
There are three dominant ways to resize a bitmap on Android which have different memory properties:
createScaledBitmap API
This API will take in an existing bitmap, and create a NEW bitmap with the exact dimensions you’ve selected.
On the plus side, you can get exactly the image size you’re looking for (regardless of how it looks). But the downside, is that this API requires an existing bitmap in order to work. Meaning the image would have to be loaded, decoded, and a bitmap created, before being able to create a new, smaller version. This is ideal in terms of getting your exact dimensions, but horrible in terms of additional memory overhead. As such, this is kind-of a deal breaker for most app developers who tend to be memory conscious
inSampleSize flag
BitmapFactory.Options has a property noted as inSampleSize that will resize your image while decoding it, to avoid the need to decode to a temporary bitmap. This integer value used here will load an image at a 1/x reduced size. For example, setting inSampleSize to 2 returns an image that’s half the size, and Setting it to 4 returns an image that’s 1/ 4th the size. Basically image sizes will always be some power-of-two smaller than your source size.
From a memory perspective, using inSampleSize is a really fast operation. Effectively, it will only decode every Xth pixel of your image into your resulting bitmap. There’s two main issues with inSampleSize though:
It doesn’t give you exact resolutions. It only decreases the size of your bitmap by some power of 2.
It doesn’t produce the best quality resize. Most resizing filters produce good looking images by reading blocks of pixels, and then weighting them to produce the resized pixel in question. inSampleSize avoids all this by just reading every few pixels. The result is quite performant, and low memory, but quality suffers.
If you're only dealing with shrinking your image by some pow2 size, and filtering isn't an issue, then you can't find a more memory efficient (or performance efficient) method than inSampleSize.
inScaled, inDensity, inTargetDensity flags
If you need to scale an image to a dimension that’s not equal to a power of two, then you’ll need the inScaled, inDensity and inTargetDensity flags of BitmapOptions. When inScaled flag has been set, the system will derive the scaling value to apply to your bitmap by dividing the inTargetDensity by the inDensity values.
mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(),
mImageIDs, mBitmapOptions);
Using this method will re-size your image, and also apply a ‘resizing filter’ to it, that is, the end result will look better because some additional math has been taken into account during the resizing step. But be warned: that extra filter step, takes extra processing time, and can quickly add up for big images, resulting in slow resizes, and extra memory allocations for the filter itself.
It’s generally not a good idea to apply this technique to an image that’s significantly larger than your desired size, due to the extra filtering overhead.
Magic Combination
From a memory and performance perspective, you can combine these options for the best results. (setting the inSampleSize, inScaled, inDensity and inTargetDensity flags)
inSampleSize will first be applied to the image, getting it to the next power-of-two LARGER than your target size. Then, inDensity & inTargetDensity are used to scale the result to exact dimensions that you want, applying a filter operation to clean up the image.
Combining these two is a much faster operation, since the inSampleSize step will reduce the number of pixels that the resulting Density-based step will need to apply it’s resizing filter on.
mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth * mBitmapOptions.inSampleSize;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);
If you're needing to fit an image to specific dimensions, and some nicer filtering, then this technique is the best bridge to getting the right size, but done in a fast, low-memory footprint operation.
Getting image dimensions
Getting the image size without decoding the whole image
In order to resize your bitmap, you’ll need to know the incoming dimensions. You can use the inJustDecodeBounds flag to help you get the dimensions of the image, w/o needing to actually decode the pixel data.
// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;
//now go resize the image to the size you want
You can use this flag to decode the size first, and then calculate the proper values for scaling to your target resolution.
As nice (and accurate) as this answer is, it's also very complicated. Rather than re-invent the wheel, consider libraries like Glide, Picasso, UIL, Ion, or any number of others that implement this complex and error prone logic for you.
Colt himself even recommends taking a look at Glide and Picasso in the Pre-scaling Bitmaps Performance Patterns Video.
By using libraries, you can get every bit of efficiency mentioned in Colt's answer, but with vastly simpler APIs that work consistently across every version of Android.
I would be very grateful if someone could confirm that I have solved the below problem correctly or if there is an alternative solution?
I have an app that loads a large image (e.g. 800*1720 pixels) into memory and displays it in a scrollview. The image is a floor plans for a museum and I wanted a simple map like experience of scrolling and zooming. The image loaded fine on older devices, but caused an out of memory error on a Samsung Galaxy S3.
Looking at the LogCat messages it turned out that in creaing the bitmap 22MB was being allocated for the bitmap instead of 800*1720*4 = 5.5MB. Essentially 4x as much memory was being allocated as required by other devices and pushing the memory usage over the 50MB heap size.
The recommended solution to this problem is to use the BitmapFactory.Options.inSampleSize option to reduce the resolution of the image loaded and have it require less memory. However, this reduces the quality of the image, and I actually want to display it at it's full size in the original quality as works fine on older devices.
After much head scratching I concluded that the issue was that the pixel density on the S3's WXGA screens is 2.0 and thus for each pixel in the image, the bitmap was actually allocating 4 pixels. With a bit of trial and error I discovered I could prevent this happening by setting options.inScaled = false;
http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inScaled
On the way, I also realised that I could cut my memory usage in half to 2.7MB by using a lower fidelity colour depth of 2 pixels instead of 4 pixels by setting options.inPreferredConfig = Bitmap.Config.RGB_565;. For my floorpans this didn't effect the visible image quality.
The final code was thus:
String uri = "drawable/floorplan";
int imageResource = getResources().getIdentifier(uri, null, getPackageName());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inScaled = false;
Bitmap bm = BitmapFactory.decodeResource(getResources(), imageResource, options);
IVfloorplan.setImageBitmap(bm);
When displaying this bitmap you need to scale it back up. To work out the scaling you can obtain the pixel density from:
float density = getResources().getDisplayMetrics().density;
I reduced memory usage for the bitmap from 22MB to 2.7MB, which in a 50MB heap is significant.
The screen of the S3 has a really high res, so it's quite understandable. Also, if the image is in the drawable folder, it could be getting upscaled automatically. There might be ways to turn that off. Even if your image size doesn't chance, the OS has to allocate a buffer to accommodate the image, also possibly one to accommodate showing it on the screen.
An alternative is using tiling, which is used in games like SNES games. This is also how they handled lots of graphics without running out of RAM. Evidence shows that this is how Google Maps has a map of Planet Earth. You basically chop up the large image into tiles and show the tiles on the screen as you are panned to them (of course, maybe 1 extra tile on each side).
Even though this is post-Honeycomb, where Google put in code to better manage Bitmap allocations, be careful with Bitmap allocation. It's more like a C program than a Java program, and it's a good idea to manage it like one. It's very easy to run out of heap space when using Bitmap objects in Android
Simple example:
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = scale;
Bitmap bmp = BitmapFactory.decodeStream(is, null, opts);
When I'm passing scale value not equal to power of two, bitmap is still scaled by closest power of 2 value. For example, if scale = 3 then actual scale become 2 for some reason. Maybe it's because I'm using hardware acceleration?
Anyway, how can I scale bitmap by non power-of-2 value without allocation memory for full bitmap?
P.S. I know, that using power of two is much faster, but in my case time isn't such critical and I need image scaled exactly by provided scale (otherway it's become too big or too small) - I'm woking with image processing, so big image itself not such a problem (ImageView scales it to required size), but it takes extra time to apply some filter for example.
If you read the documentation for inSampleSize:
Note: the decoder will try to fulfill this request, but the resulting bitmap may have different dimensions that precisely what has been requested. Also, powers of 2 are often faster/easier for the decoder to honor.
You are not guaranteed exact dimensions. Since memory sounds like it is a concern, I would use your current method to get the image to larger than what you need but something that fits better with your memory requirements than the source image. Then use a different method like Bitmap.createScaledBitmap to get it to your exact dimensions.
I am joining two images using the code below but it throws an OutOfMemory error my images are around 1MB each.
private Bitmap overlayMark(String first, String second)
{
Bitmap bmp1, bmp2;
bmp1 = BitmapFactory.decodeFile(first);
bmp2 = BitmapFactory.decodeFile(second);
if (bmp1 == null || bmp2 == null)
return bmp1;
int height = bmp1.getHeight();
if (height < bmp2.getHeight())
height = bmp2.getHeight();
Bitmap bmOverlay = Bitmap.createBitmap(bmp1.getWidth() + bmp2.getWidth(), height,
Bitmap.Config.ARGB_8888);// Out of memory
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(bmp1, 0, 0, null);
canvas.drawBitmap(bmp2, bmp1.getWidth(), 0, null);
bmp1.recycle();
bmp2.recycle();
return bmOverlay;
}
Update: I tried below two answers but it still not allwoing me to create bitmap of such big size the problem is that the resultant bitmap is too large in size around 2400x3200 so its going out of memory.
How can I join large images without running out of memory?
Without loading the image into memory, you CAN get the size of the image, using inJustDecodeBounds. The Bitmap returns null, but all the parameters are set. You can scale down the image accordingly.
If your JPEG images are 1 MiB each, conversion to a BMP will take a lot of memory indeed. You can easily calculate its BMP equivalent by the dimensions of the image. Conversion of such a large image is expected to crash indeed. Android limits its apps to 16 MiB VM only.
Also use RGB_565 instead of ARGB_8888.
So your only solution is:
(a) To use BitmapFactory.Options.inSampleSize to scale down the image
or
(b) Use Android NDK where the 16 MiB limit isn't there.
I use this simple rule of the thumb:
the heavy lifting (both memory/CPU) is done on the server.
So write some servlet that takes the image, resizes it to a specified dimension (probably reduces the pixel depth too) and returns the result.
Piece of cake and it works on any mobile device you need.
Good luck!
I think a solution sort of like Sumon suggests might work.
Figure out the size of the final
image based on what will fit on the
screen.
Get the size of the first image using
the inJustDecodeBounds technique.
Figure out the size of the first
image in the final image. Calculate
re-sizing parameters.
Resize image, loading into memory.
Write resized image back to disk.
Recycle the bitmap. (This will help
when resizing the 2nd image)
Repeat for the second image, only you
can skip the writing to disk part.
Load first image.
If you only need to display, then just do that. If not then you can combine into a single bitmap at this point and write to disk. If this is the case, it may be difficult because you wil have essentially 2x the screen size in memory. In that case I would recommend resizing smaller. If you can't go smaller, then you will have to go the NDK route, thought I'm not sure how much that will help. Here's an amusing intro to the NDK and JNI. Finally, I would highly recommend developing this using a phone running Android 2.3+ since its use of heap-allocated bitmaps will make debugging much easier. More about those here.
It's not necessary that the space taken by in-memory representation of bitmaps correspond closely with file size. So even if you have 3mb memory available to jvm, you might still get OutOfMemoryException.
Your code is creating three in-memory images simultaneously. If you can find the size of both images without reading the complete files, you can modify the code in a way to have only one of the source images in memory at a time. If even that doesn't prove to be sufficient you might need some sort of streaming method of reading the images.
you may get some idea from here.
Are you trying to display this super large image or are you just trying to save it?
If your trying to display it. Cut the images into tiles. Then only display the tiles that are being viewed. If the user zooms out you need to reduce the size of the bitmap before showing the whole thing.
If your trying to save it, try saving it in sections to the same file by cutting the image up.
Loading 2 1m files in memory then creating a 2m file leaves you with 4M in memory for your images alone. Dynamically loading and unloading the memory solves this issue similar to tiles on Google maps or dynamic zooming in other map oriented solutions.
If you need to return that huge 2400x3200 bitmap as your result, there is no way to actually realize this goal. The reason is that 2400*3200*4 bytes ~ 30 Mb! How can you hope to implement this method, when even you can't even fit the return value into your limited heap space (ie 16Mb)?
And even if you used 16-bit color, it would still fail because you would end up using about 15MB, which would not leave you enough space for the language run time.