Cropping and scaling bitmap efficiently 2018? - android

We are having difficulty with bitmaps in our product. Our goal is to take picture fast and display it/crop immediately. The problem is - image has to be in a good quality, has to be cropped well and fast. I personally tried the code below and it does reduce memory usage by ~ 2-3 times. Still, we would like to know more efficient way. Should we always transfer imageArray[] instead of actual bitmaps between our custom frames processing(using Fotoapparat library, because of its ability to efficiently display full screen camera View) and ImageViews? We are open to use Glide or any other tool to crop, or load bitmap if that would be more efficient. Our current code for image retrieving from cameraView frames(this reduces usage ~ 2-3 times):
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, width, height, null);
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 50, out);
byte[] imageBytes = out.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options);
options.inSampleSize = calculateInSampleSize(options, (int)(width/1.5),(int)(height/1.5));
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options);
Then we crop this Bitmap with method below (this seems inefficient in 2018):
resizedbitmap = Bitmap.createBitmap(original, 0,0,width, height);
Also, what about threads? Should we use AsyncTask, Executors or any other ways for cropping/showing bitmap? I personally always use RxJava, however our core product must be as lightweight as possible :)

Alternatives
If you're on API level 10+ you can alternatively use BitmapRegionDecoder which does the cropping and downsampling in one step, probably in native.
As a totally different approach you can also try converting the data directly to a Bitmap and then using a Canvas to draw some part of the original Bitmap onto a new cropped version, doing scaling via the Canvas' transformation matrix.
Quality
image has to be in a good quality, has to be cropped well and fast.
Your first step where you compress to quality=50, you're losing a lot of information. Then later when you create the new Bitmap, you do an up-scaling which also affects the quality; in my opinion cropping only makes sense if the resulting image is actually smaller.
Efficiency
Then we crop this Bitmap with method below (this seems inefficient in 2018)
Consider this: do you really need the Bitmap to be the exact size? It's probably much better to leave the Bitmap as the cropped size, have the smaller Bitmap in memory, upload the smaller Bitmap to the GPU and let the GPU rendering do the scaling. It's possible the View won't match the Bitmap size, so this will happen anyway.
Glide
Glide pretty much does the same thing at its core as your code (except the YUV bit). See Downsampler, the difference is that it works with many input sources, formats and API levels, hence the size difference.
our core product must be as lightweight as possible :)
Including an image loading library and forcing the user to include that as well goes against this. But at the same time do you really want to re-invent the wheel and write your own image loading library? For example Glide has a lot of pieces that can be replaced for custom behavior.
In my app I have a camera -> user crop selection -> cropped smaller Bitmap flow. I did something similar to yours, except using the disk instead of ByteArrayOutputStream, because the input can be huge and it would need to fit kind-of twice into memory for which there's no guarantee.

Related

Android still uses excessive memory when loading background image

I have read multiple posts like this about memory usage of background image.
my background image is 2048x1365 59KB JPEG; its uncompressed bitmap is 11MB
the background on the view for the particular device would be 480x605, so usage would be 1.1MB (480x605x4)
my app originally uses 12MB without background image
placing the image in drawable-nodpi/ and set it in the layout XML cause the memory usage to 23MB; so exactly base + BMP size
Using BitmapFactory to decode the image (moved to raw/) according to the advice results in 33MB of memory usage. (See codes below.)
Codes to set the background
View view = findViewById(R.id.main_content);
Rect rect = new Rect();
view.getLocalVisibleRect(rect);
BitmapFactory.Options options = new BitmapFactory.Options();
options.outHeight = rect.height();
options.outWidth = rect.width();
options.inScaled = false;
Bitmap backgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundId, options);
view.setBackgroundDrawable(new BitmapDrawable(getResources(), backgroundBitmap));
What goes wrong? What else can I do to shrink the memory usage?
The trick to getting BitmapFactory to give you a low-memory image is to fill in inSampleSize on the BitmapFactory.Options. This tells BitmapFactory to downsample the image as it loads, giving you a lower-resolution image, but one that is better tuned to whatever use you plan to put it to. You would need to calculate the desired inSampleSize that you want, based on the resolution of the ImageView (or whatever) that you are using the image for.
This sample app demonstrates loading some images out of assets/ with different inSampleSize values.
I have experienced this too but with much smaller images. I found out of that this was happening because I was using the same image size for all screen resolutions. I recommend you have different sizes of the same image and put them in the appropriate folders.

Crop image without OutOfMemory - Android

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

How to reduce the transparent bitmap size (not dimensions)

