Downsample image below size limit before upload - android

There's a 2MB limit on the server where I need to upload an image.
I'm using this method to downsample a Bitmap Strange out of memory issue while loading an image to a Bitmap object
within this method
public InputStream getPhotoStream(int imageSizeBytes) throws IOException {
int targetLength = 1500;
ByteArrayOutputStream photoStream;
byte[] photo;
Bitmap pic;
final int MAX_QUALITY = 100;
int actualSize = -1;
do {
photo = null;
pic = null;
photoStream = null;
//this calls the downsampling method
pic = getPhoto(targetLength);
photoStream = new ByteArrayOutputStream();
pic.compress(CompressFormat.JPEG, MAX_QUALITY, photoStream);
photo = photoStream.toByteArray();
actualSize = photo.length;
targetLength /= 2;
} while (actualSize > imageSizeBytes);
return new ByteArrayInputStream(photo);
}
This throws OutOfMemoryError on the second iteration. How could I downsample the image below a certain size limit?

I think the problem is happening because you are compressing the image to a in memory representation, you need to free up that memory before you try to compress again.
You need call close() in the photoStream before trying again in order to free up resources.
Also toByteArray() makes a copy of the stream in memory that you have to free up later, why don't you just use the photoStream.size() to check for the file size?
I can post some code if you need.

Instead of this:
pic = null;
Do this:
if (pic!=null)
pic.recycle();
pic = null
If you simply set the bitmap object to null the memory it occupied isn't released immediately. In the second case you are explicitly telling the OS you are done with the bitmap and its okay to release its memory.
Also consider using a compression quality of 90 instead of 100, I believe that will reduce the resulting file size quite a bit.

Related

How can I get the device orientation when a photo was taken

I've never been struggling this much with images as I am today and I'd appreciate some help :D
So, thing is, I'm using the built-in camera to capture a picture and then send it to the back end to save it but the orientation is messed up for Kit kat and specially Samsung devices. I tried to use the exif interface everyone suggests, but I just can't get the photo orientation.
A few minutes ago I found an answer somewhat related to this, saying that maybe a good solution is to save the device orientation when the picture is taken, which sounds pretty nice, however, I don't know how to do that with the built in camera, since I don't have full control when opening the camera with an Intent, like this:
mPathToTakenImage = ImageProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider",
newFile);
openCamera.putExtra(MediaStore.EXTRA_OUTPUT, mPathToTakenImage);
openCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(openCamera, AlgebraNationConstants.TAKE_PHOTO_REQUEST_CODE);
So, how can I get the device orientation while the image was being taken in order to rotate the image correctly?
This is the code to rotate the image, however, I'm getting always zero:
final Bitmap finalImg;
final StringBuilder base64Image = new StringBuilder("data:image/jpeg;base64,");
final ExifInterface exifInterface;
try {
final String imagePath = params[0].getPath();
exifInterface = new ExifInterface(imagePath);
final int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
final Bitmap takenPhoto = MediaStore.Images.Media.getBitmap(mRelatedContext.getContentResolver(),
params[0]);
if (null == takenPhoto) {
base64Image.setLength(0);
} else {
finalImg = rotateBitmap(takenPhoto, orientation);
if (null == finalImg) {
base64Image.setLength(0);
} else {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
finalImg.compress(Bitmap.CompressFormat.JPEG, 75, byteArrayOutputStream);
final byte[] byteArray = byteArrayOutputStream.toByteArray();
base64Image.append(Base64.encodeToString(byteArray, Base64.DEFAULT));
}
}
} catch (IOException e) {
e.printStackTrace();
}
return base64Image.length() == 0 ? null : base64Image.toString();
I'm going crazy with this, any help will be deeply appreciated.
EDIT:
A Uri is not a file. Unless the scheme of the Uri is file, getPath() is meaningless. In your case, the scheme is mostly going to be content, not file. As it stands, you are not getting EXIF headers, because ExifInterface cannot find that file.
Use a ContentResolver and openInputStream() to open an InputStream on the content identified by the Uri. Pass that InputStream to the android.support.media.ExifInterface constructor.
Also, bear in mind that you will crash much of the time with an OutOfMemoryError, as you will not have heap space to hold a base64-encoded photo.
So, I finally resolved my problem making use of these two answers:
https://stackoverflow.com/a/17426328
https://stackoverflow.com/a/40865982
In general, the issue was due to the image allocation in disk since I don't know why Android didn't like the fact that I was giving my own path to save the taken image. At the end, the Intent to open the camera is looking like this:
final Intent openCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCamera, AlgebraNationConstants.TAKE_PHOTO_REQUEST_CODE);
And Android itself is resolving where to put the image. Nevertheless, #CommonsWare thanks for the enlightenment and answers.

FreeImage problems on Android (NDK)

