Dealing with large bitmap onPictureTaken android - android

My app is an OCR app base on Tesseract. It will do OCR task from camera picture. Users can take many pictures and put them into an OCR queue. To get more accuracy, I want to keep high quality image (I choose min size is 1024 x 768 (maybe larger in future), JPEG, 100% quality). When users take many pictures, there are three things to do:
Save the image data byte[] to file and correct EXIF.
Correct the image orientation base on device's orientation. I know there are some answers that said the image which comes out of the camera is not oriented automatically, have to correct it from file, like here and here. I'm not sure about it, I can setup the camera preview orientation correctly, but the image results aren't correct.
Load bitmap from taken picture, convert it to grayscale and save to another file for OCR task.
And here is my try:
public static boolean saveBitmap(byte[] bitmapData, int orientation, String imagePath, String grayScalePath) throws Exception {
Boolean rotationSuccess = false;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap originalBm = null;
Bitmap bitmapRotate = null;
Bitmap grayScale = null;
FileOutputStream outStream = null;
try {
// save directly from byte[] to file
saveBitmap(bitmapData, imagePath);
// down sample
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
int sampleSize = calculateInSampleSize(options, Config.CONFIG_IMAGE_WIDTH, Config.CONFIG_IMAGE_HEIGHT);
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
originalBm = BitmapFactory.decodeFile(imagePath, options);
Matrix mat = new Matrix();
mat.postRotate(orientation);
bitmapRotate = Bitmap.createBitmap(originalBm, 0, 0, originalBm.getWidth(), originalBm.getHeight(), mat, true);
originalBm.recycle();
originalBm = null;
outStream = new FileOutputStream(new File(imagePath));
bitmapRotate.compress(CompressFormat.JPEG, 100, outStream);
// convert to gray scale
grayScale = UIUtil.convertToGrayscale(bitmapRotate);
saveBitmap(grayScale, grayScalePath);
grayScale.recycle();
grayScale = null;
bitmapRotate.recycle();
bitmapRotate = null;
rotationSuccess = true;
} catch (OutOfMemoryError e) {
e.printStackTrace();
System.gc();
} finally {
if (originalBm != null) {
originalBm.recycle();
originalBm = null;
}
if (bitmapRotate != null) {
bitmapRotate.recycle();
bitmapRotate = null;
}
if (grayScale != null) {
grayScale.recycle();
grayScale = null;
}
if (outStream != null) {
try {
outStream.close();
} catch (IOException e) {
}
outStream = null;
}
}
Log.d(TAG,"save completed");
return rotationSuccess;
}
Save to file directly from byte[]
public static void saveBitmap(byte[] bitmapData, String fileName) throws Exception {
File file = new File(fileName);
FileOutputStream fos;
BufferedOutputStream bos = null;
try {
final int bufferSize = 1024 * 4;
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos, bufferSize);
bos.write(bitmapData);
bos.flush();
} catch (Exception ex) {
throw ex;
} finally {
if (bos != null) {
bos.close();
}
}
}
Calculate scale size
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
When save complete, this image is loaded into thumbnail image view by UIL. The problem is the save task is very slow (wait some second before save complete and load into view), and sometime I got OutOfMemory exception. Is there any ideas to reduce the save task and avoid OutOfMemory exception?
Any help would be appreciated!
P/S: the first time I try to convert byte[] to bitmap instead of save to file, and then rotate and convert to grayscale, but I still got above issues.
Update: here is the grayscale bitmap process:
public static Bitmap convertToGrayscale(Bitmap bmpOriginal) {
int width, height;
height = bmpOriginal.getHeight();
width = bmpOriginal.getWidth();
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bmpOriginal, 0, 0, paint);
return bmpGrayscale;
}
The OutOfMemory exception seldom occurred (just a few times) and I can't reproduce it now.

