I'm trying to adapt the code in google developer guides to resize a large image obtained from HTTP.
In order to resize the image, I have to process it once (using Bitmapfactory.decodeStream) to determine its original height and width. Then, I have to run Bitmapfactory.decodeStream again in order to resize it. THe problem with this approach is that I cannot use the same stream twice.
If I do, the second called to decodeStream returns null.
I thought about trying to clone / copy the stream first so that I would have two copies to work with. However, this uses up memory, which was the problem I was trying to solve by resize the image, in the first place.
Just use the Bitmap returned by Bitmapfactory.decodeStream() for the resize operation. You do not need to decode it twice. You have it already.
Bitmap b = Bitmapfactory.decodeStream(/* your InputStream */);
// get original dimensions from b
int h = b.getHeight();
int w = b.getWidth();
// resize b to half (actually quarter) size
Bitmap resizedBitmap = Bitmap.createScaledBitmap(b, w/2, h/2, false);
Related
I need to crop images from Facebook, Instagram or the device itself. At the end of the user flow (where he selects them), all the said images should be send to the server. But all in the same width/height ratio (e.g. 3/2), therefore I need to crop the images manually.
Here a simple example of what's going on in my upload service and the cropping:
Bitmap bitmap = ImageLoading.getLoader(UploadService.this).loadImageSync(path);
//crop calculations
Bitmap croppedBitmap = Bitmap.createBitmap(bitmap, x, y, w, y);
String croppedPath = saveCroppedImage(croppedBitmap);
I then read this file from the filesystem and encode it to BASE64 to send it to the backend.
When I have to do this for multiple images, I regularly get an OOM Exception. Maybe I'm going at this the wrong way.. Cropping on the server side is really the last thing I want to do, because then the backend would have to query Facebook or Instagram.
Loading a scaled version of the bitmap isn't an option, I'm not displaying it on the device.
I have tried:
System.gc()
Bitmap.recycle();
Should I really use largeHeap in my manifest?
note: I'm using Universal Image Loader for the user flow as well, that's why I'm using it in the upload service too.
I think you should add largeHeap = true in the AndroidManifest file.
Secondly, If you are using Universal ImageLoader, you can download the bitmap in smaller size. Please have a look at this code:
ImageSize targetSize = new ImageSize(80, 50); // result Bitmap will be fit to this size
Bitmap bmp = imageLoader.loadImageSync(imageUri, targetSize, options); // This bitmap will be of specified size.
You can further read about Universal Image Loader by following the link.
I'm using the Android Camera2 API to take still capture images and displaying them on a TextureView (for later image editing).
I have been scouring the web for a faster method to:
Decode Camera Image buffer into bitmap
Scale bitmap to size of screen and rotate it (since it comes in rotated 90 degrees)
Display it on a texture view
Currently I've managed an execution time of around 0.8s for the above, but this is too long for my particular application.
A few solutions I've considered were:
Simply taking a single frame of the preview (timing-wise this was fast, except that I had no control over auto flash)
Trying to get instead a YUV_420_888 formatted image and then somehow turning that into a bitmap (there's a lot of stuff online that might help but my initial attempts bore no fruit as of yet)
Simply sending a reduced quality image from the camera itself, but from what I've read it looks like the JPEG_QUALITY parameter in CaptureRequests does nothing! I've also tried setting BitmapFactory options inSampleSize but without any noticeable improvement in speed.
Finding some way to directly manipulate the jpeg byte array from image buffer to transform it and then converting to bitmap, all in one shot
For your reference, the following code takes the image buffer, decodes and transforms it, and displays it on the textureview:
Canvas canvas = mTextureView.lockCanvas();
// obtain image bytes (jpeg) from image in camera fragment
// mFragment.getImage() returns Image object
ByteBuffer buffer = mFragment.getImage().getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
// decoding process takes several hundred milliseconds
Bitmap src = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
mFragment.getImage().close();
// resize horizontally oriented images
if (src.getWidth() > src.getHeight()) {
// transformation matrix that scales and rotates
Matrix matrix = new Matrix();
if (CameraLayout.getFace() == CameraCharacteristics.LENS_FACING_FRONT) {
matrix.setScale(-1, 1);
}
matrix.postRotate(90);
matrix.postScale(((float) canvas.getWidth()) / src.getHeight(),
((float) canvas.getHeight()) / src.getWidth());
// bitmap creation process takes another several hundred millis!
Bitmap resizedBitmap = Bitmap.createBitmap(
src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
canvas.drawBitmap(resizedBitmap, 0, 0, null);
} else {
canvas.drawBitmap(src, 0, 0, null);
}
// post canvas to texture view
mTextureView.unlockCanvasAndPost(canvas);
This is my first question on stack overflow, so I apologize if I haven't quite followed common conventions.
Thanks in advance for any feedback.
If all you're doing with this is to draw it into a View, and won't be saving it, have you tried to simply request JPEGs that are lower resolution than maximum, and match the screen dimensions better?
Alternatively, if you need the full-size image, JPEG images typically contain a thumbnail - extracting that and displaying it is a lot faster than processing the full-resolution image.
In terms of your current code, if possible, you should avoid having to create a second Bitmap with the scaling. Could you instead place an ImageView on top of your TextureView when you want to display the image, and then rely on its built-in scaling?
Or use Canvas.concat(Matrix) instead of creating the intermediate Bitmap?
Currently im making an app to show images from SD card. Basicly you make an album and add pictures to it, be it from camera of the MediaStore picker.
I tried implementing 2 methods:
The standard gallery app with a custom BaseAdapter to return a view
A viewpager with custom PagerAdapter
I dont want to display a grid view, so it should go fullscreen right away. After that i want to disable swipe left and right and only listen for clicks.
Atm both methods work in portrait mode. When i switch over to landscape some images just drop
03-20 12:20:56.515: W/OpenGLRenderer(17398): Bitmap too large to be uploaded into a texture
followed by Out of memory errors. Stack overflow if full with problems about the OOM and the gallery, you should recycle the views to make them work because convertView is always null in the getView from BaseAdapter.
So i used a recycler for the views, i limit it to 2 views and portrait mode worked for method 1 (using the gallery). Landscape still gives me the same problem.
For method 2 (viewflipper) it handles the views by
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Wich is never called btw... However portrait mode works here. Landscape still crashes.
My method for getting the bitmap:
public static Bitmap getBitmap(Context ctx, int imageId, ImageView target) {
String file = getPath(ctx, imageId);
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file, bmOptions);
WindowManager mgr = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
int scaleFactor = 1;
if (mgr != null) {
// Get the dimensions of the View
int targetW = mgr.getDefaultDisplay().getWidth();
int targetH = mgr.getDefaultDisplay().getHeight();
Log.d(TAG, "Image width + height=" + targetW + "," + targetH);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
scaleFactor = Math.min(photoW / targetW, photoH / targetH);
} else {
Log.d(TAG, "Target is null");
}
// Get the dimensions of the bitmap
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
logHeap(ImageHelper.class);
Bitmap bm = BitmapFactory.decodeFile(file, bmOptions);
if (target != null) {
target.setImageBitmap(bm);
}
return bm;
}
Works fine, i know i use the window manager to get a screensize but that is because my ImageView is still size(0,0) when i inflate it. Afterwards i call
imgView.setLayoutParams(new Gallery.LayoutParams(
Gallery.LayoutParams.FILL_PARENT,
Gallery.LayoutParams.FILL_PARENT));
imgView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
Nothing seems to work...
Please note that i dont use drawables or any other resources included in the APK.
This gallery should be able to load pictures from SD card or from camera in whatever quality they were taken. Obvously the gallery should be able to handle as much images as there are in the directory.
Could someone please help me out with the following questions?
Is there any way to make the default Gallery go fullscreen right away and block out the grid view? This way i only need an adapter to provide images instead of making my own views. (maybe this solves the OOM crashes)
Is my Bitmap decoding function ok? Do i need to built in some hacks to catch the landscape changes?
Whats the best way to make the kind of gallery i need, using a viewpager or the gallery?
Does anyone have some sample code of a fullscreen gallery that doesnt crash?
This is an issue i faced a few times in the past. Google actually posted an article about this.
The problem is in the inSampleSize that u use to decode the image. Since different devices have different screens and memory sizes (VM Heap Sizes that may go from 12 to 64 or more Mb) you cant decode the images the same way in all of them. For example, an image in a 320x240 device with a 12Mb Heap Memory should u a given inSampleSize while a 1280x720 64Mb Heap Memory device should user another inSampleSize (a bigger one).
The article i attack shows an efficient way to render the images for a given height and width. Take a special attention to the last code segment, they "pre-open" the file and calculate the inSampleSize for a "full-decode" later on.
I would also like to add this recommendations.
Use the "getBitmap" function in an async way, call a new thread or an asyncTask (this is done because using the SD takes time and this will cause your application to hang, to be slow and give a bad impression on low-mid end devices.
Try to always use the same Bitmap object. Avoid creating multiple "Bitmap bitmap = new Bitmap()". You have to realize that loaded bitmaps are stored in ram and once assigned to a view its "harder" for the garbage collector to get them.
When you have finished using a Bitmap over make it null. This of this, imagine your have 12Mb max heap RAM. your app is using 8Mb now. Garbage collector starts and cant get any more unused RAM. you 10 Bitmaps fast never make them null after assigning the data to the ImageView and somehow you retain their reference. Taken that each bitmap take 500Kb of Ram your app will crash. Always make Bitmaps null after using.
Finally use the DDMS provided in the google platform-tools (you can access it with the eclipse to debug and see your memory usage, you can also force Garbage Collection to see how your app behaves).
I like to use the assets folder instead of the drawable folder (if it's not a nine-patch) because I can use multiple folders there. However the method I use to get an drawable required the cpu to do quiet a lot. For example: after adding 10 ImageViews need 10% CPU (I am using Android Assistent and Samsung TouchWiz TaskManager). I haven't notice it while I was writing a game. And now this game needs 40-100% CPU even if it isn't in the foreground.
That's the method I use to create an drawable:
public BitmapDrawable readAsset(path){
try{
inputStream = assetManager.open(path);
//get the Bitmap
desiredImg = BitmapFactory.decodeStream(inputStream, null, opts);
//resize for other screen sizes (scale is calculated beforehand)
scaleX =(int)(desiredImg.getWidth()/scale);
scaleY = (int)(desiredImg.getHeight()/scale);
//Bitmap to get config ARGB_8888 (createScaledBitmap returns RGB_565 => bad quality especially in gradients)
//create empty bitmap with Config.ARGB_8888 with the needed size for drawable
Bitmap temp = Bitmap.createBitmap(scaleX, scaleY, Config.ARGB_8888);
//Canvas to draw desiredImg on temp
Canvas canvas = new Canvas(temp);
canvas.drawBitmap(convert, null, new Rect(0, 0, scaleX, scaleY), paint);
//Convert to BitmapDrawable
BitmapDrawable bitmapDrawable=new BitmapDrawable(temp);
bitmapDrawable.setTargetDensity(metrics);
inputStream.close();
return bitmapDrawable;
}catch (Exception e) {
Log.d(TAG, "InputStream failed: "+e.getMessage());
}
return null;
}
The only thing I do in the app is adding some ImageViews in a RelativeLayout with this method:
private void addImageToContainer(int paddingLeft, int paddingTop) {
ImageView imageView = new ImageView(this);
imageView.setImageDrawable(assetReader.readAsset("test.jpg"));
imageView.setPadding(paddingLeft, paddingTop, 0, 0);
container.addView(imageView);
}
Probably the best thing for you to do would be to profile the execution with traceview, as this will give you a full understanding of where your app is spending most of its execution time. Then you can focus on optimizing that specific piece of code.
Just an educated guess, but I have a feeling that the majority of the wasted execution is not because you are pulling the images out of assets/ instead of resources, but all the scaling work being done afterwards (and from the looks of it, this is all being done on the main thread, so there's no concurrency to speak of).
I might recommend trying to leverage some of the BitmapFactory.Options (Docs link) available to you when you decode the asset. In particular, you should be able to do all the scaling you need with a combination of the inScaled, inDensity, and inTargetDensity options. If you pass these to your decodeStream() method, you could likely remove all the subsequent code used to resize the image before returning.
I need to load lots of big images (500 .png files) from the SD card to my app. Do I always have to convert images to Bitmap and make Bitmap files? I don't want to resize the Heap.
Is there another way to read the images from SD card?
If you're displaying them in a view, then you have to load them into memory in their entirety.
You didn't mention how large your images will get, but what we do in our photo gallery is to keep a list of SoftReferences to these bitmaps, so that the garbage collector can throw them away when they're not visible (i.e. when the view displaying them gets discarded--make sure that this actually happens, e.g. by using AdapterView). Combine this with lazy loading of these bitmaps and you should be good.
The internal representation of the image in your app is a collection of bits and bytes - not an image of any specific format (png, bmp, etc).
The image is converted to this internal representation when the image is loaded by the BitmapFactory.
It is usually not a good idea to load all the bitmaps at once, you will quickly run out of memory...
If your image's dimension is very big, you must to resize them before loading in to ImageView. Otherwise, even one picture can easily cause out of memory problem. I don't know how many images you want to display concurrently and how big they are. But I suggest you to resize them before displaying them.
To resize image and show it, you can use this code:
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(fileInputStream, null, bitmapOptions);
//reduce the image size
int imageWidth = bitmapOptions.outWidth;
int imageHeight = bitmapOptions.outHeight;
int scale = 1;
while (imageWidth/scale >= screenWidth && imageHeight/scale >= screenHeight) {
imageWidth = imageWidth / 2;
imageHeight = imageHeight / 2;
scale = scale * 2;
}
//decode the image with necessary size
fileInputStream = new FileInputStream(cacheFile);
bitmapOptions.inSampleSize = scale;
bitmapOptions.inJustDecodeBounds = false;
imageBitmap = BitmapFactory.decodeStream(fileInputStream, null, bitmapOptions);
ImageView imageView = (ImageView)this.findViewById(R.id.preview);
imageView.setImageBitmap(imageBitmap);
In my android project, I am using this piece of code to resize my HD wallpaper to review it.
Android Save And Load Downloading File Locally