I tried to use FreeImage library to load PNG as a texture (from memory). That's the fragment of code:
FIMEMORY *fiStream = FreeImage_OpenMemory(streamData, size);
FREE_IMAGE_FORMAT fileFormat = FreeImage_GetFileTypeFromMemory(fiStream, 0);
FIBITMAP *image = FreeImage_LoadFromMemory(fileFormat, fiStream, 0);
int bitsPerPixel = FreeImage_GetBPP(image);
width = (int)FreeImage_GetWidth(image);
height = (int)FreeImage_GetHeight(image);
I'm using FILE with fopen to open file and then read stream to streamData object. File and stream is read correctly.
The result is: fileFormat = -1 and image is NULL.
I also tried to use FreeImage to load PNG file directly from disk using FreeImage_Load, but the result is the same - it returns NULL.
Has anybody faced similar problem? Can you suggest an alternative to FreeImage that can read data from memory?
try this code to load your image from memory:
File file = new File("/sdcard/Images/image1.jpg");
if(file.exists()){
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
ImageView image = (ImageView) findViewById(R.id.imageview);
image.setImageBitmap(bitmap);
}

Compress image file to specified size

I have a requirement to make sure that all pictures my users take from ACTION_IMAGE_CAPTURE Intent in my app are under 4 MB due to server restrictions. What would be the best way to go about doing this? The files are JPEG if that matters.
So far I'm using a brute force method below but this doesn't work. (Edit: It works now with suggestions given below, but takes a while) I'm wondering if there is some more efficient method, perhaps using BitmapFactory.Options.inJustDecodeBounds, that could maybe map pixel count to number of bytes so I wouldn't need so many IO operations.
private final int MAX_IMAGE_SIZE = 4194304;
private void shrinkImageFile(File file) throws IOException
{
Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
int quality = 90;
while (file.length() > MAX_IMAGE_SIZE )
{
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, out);
quality -= 10;
}
}

The most efficient way to show the local image in android

I am currently work on a magazine like apps. Since there is an option to zoom in(150%, 200%, 250% of original source) , I would prefer not to scale down the image. However , the app will force restart when I try to decode the image because of the out of memory. Are there any suggestion to fix that?
The image are local (SD card) , can be retrieve in any approach, but eventually need to be a bitmap as I use something like cavans.drawbitmap to display it. I tried, input stream-> bytes , input stream->bitmap etc... but are there any most efficient way or at least
I can sure the app does not force restart / close? Thanks
try {
InputStream is = (InputStream) new URL(url).getContent();
try {
return BitmapFactory.decodeStream(is);
} finally {
is.close();
}
} catch (Exception e) {
return BitmapFactory.decodeResource(context.getResources(), defaultDrawable);
}
You should consider using nostra's image loader, it deals with memory quite efficiently, you can specify lots of config stuffs, im using it and its working pretty well for large images aswell
This is what smartImageView(by loopj - you can find him on http://loopj.com/) uses to retrieve files from the drive/sd.
private Bitmap getBitmapFromDisk(String imgID) {
Bitmap bitmap = null;
if(diskCacheEnabled){
String filePath = getFilePath(imgID);
File file = new File(filePath);
if(file.exists()) {
bitmap = BitmapFactory.decodeFile(filePath);
}
}
return bitmap;
}

problems with big drawable jpg image

during a couple of days i've been looking here and on the Internet in many post for a solution on an issue im having loading or moving a large jpg image (located as a drawable reource by now) and I think it's time to ask myself altought there are many questions and solutions very related.
I'm developing a 3D terrain simulation with OpenGL and the problem is that I'm using as terrain texture a quite large jpg image ( 2048x2048 = 1,94 MB ).
Let's look at some workarround I Did:
1st:
Bitmap terrainBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.terrain, options);
This one crashes inmediately, probably because the image is too big
2nd:
Uri path = Uri.parse("android.resource://com.jgc.my_app/" + R.drawable.terrain);
URL ulrn;ulrn = new URL(path.getPath());
HttpURLConnection con = (HttpURLConnection)ulrn.openConnection();
InputStream is = con.getInputStream();
Bitmap bmp = BitmapFactory.decodeStream(is);
that one probably was a quite silly attempt
3rd:
Bitmap terrainBitmap = BitmapFactory.decodeFile("android.resource://com.jgc.my_app/terrain.jpg", options);
but I'm getting a very frustrating null bitmap as result...
Other workarrounds in mind are, as the drawable resources are "inside" the application, why not to move them to the sd card and decodefile form there, but, the solutions given to move a bitmap or a drawable to the sdcard that I've found always user BitmapFactory.decodeResource(getResources(), R.drawable.terrain, options);
I appreciate any suggestion, since then I'll be working arround other tasks in my proyect.
Thanks in advance
Ok, maybe I've been lucky, here is the n work-arround ind this workd for me:
private static BitmapFactory.Options terrainBitmapOptions = new BitmapFactory.Options();
...
// Set our bitmaps to 16-bit, 565 format.
terrainBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
...
InputStream is = getResources().openRawResource(R.drawable.terrain);
Bitmap terrainBitmap;
try {
terrainBitmap = BitmapFactory.decodeStream(is, null, terrainBitmapOptions);
} finally {
try {
is.close();
} catch (IOException e) {
// Ignore.
}
}
I hope it helps anyone else...

Categories

Resources