Update:
Since you're still saying that the method takes too long time I would define a callback interface
interface BitmapCallback {
onBitmapSaveComplete(Bitmap bitmap, int orientation);
onBitmapRotateAndBWComlete(Bitmap bitmap);
}
Let your activity implement the above interface and convert the byte[] to bitmap in top of your saveBitmap method and fire the callback, before the first call to save. Rotate the imageView based on the orientation parameter and set a black/white filter on the imageView to fool the user into thinking that the bitmap is black and white (do this in your activity). See to that the calls are done on main thread (the calls to imageView). Keep your old method as you have it. (all steps need to be done anyway) Something like:
public static boolean saveBitmap(byte[] bitmapData, int orientation, String imagePath, String grayScalePath, BitmapCallback callback) throws Exception {
Boolean rotationSuccess = false;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap originalBm = null;
Bitmap bitmapRotate = null;
Bitmap grayScale = null;
FileOutputStream outStream = null;
try {
// TODO: convert byte to Bitmap, see to that the image is not larger than your wanted size (1024z768)
callback.onBitmapSaveComplete(bitmap, orientation);
// save directly from byte[] to file
saveBitmap(bitmapData, imagePath);
.
.
// same as old
.
.
saveBitmap(grayScale, grayScalePath);
// conversion done callback with the real fixed bitmap
callback.onBitmapRotateAndBWComlete(grayScale);
grayScale.recycle();
grayScale = null;
bitmapRotate.recycle();
bitmapRotate = null;
rotationSuccess = true;
How do you setup your camera? What might be causing the long execution time in the first saveBitmap call, could be that you are using the default camera picture size settings and not reading the supported camera picture size and choosing best fit for your 1024x768 image needs. You might be taking big mpixel images and saving such, but in the end need you need < 1 mpixles (1024x768). Something like this in code:
Camera camera = Camera.open();
Parameters params = camera.getParameters();
List sizes = params.getSupportedPictureSizes();
// Loop camera sizes and find best match, larger than 1024x768
This is probably where you will save most of the time if you are not doing this already. And do it only once, during some initialization phase.
Increase the buffer to 8k in saveBitmap, change the 1024*4 to 1024*8, this would increase the performance at least, not save any significant time perhaps.
To save/reuse bitmap memory consider using inBitmap field, if you have a post honeycomb version, of BitmapFactory.Options and set that field to point to bitmapRotate bitmap and send options down to your convertToGrayscale method to not need allocating yet another bitmap down in that method. Read about inBitmap here: inBitmap

Related

OutOfMemory when Bitmap.createBitmap & converting to Base64

Sometimes on some mobile devices, image converting to Base64 String get OutOfMemoryError because of enormous size if base64 string (when the original image & it's resize - not weight too much). Is there any way to get more modest size of string without Outofmemory exception? In my code I resize image & compress... but anyway the size of final Base64 String is large.
public String getBase64Image() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(getFileInst().getAbsolutePath(), options);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int currentWidth = options.outWidth;
int currentHeight = options.outHeight;
int maxSize = Math.max(currentHeight, currentWidth);
double diff = 1;
if (maxSize > maxAcceptableImageSize) {
diff = (double)maxAcceptableImageSize / maxSize;
}
Matrix matrix = new Matrix();
int rotate = getCameraPhotoOrientation(getImage().getContext(), getFileInst().getAbsolutePath());
matrix.preRotate(rotate);
Bitmap image;
Bitmap src = Bitmap.createBitmap(BitmapFactory.decodeFile(getFileInst().getAbsolutePath()), 0, 0,
currentWidth, currentHeight, matrix, false);
if (diff <= 1) {
image = Bitmap.createScaledBitmap(src, (int)(src.getWidth() * diff), (int)(src.getHeight() * diff), false);
} else {
image = src;
}
image.compress(Bitmap.CompressFormat.PNG, 75, baos);
byte[] byteArrayImage = baos.toByteArray();
String base64Str = "data:image/png;base64," + Base64.encodeToString(byteArrayImage, Base64.DEFAULT);
src.recycle();
image.recycle();
return base64Str;
}
You should scale the image before rotating it.
If you scale it down first, there are less pixels that need to be rotated, saving memory.
You should also place .recycle() calls directly after you're done using them, not all at the end of the function.
As a very very VERY last resort, you could add android:largeHeap="true" to your application in the manifest, which will most likely give you more available memory.
For specific details on largeHeap, read the documentation here:
https://developer.android.com/guide/topics/manifest/application-element.html

How to add texture from android to unity?

I know I can get it with Unity with these lines:
Texture2D texture = Resources.Load ("TextureTest") as Texture2D;
GameObject obPlane = GameObject.Find("Plane");
obPlane.renderer.material.mainTexture = texture ;
But it's not my goal. I want to do it from Android to Unity.
I've tried (in Java):
private boolean updateTexture(int IDTexture) {
Bitmap b = getBitMapFromFile("sample.bmp");
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, IDTexture);;
ByteBuffer bf = extract(b);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 256, 256, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, bf);
return true;
}
And in Unity I get the textureID with cTexture.GetNativeTextureID():
void Start () {
cTexture = new Texture2D (width, height,TextureFormat.RGBA32,false);
cTexture.Apply();
obPlane.renderer.material.mainTexture = cTexture;
}
if(GUI.Button(new Rect(a,b,c,c), "Button")) {
activity.Call("updateTexture", cTexture.GetNativeTextureID());
}
But when I press the button, nothing happens.
A few things to remember when doing texturing with plugins:
Any time something alters Unity's graphical state externally, these alterations must be performed from the same thread as the renderer runs on. This means that your calling function should originate in one of the graphical Monobehaviour methods such as OnPreRender() and OnPreCull() to name but a few. I cant find an explicit list in the docs, but it should be easy enough to work out which are relevant. MonoBehaviour Docs The script that these calls are performed from must also be attached to the main camera in the scene.
Any time the graphical state of the engine is altered externally (like you are doing here) you must call GL.InvalidateState() afterwards. The docs explain this well:
"This invalidates any cached renderstates tied to the GL context. If for example a (native) plugin alters the renderstate settings then Unity's rendering architecture must be made aware of that to not assume the GL context is preserved." - GL.InvaliateState Docs
Slightly off topic
Let me also add that I attempted something identical to you, and failed to get the texture to update at all. While I'm not sure why this is the case, i can provide a much less efficient, but working method if speed is not critical. If you do get this working, please let me know on the question I've asked here, I'll be sure to update this answer if i find a proper solution.
This method will take in a bitmap from a path, scale and/or compress it (Unity has a max texture size of 4096x4096) into a byte array, which Unity then reads into a texture.
byte[] m_image;
File inFile = new File(_fileName);
Bitmap bm;
int width, height;
height = width = 4096;
String logTag = "BitmapHandler";
//Use BitmapFactory to check that bitmap is not too large.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bm = BitmapFactory.decodeFile(inFile.getAbsolutePath(), options);
m_StartWidth = options.outWidth;
m_StartHeight = options.outHeight;
//If it is, calculate a sampleSize to reduce the image by
if (options.outWidth > 4096 || options.outHeight > 4096) {
options.inSampleSize = calculateInSampleSize(options, 4096, 4096);
}
else {
width = options.outWidth;
height = options.outHeight;
options.inSampleSize = 1;
}
options.inJustDecodeBounds = false;
bm = BitmapFactory.decodeFile(_fileName, options);
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, out);
m_image = out.toByteArray();
m_width = bm.getWidth();
m_height = bm.getHeight();
out.flush();
out.close();
}
catch (Exception e) {
m_image = null;
m_height = -1;
m_width = -1;
return null;
}
return m_image;
How sample size is worked out - scales the image by a power of two so that both width and height are smaller than 4096 pixels:
private int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
while (height / inSampleSize > reqHeight || width / inSampleSize > reqWidth) {
inSampleSize *=2;
}
}
return inSampleSize;
}
I'm sorry that I can't directly solve the problem, but i hope that the info I've given will be of use to you!