In my android application I am working on canvas bitmaps (actually it is SCanvas from Samsung SPen SDK, but it does not matter), as a rule 90-95% of area of such bitmaps is transparent, so I expected to have not too large bitmap size in KB. But seems like bitmap size (in KB) does not depend on whether it is a simple background or complex picture, so for example if I have two images (sorry, I am a new user and I can't post any images):
1) empty frame (1000x700 px, background is transparent, color border)
2) full frame (1000x700 px, background is transparent, color border, a lot of text inside)
the size of both bitmaps are about 1.3MB.
But if I convert these bitmaps in byte arrays, the size of the first array is about 11 times less than the size of the second array.
I have to store a lot of such images as BLOBs in DB and display them as ImageView bitmaps.
Question 1: if I need to display 20 ImageView objects with such 95% transparent images, is there any way to not use 20 * 1.3MB at the same time? To me it seems like it should be only 1 alpha layer + 20 "data" layers.
Question 2: is there any way to reduce size (in KB) of an image with transparency without losing too much quality? The only way I saw is to decode the image byte array with inSampleSize = 2 and then create a scaled bitmap to keep the original image dimensions, something like:
originalBitmap.compress(Bitmap.CompressFormat.PNG, 0, stream);
byte[] bitmapBytes = stream.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length, options);
int bWidth = options.outWidth;
int bHeight = options.outHeight;
options.inSampleSize = 2;
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
options.inJustDecodeBounds = false;
Bitmap scaledBitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length, options);
Bitmap finalBitmap = Bitmap.createScaledBitmap(scaledBitmap, bWidth, bHeight, true);
But the finalBitmap quality is unacceptable, no matter what options I used (inPreferredConfig, inDither).
Any other thoughts about the optimal way to handle the images with about 90% of simple transparent background and 10% of one-color "data"?
No matter what format you use, it will always end up as raw uncompressed (decoded) data before displaying, if it weren't decoded, you couldn't see it on the display. If you're worried about memory, try not to have all the images decoded all the time, just decode the one you have to show, and always release the others (don't keep a reference to them).

Android: combining loading and resizing of a drawable resource

Is there a way to make this more efficient:
Options mOpts = new BitmapFactory.Options();
Bitmap myNewBitmap=Bitmap.createScaledBitmap(BitmapFactory.decodeResource(mRes , AnimationLists.IntroArray[i],mOpts),250,250, true);
Are there some mOpts I could set which would resize upon load in one step? Are there alternative methods to consider which could produce a scaled bitmap faster or more efficient in terms of speed and memory use?

Drawing scaled bitmaps on a SurfaceView -- no antialiasing

I'm sorry if this topic has been brought before, but all my searches on the web and google groups did not help me.
I'm currently developing a little game with the Android SDK, and use hi-res bitmaps that I resize accordingly to match the device's resolution (letting the system do it for me is
not "crisp" enough).
I use a SurfaceView, on which I paint in one pass a canvas filling the whole surface. The paint uses setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)) to allow masking.
Beforehand, I retrieve various bitmaps -- which are resized at initialization with createScaledBitmap() and put in a cache -- and I apply the bitmaps with a paint on this canvas, before drawing this canvas on the SurfaceView.
My problem is, whatever I try, whatever paint settings I use (dithering, antialias, etc..), the resized bitmaps are not antialiased and the drawing present jagged edges. I tried everything.
The only little success I had was using inSampleSize to approach the desired scaled size and force a first pass of antialiasing, before invoking createScaledBitmap on the retrieved
hi-res bitmap, but it is not beautiful enough. I just can't allow to create multitudes of pre-sized bitmaps for every combination of resolution. What did I miss ?
Thanks a lot in advance
First when you load your bitmap you make sure that you don't lose any image quality by settings options to argb_8888:
Options options = new Options();
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap pic = BitmapFactory.decodeResource(getResources(), R.id.pic, options);
When you scale the bitmap turn on the filter:
pic = Bitmap.createScaledBitmap(pic, screenW, screenH, true);
However if one streaches the image too much inevitably it degrades in quality.
When you use paint you can improve quality but lose on speed with turning on ditherig and filtering:
Paint paint = new Paint();
paint.setFlags(Paint.DITHER_FLAG);
paint.setFilterBitmap(true);
Finally the entire activity window could be set on argb_4444 instead on argb_8888 (OS < 2.3). You can chage this if you instert this line before setContentView:
getWindow().setFormat(PixelFormat.RGBA_8888);
If it comes down to it, you can manually antialias without too much trouble. Just apply a simple lowpass filter (something like an NxN average) to the pixel data before asking the bitmap object to rescale itself.
you may clear canvas buffer by youself! such as follows:
canvas.drawColor(Color.TRANSPARENT, android.graphics.PorterDuff.Mode.CLEAR);

Categories

Resources