In my project I read a bitmap picture from assets directory and I want to change the quality of this picture and save it again. I am using this code:
FileOutputStream fos = new FileOutputStream(path);
image.compress(Bitmap.CompressFormat.PNG, 10, fos);
The problem is in second line whether I put 10 or 100, result is the same in terms of pixels of width, height and size of the picture. How can I resize or change a quality?
Thanks
Saving in a compressed format should not by itself change the image dimensions. The quality factor will merely effect the amount of data used to represent an compressed image having the same dimensions as the original.
If you want to rescale it too, you might want to use createScaledBitmap() first.
Related
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.
I am writing a function, which takes a bitmap, edits it and saves it.
But the problem is that the saved image is not having the same dimensions.
Can anyone help me fix it? I want the output image to be of same length and width as that of the original image.
This is my code -
public void bitTest(View v) throws IOException {
Drawable t=getResources().getDrawable(R.drawable.slider_touch,getTheme());//the image to be processed
Bitmap bit=((BitmapDrawable) t).getBitmap();//its bitmap
Bitmap finalImage=Bitmap.createBitmap(bit.getWidth(),bit.getHeight(),bit.getConfig());//creating another bitmap of same dimensions
Context context=getApplicationContext();
FileOutputStream fos = context.openFileOutput("test.png", Context.MODE_PRIVATE);//saving
finalImage.compress(Bitmap.CompressFormat.PNG,100, fos);
fos.close();
}
Here the original size is having dimensions of 288x192 but the output image has dimensions of 605x403.
Where am i wrong?
Also i have tried changing the quality, but it doesn't works.
When placing the image resources in drawable directory, the decoded image size depends upon the screen size of the running device.
Keep your drawable inside the folder drawable-nodpi rather than other drawable directories so that the image size remains preserved.
Also rather than using BitmapDrawable, you can directly decode a bitmap from resource using BitmapFactory.decodeResource()
I have a very large image file (over 25MB) with dimensions about 10000x10000 px. I need to downscale this image that file was not more then 20MB. I know about
BitmapFactory.Options o = new BitmapFactory.Options();
o2.inSampleSize = <scale>;
BitmapFactory.decodeSomething(...);
But if scale = 2 dimension was 5000x5000 px, that is good for me but more then android can decode and it cause OutOfMemory. So the question is how to downscale 10000x10000 px image file to 5000x5000 px image file? Is there a way to do it without using Bitmap to avoid OutOfMemory?
First you need to get metadata about the image,Free required space in mobile memory if needed, Create a bitmap and draw-able object then convert the original image in A888 or other format (the image format that occupy less memory) then take a size (height ans width) of required image and draw image on Draw-able class. for more information follow this link
http://voidcanvas.com/whatsapp-like-image-compression-in-android/
it worked for me. change the image size and compression format as per requirement.
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 have a PNG image file with 2236x971px dimensions as a resource.
I want to scale it down by a factor of two (to its half). However, when i use this code:
BitmapFactory.Options bo = new BitmapFactory.Options();
bo.inSampleSize = 2;
Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, bo);
the decodedBitmap.getWidth() and decodedBitmap.getHeight() show width: 1677, height:728 → Only 25% reduction in size instead of expected 50%. Why is that so?
I am running the code on API 19.
The reason is that, your resource gets loaded according to your screen metrics. Put your image in the drawable-nodpi Folder or open an input stream to your resource and decode that input stream.