Out of memory when processing multiple large bitmaps

In the app I'm working on, part of the user's input is a series of images. Some of these might be 4MB large in their raw form. I resize and rotate them, then save them in the app's portion of the device memory for later use. The problem I'm experiencing is that I seem to run out of memory even though I recycle each Bitmap after it's saved.
Here's the main processing
private class SaveImagesTask extends AsyncTask<Long, Void, Void>{
#Override
protected Void doInBackground(Long... ids){
long id = ids[0];
Iterator<ImageButton> itImg = arrBtnImage.iterator();
Iterator<TextView> itLbl = arrLblImage.iterator();
while(itImg.hasNext() && itLbl.hasNext()){
String imgPath = (String) itImg.next().getTag();
String imgLbl = itLbl.next().getText().toString().trim();
String imgName = imgLbl.replace(" ", "_").replace(",", "_");
imgName += ".jpg";
if(imgPath != null){
/* Save resized version of image */
File dir = getApplicationContext().getFilesDir();
dir = new File(dir, "temp/" + Long.toString(plantId));
boolean madeDir = dir.mkdirs();
File path = new File(dir, imgName);
Bitmap toSave = getScaledBitmap(imgPath, IMAGE_MAX_SIDE_LENGTH, IMAGE_MAX_SIDE_LENGTH);
try{
BufferedOutputStream outStream = new BufferedOutputStream(new FileOutputStream(path));
boolean insertSuccess = toSave.compress(Bitmap.CompressFormat.JPEG, 90, outStream);
outStream.close();
}
catch(FileNotFoundException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
toSave.recycle();
}//if
}//while(more images to process)
}// method: doInBackground(params)
}// inner class: saveImages extends AsyncTask
And here's where I resize the image
private Bitmap getScaledBitmap(String picturePath, int newWidth, int newHeight){
/* Size */
BitmapFactory.Options sizeOptions = new BitmapFactory.Options();
sizeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(picturePath, sizeOptions);
int sampleSize = 1;
int rawHeight = sizeOptions.outHeight;
int rawWidth = sizeOptions.outWidth;
if(rawHeight > newHeight || rawWidth > newWidth){
/* Find the dimension that needs to change the most */
int heightRatio = Math.round((float) rawHeight / (float) newHeight);
int widthRatio = Math.round((float) rawWidth / (float) newWidth);
sampleSize = (heightRatio > widthRatio ? heightRatio : widthRatio);
}//if(raw image is wider or taller than it should be){reduce size so neither is too large}
sizeOptions.inJustDecodeBounds = false;//Load pixels for display.
sizeOptions.inSampleSize = sampleSize;//Set shrink factor.
Bitmap scaledBitmap = BitmapFactory.decodeFile(picturePath, sizeOptions);
/* Rotation */
int rotation = 1;
try{
ExifInterface exif = new ExifInterface(picturePath);
rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
}
catch(IOException e){
e.printStackTrace();
}
int rotationInDegrees = 0;
if(rotation == ExifInterface.ORIENTATION_ROTATE_90)
rotationInDegrees = 90;
else if(rotation == ExifInterface.ORIENTATION_ROTATE_180)
rotationInDegrees = 180;
else if(rotation == ExifInterface.ORIENTATION_ROTATE_270)
rotationInDegrees = 270;
Matrix matrix = new Matrix();
if(rotation != 0f)
matrix.preRotate(rotationInDegrees);
return Bitmap.createBitmap(scaledBitmap, 0, 0,
scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
}// method: getScaledBitmap(String, int, int)
Before I start getting comments about this being so common of a question, I'll point out that I'm not displaying these images, so it's not like I'm trying to keep all of these in memory. I need to keep large images because users will want to be able to zoom in on the pictures, but I'm resizing them because they don't need to be ridiculously huge. Pretty much any other solution I've seen on SO for images and OOM errors don't apply to my back-to-back access of multiple images.
So like I said, I'm recycling each Bitmap after it's saved, but they still seem to be using memory. Any idea what I'm missing?
You're not recycling scaledBitmap in getScaledBitmap. Fixing that should help. Change this line:
return Bitmap.createBitmap(scaledBitmap, 0, 0,
scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
to something like:
Bitmap newBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0,
scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
scaledBitmap.recycle();
return newBitmap;
If you have multiple threads working on large bitmaps, you will use a lot of memory on some cases.
What you need is to find the best approach according to your needs. here are some things you can do and/or need to know:
use a single thread for the images handling.
always recycle old bitmaps that you don't need anymore, as soon as possible. it's true that the GC will help you, but that can help it too, and it will work even on pre-honeycomb devices.
do the image manipulations via NDK (so you won't need to have 2 bitmaps for each image manipulation), for example using this.
downsample the image to the minimal size that you need, and never assume that the memory is large enough for any given image (unless you are 100% sure that the images are small).
remember that the requirements for android devices are still very low in terms of RAM per app (heap size) - the bare minimal is still 16MB per app.
you can use android:largeHeap="true" in the manifest, but that doesn't mean anything about how much more you will get, if at all.

Load many large bitmap images as thumbnails - Android

I'm working on a media player app and wish to load album art images to display in a ListView. Right now it works fine with the images I'm auto-downloading from last.fm which are under 500x500 png's. However, I recently added another panel to my app that allows viewing full screen artwork so I've replaced some of my artworks with large (1024x1024) png's instead.
Now when I scroll over several albums with high res artwork, I get a java.lang.OutOfMemoryError on my BitmapFactory.
static public Bitmap getAlbumArtFromCache(String artist, String album, Context c)
{
Bitmap artwork = null;
File dirfile = new File(SourceListOperations.getAlbumArtPath(c));
dirfile.mkdirs();
String artfilepath = SourceListOperations.getAlbumArtPath(c) + File.separator + SourceListOperations.makeFilename(artist) + "_" + SourceListOperations.makeFilename(album) + ".png";
File infile = new File(artfilepath);
try
{
artwork = BitmapFactory.decodeFile(infile.getAbsolutePath());
}catch(Exception e){}
if(artwork == null)
{
try
{
artwork = BitmapFactory.decodeResource(c.getResources(), R.drawable.icon);
}catch(Exception ex){}
}
return artwork;
}
Is there anything I can add to limit the size of the resulting Bitmap object to say, 256x256? That's all the bigger the thumbnails need to be and I could make a duplicate function or an argument to fetch the full size artwork for displaying full screen.
Also, I'm displaying these Bitmaps on ImageViews that are small, around 150x150 to 200x200. The smaller images scale down nicer than the large ones do. Is there any way to apply a downscaling filter to smooth the image (anti-aliasing perhaps)? I don't want to cache a bunch of additional thumbnail files if I don't have to, because it would make managing the artwork images more difficult (currently you can just dump new ones in the directory and they will automatically be used next time they get loaded).
The full code is at http://github.org/CalcProgrammer1/CalcTunes, in src/com/calcprogrammer1/calctunes/AlbumArtManager.java, though there's not much different in the other function (which falls back to checking last.fm if the image is missing).
I use this private function to setup the size I want for my thumbnails:
//decodes image and scales it to reduce memory consumption
public static Bitmap getScaledBitmap(String path, int newSize) {
File image = new File(path);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inInputShareable = true;
options.inPurgeable = true;
BitmapFactory.decodeFile(image.getPath(), options);
if ((options.outWidth == -1) || (options.outHeight == -1))
return null;
int originalSize = (options.outHeight > options.outWidth) ? options.outHeight
: options.outWidth;
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = originalSize / newSize;
Bitmap scaledBitmap = BitmapFactory.decodeFile(image.getPath(), opts);
return scaledBitmap;
}
public static Bitmap decodeSampledBitmapFromFile(String path, int reqWidth, int reqHeight)
{
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
Adapted from http://developer.android.com/training/displaying-bitmaps/load-bitmap.html but to use loads from files rather than resources. I've added a thumbnail option as such:
//Checks cache for album art, if it is not found return default icon
static public Bitmap getAlbumArtFromCache(String artist, String album, Context c, boolean thumb)
{
Bitmap artwork = null;
File dirfile = new File(SourceListOperations.getAlbumArtPath(c));
dirfile.mkdirs();
String artfilepath = SourceListOperations.getAlbumArtPath(c) + File.separator + SourceListOperations.makeFilename(artist) + "_" + SourceListOperations.makeFilename(album) + ".png";
File infile = new File(artfilepath);
try
{
if(thumb)
{
artwork = decodeSampledBitmapFromFile(infile.getAbsolutePath(), 256, 256);
}
else
{
artwork = BitmapFactory.decodeFile(infile.getAbsolutePath());
}
Yoann's answer looks pretty similar and is a bit more condensed, might use that solution instead, but that page had some good information on it about BitmapFactory.
One way to do this would be the AQuery library.
This is a library that allows you to lazy load images from either your local storage or an url. With support for things like caching and downscaling.
Example to lazy load a resource without downscaling:
AQuery aq = new AQuery(mContext);
aq.id(yourImageView).image(R.drawable.myimage);
Example to lazy load a image in a File object with downscaling:
InputStream ins = getResources().openRawResource(R.drawable.myImage);
BufferedReader br = new BufferedReader(new InputStreamReader(ins));
StringBuffer sb;
String line;
while((line = br.readLine()) != null){
sb.append(line);
}
File f = new File(sb.toString());
AQuery aq = new AQuery(mContext);
aq.id(yourImageView).image(f,350); //Where 350 is the width to downscale to
Example how to download from an url with local memory caching, local storage caching and resizing.
AQuery aq = new AQuery(mContext);
aq.id(yourImageView).image(myImageUrl, true, true, 250, 0, null);
This will start a async download of the image at myImageUrl,resize it to a 250 width and cache it in memory and storage.Then it will show the image in your yourImageView. Whenever the image of myImageUrl has been downloaded and cached before, this line of code will load the one cached in the memory or storage instead.
Usually these methods would be called in the getView method of a list adapter.
For full documentation on AQuery's image loading capabilities, you can check the documentation.
This is easily done with droidQuery:
final ImageView image = (ImageView) findViewById(R.id.myImage);
$.ajax(new AjaxOptions(url).type("GET")
.dataType("image")
.imageHeight(256)//set the output height
.imageWidth(256)//set the output width
.context(this)
.success(new Function() {
#Override
public void invoke($ droidQuery, Object... params) {
$.with(image).val((Bitmap) params[0]);
}
})
.error(new Function() {
#Override
public void invoke($ droidQuery, Object... params) {
droidQuery.toast("could not set image", Toast.LENGTH_SHORT);
}
}));
You can also cache the responses using the cache and cacheTimeout methods.

Bitmap Not Reading EXIF Data on Decode

My Problem
I have a series of Bitmaps that I would like to load up in the correct orientation.
When I save the image I go in and set the orientation attribute using the ExifInterface
ExifInterface exif = new ExifInterface(EXTERNAL_IMAGE_PATH+File.separator+this._currentPhotoName+JPEG_FILE_SUFFIX);
int rotation = CCDataUtils.exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL));
Log.v("PhotoManager", "Rotation:"+rotation);
if (rotation > 0) {
exif.setAttribute(ExifInterface.TAG_ORIENTATION,String.valueOf(0));
This works fine and if I was to pull this image off of my device it would be in the correct orientation. However, when I then decode my Bitmap later down the line it stays in the camera's default orientation of left-horizontal even if the image was taken in portrait?
My Question
How can I decode the bitmap and take into account its EXIF information?
I don't want to have to rotate the image after I decode it every time as I would have to create another Bitmap and that is memory I don't have.
Thanks in advance.
For those that are also stuck on this and have oom issues when manipulating multiple bitmaps here is my solution.
Do not change the exif data like I originally thought in the question - We need this later down the line.
When it comes to decoding the image to view, instead of decoding the full size image just decode the image scaled down to what you need. The following code example contains both the decoding of the bitmap to the device screen size and then it also handles the rotation of the bitmap for you.
public static Bitmap decodeFileForDisplay(File f){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
DisplayMetrics metrics = MyApplication.getAppContext().getResources().getDisplayMetrics();
//The new size we want to scale to
//final int REQUIRED_SIZE=180;
int scaleW = o.outWidth / metrics.widthPixels;
int scaleH = o.outHeight / metrics.heightPixels;
int scale = Math.max(scaleW,scaleH);
//Log.d("CCBitmapUtils", "Scale Factor:"+scale);
//Find the correct scale value. It should be the power of 2.
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
Bitmap scaledPhoto = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
try {
ExifInterface exif = new ExifInterface(f.getAbsolutePath());
int rotation = CCDataUtils.exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL));
if (rotation > 0)
scaledPhoto = CCBitmapUtils.convertBitmapToCorrectOrientation(scaledPhoto, rotation);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return scaledPhoto;
} catch (FileNotFoundException e) {}
return null;
}
public static Bitmap convertBitmapToCorrectOrientation(Bitmap photo,int rotation) {
int width = photo.getWidth();
int height = photo.getHeight();
Matrix matrix = new Matrix();
matrix.preRotate(rotation);
return Bitmap.createBitmap(photo, 0, 0, width, height, matrix, false);
}
So the image Bitmap thats returned after calling decodeFileForDisplay(File f); is in the correct orientation and the correct size for you screen saving you tons of memory problems.
I hope it helps someone

Categories

Resources