android reduce file size for camera captured image to be less than 500 kb
Asked Answered
A

8

21

My requirement is to upload camera captured image to the server, but it should be less than 500 KB. In case, if it is greater than 500 KB, it needs to be reduced to the size less than 500 KB (but somewhat closer to it)

For this, I am using the following code -

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        try {
            super.onActivityResult(requestCode, resultCode, data);
            if (resultCode == getActivity().RESULT_OK) {

                    if (requestCode == REQUEST_CODE_CAMERA) {

                        try {

                            photo = MediaStore.Images.Media.getBitmap(
                                    ctx.getContentResolver(), capturedImageUri);
                            String selectedImagePath = getRealPathFromURI(capturedImageUri);

                            img_file = new File(selectedImagePath);

                            Log.d("img_file_size", "file size in KBs (initially): " + (img_file.length()/1000));

                            if(CommonUtilities.isImageFileSizeGreaterThan500KB(img_file)) {
                                photo = CommonUtilities.getResizedBitmapLessThan500KB(photo, 500);
                            }
                            photo = CommonUtilities.getCorrectBitmap(photo, selectedImagePath);


//  // CALL THIS METHOD TO GET THE URI FROM THE BITMAP

                            img_file = new File(ctx.getCacheDir(), "image.jpg");
                            img_file.createNewFile();

//Convert bitmap to byte array
                            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                            photo.compress(Bitmap.CompressFormat.JPEG, 100, bytes);

//write the bytes in file
                            FileOutputStream fo = new FileOutputStream(img_file);
                            fo.write(bytes.toByteArray());

// remember close de FileOutput
                            fo.close();
                            Log.d("img_file_size", "file size in KBs after image manipulations: " + (img_file.length()/1000));


                        } catch (Exception e) {
                            Logs.setLogException(class_name, "onActivityResult(), when captured from camera", e);
                        }


                    } 

            }
        } catch (Exception e) {
            Logs.setLogException(class_name, "onActivityResult()", e);
        } catch (OutOfMemoryError e) {
            Logs.setLogError(class_name, "onActivityResult()", e);

        }
    }

And

public static Bitmap getResizedBitmapLessThan500KB(Bitmap image, int maxSize) {
        int width = image.getWidth();
        int height = image.getHeight();



        float bitmapRatio = (float)width / (float) height;
        if (bitmapRatio > 0) {
            width = maxSize;
            height = (int) (width / bitmapRatio);
        } else {
            height = maxSize;
            width = (int) (height * bitmapRatio);
        }
        Bitmap reduced_bitmap = Bitmap.createScaledBitmap(image, width, height, true);
        if(sizeOf(reduced_bitmap) > (500 * 1000)) {
            return getResizedBitmap(reduced_bitmap, maxSize);
        } else {
            return reduced_bitmap;
        }
    }

To rotate the image, if needed.

public static Bitmap getCorrectBitmap(Bitmap bitmap, String filePath) {
        ExifInterface ei;
        Bitmap rotatedBitmap = bitmap;
        try {
            ei = new ExifInterface(filePath);

            int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL);
            Matrix matrix = new Matrix();
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    matrix.postRotate(90);
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    matrix.postRotate(180);
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    matrix.postRotate(270);
                    break;
            }

            rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return rotatedBitmap;
    }

Here is the output of the image file size initially and after all the operations to reduce file size.

img_file_size﹕ file size in KBs (initially): 3294

img_file_size﹕ file size in KBs after image manipulations: 235

See the difference above (in the output). The initial file size without those operations, and after those compression and other operations. I need that size to be somewhat closer to 500 kb.

The above code is working somewhat fine for me, as it is reducing the image file size to make it less than 500 KB.

But, the following are the issues with the above code -

  • This code is reducing the file size even if its less than 500 KB

  • In case it is more than 500 KB, the reduced file size becomes too less from 500 KB, , though I need it somewhat closer.

I need to get rid off above 2 issues. So, need to know what should I manipulate in the above code.

As I also want to correct the EXIF-orientation (rotated images), along with my above mentioned requirement.

Artemus answered 18/5, 2016 at 12:21 Comment(6)
Obviously it is not possible (other then resize or change quality(for jpg) and test size) for png/jpg as you will not know the size of compressed data if you do not compressPeper
So, doesn't it have any solution? @PeperArtemus
brute-force solution ... resize or change quality and check the size ... if size is greater then requeried, tweak size or quality and do it again until you get the right size ...Peper
Please check my code again, I am doing it in isImageFileSizeGreaterThan500KB() but, after that, I am rotating the image to make it correctly oriented (which too is necessary, that I cannot skip). May be, thats creating the troubleArtemus
I am doing it no, you don't ... i don't see any loop in your code ... and no, i will not write any code ... basically, you can know how much uncompressed image data takes (simply H * W * perPixelDataSize (which is 4 for ARGB_8888 and 3 for RGB_888, etc...) but you cannot get the size of the image data after compression until you compress it(for png, jpg codecs)Peper
@Peper please check isImageFileSizeGreaterThan500KB() once again. Though, there isn't any loop, but its recursive call.Artemus
D
5

You can check size before resizing it. If the bitmap is larger than 500kb in size then resize it .

Also for making larger bitmap nearer to 500kb size, check the difference between size and compress accordingly .

if(sizeOf(reduced_bitmap) > (500 * 1024)) {
    return getResizedBitmap(reduced_bitmap, maxSize, sizeOf(reduced_bitmap));
} else {
    return reduced_bitmap;
}

and in resize Bitmap calculate the difference in size and compress accordingly

Dittany answered 12/7, 2016 at 6:39 Comment(2)
How did you import the sizeOf method?Tales
its a user written method in questioner code itself , its not a inbuilt methodDittany
S
1

This is not the solution for your problem but the bug why you get very small files.

In getResizedBitmapLessThan500KB(photo, 500) the 500 is the max with/height in pixel of the image not the max size in kb.

So all compressed files are less 500x500 pixels

Salient answered 6/7, 2016 at 14:43 Comment(0)
R
1

Another issue you have is that you're measuring the size of the bitmap (which isn't compressed), and then converting it to JPG and measuring that. The JPG is probably always going to be smaller, and how well it compresses is going to be a factor of what the image is. Lots of big areas of the same color? Great! An extremely "busy" pattern? Compression won't be as great.

OK, so further clarification:

If you're targeting a certain file size, you can't do that by looking at the size BEFORE compression. You could get a general idea (e.g. JPEG compresses by a factor of ~15 for these photos), so then you could target 500k * 15 for the bitmap. But depending on what's in the photo, you might not hit that target exactly. So you probably want to do this:

  1. Pick a jpegFactor
  2. bitMapTarget = target * jpegFactor
  3. adjustBitmap size to fit bitMapTarget
  4. compress bitmap to JPEG
  5. If this is still above the target, then adjust the jpegFactor and try again.

You could have some steps on #5 to figure out how close you were and try to take that into account.

Racism answered 6/7, 2016 at 14:49 Comment(1)
It doesn't seem to be an anwser. Can you please provide answer for that?Artemus
T
1

To reduce the size of Image I've used this code.. and its work for me.. Please check it once.. It might be helpful to you..

Bitmap photo1 ;
private byte[] imageByteArray1 ;


BitmapFactory.Options opt1 = new BitmapFactory.Options();
opt1.inJustDecodeBounds=true;
BitmapFactory.decodeFile(imageUrl.get(imgCount).toString(),opt1);

// The new size we want to scale to
final int REQUIRED_SIZE=320;

// Find the correct scale value. It should be the power of 2.
int width_tmp=opt1.outWidth,height_tmp=opt1.outHeight;
int scale=2;
while(true){
    if(width_tmp>REQUIRED_SIZE||height_tmp>REQUIRED_SIZE)
        break;
    width_tmp/=2;
    height_tmp/=2;
    scale*=2;
}
// Decode with inSampleSize
BitmapFactory.Options o2=new BitmapFactory.Options();
o2.inSampleSize=scale;
o2.inJustDecodeBounds=false;
photo1=BitmapFactory.decodeFile(imageUrl.get(imgCount).toString(),o2);

ByteArrayOutputStream baos1=new ByteArrayOutputStream();
photo1.compress(Bitmap.CompressFormat.JPEG,60,baos1);
imageByteArray1=baos1.toByteArray();
Thetos answered 7/7, 2016 at 6:34 Comment(2)
What are imageUrl and imgCount here?Artemus
imageUrl is an Arraylist and imgCount is positon to get image url from arraylist..Thetos
K
1

you can try this method

    public static Bitmap getScaledBitmap(Bitmap b, int reqWidth, int reqHeight)
        {
            Matrix m = new Matrix();
            m.setRectToRect(new RectF(0, 0, b.getWidth(), b.getHeight()), new RectF(0, 0, reqWidth, reqHeight), Matrix.ScaleToFit.CENTER);
            return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true);
        }

//call this method like
    Bitmap bm400=getScaledBitmap(bm,500,500);

its helpful for you.

Kristikristian answered 11/7, 2016 at 10:54 Comment(0)
A
0

Please check if this code is helpful:

    final static int COMPRESSED_RATIO = 13;
    final static int perPixelDataSize = 4;
    public byte[] getJPGLessThanMaxSize(Bitmap image, int maxSize){
        int maxPixelCount = maxSize *1024 * COMPRESSED_RATIO / perPixelDataSize;
        int imagePixelCount = image.getWidth() * image.getHeight();
        Bitmap reducedBitmap;
        // Adjust Bitmap Dimensions if necessary.
        if(imagePixelCount > maxPixelCount) reducedBitmap = getResizedBitmapLessThanMaxSize(image, maxSize);
        else reducedBitmap = image;

        float compressedRatio = 1;
        byte[] resultBitmap;
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        int jpgQuality = 100;
        // Adjust Quality until file size is less than maxSize.
        do{
            reducedBitmap.compress(Bitmap.CompressFormat.JPEG, jpgQuality, outStream);
            resultBitmap = outStream.toByteArray();
            compressedRatio = resultBitmap.length / (reducedBitmap.getWidth() * reducedBitmap.getHeight() * perPixelDataSize);
            if(compressedRatio > (COMPRESSED_RATIO-1)){
                jpgQuality -= 1;
            }else if(compressedRatio > (COMPRESSED_RATIO*0.8)){
                jpgQuality -= 5;
            }else{
                jpgQuality -= 10;
            }
        }while(resultBitmap.length > (maxSize * 1024));
        return resultBitmap;
    }

    public Bitmap getResizedBitmapLessThanMaxSize(Bitmap image, int maxSize) {
        int width = image.getWidth();
        int height = image.getHeight();
        float bitmapRatio = (float)width / (float) height;

        // For uncompressed bitmap, the data size is:
        // H * W * perPixelDataSize = H * H * bitmapRatio * perPixelDataSize
        // 
        height = (int) Math.sqrt(maxSize * 1024 * COMPRESSED_RATIO / perPixelDataSize / bitmapRatio);
        width = (int) (height * bitmapRatio);
        Bitmap reduced_bitmap = Bitmap.createScaledBitmap(image, width, height, true);
        return reduced_bitmap;
    }
Announcer answered 7/7, 2016 at 6:27 Comment(2)
How should I decide the values for COMPRESSED_RATIO and perPixelDataSize?Artemus
The assumption is from Wikipedia about jpg, it states "JPEG typically achieves 10:1 compression with little perceptible loss in image quality". I think it is for RGB format. For ARGB (perPixelDataSize = 4), I just scale it up.Announcer
P
0

I assume you want to use the gallery or system camera to get the image. Please note that there is a upper limit bound for the maximum data transferred via Intent and that's why you always get downsized version of images.

You can refer to https://developer.android.com/training/camera/photobasics.html for the standard solution. In summary, you should acquire the access to external storage and generate an URI for camera or get the URI from gallery app. Then use the ContentResolver to get the image.

InputStream inputStream = mContentResolver.openInputStream(mUri);

You may want to implement content resolver for other application to access your data and that's the standard practice.

Primaveras answered 7/7, 2016 at 16:50 Comment(0)
A
0

Customizing my getResizedBitmapLessThan500KB() to this below, worked for me.

    public static final long CAMERA_IMAGE_MAX_DESIRED_SIZE_IN_BYTES = 2524970;
        public static final double CAMERA_IMAGE_MAX_SIZE_AFTER_COMPRESSSION_IN_BYTES = 1893729.0;

 public static Bitmap getResizedBitmapLessThan500KB(Bitmap image, int maxSize, long file_size_in_bytes) {
                int width = image.getWidth();
                int height = image.getHeight();


                if(file_size_in_bytes <= AppGlobalConstants.CAMERA_IMAGE_MAX_DESIRED_SIZE_IN_BYTES) {
                    if (width > height) {
                        if (width > 500)
                            maxSize = width * 75 / 100;
                    } else {
                        if (height > 500)
                            maxSize = height * 75 / 100;
                    }
                } else {
                    double percentage = ((AppGlobalConstants.CAMERA_IMAGE_MAX_SIZE_AFTER_COMPRESSSION_IN_BYTES/file_size_in_bytes)*100);
                    if (width > height) {
                        if (width > 500)
                            maxSize = width * (int)percentage / 100;
                    } else {
                        if (height > 500)
                            maxSize = height * (int)percentage / 100;
                    }

                    if(maxSize > 600) {
                        maxSize = 600;
                    }

                }

                float bitmapRatio = (float)width / (float) height;
                if (bitmapRatio > 0) {
                    width = maxSize;
                    height = (int) (width / bitmapRatio);
                } else {
                    height = maxSize;
                    width = (int) (height * bitmapRatio);
                }
                Bitmap reduced_bitmap = Bitmap.createScaledBitmap(image, width, height, true);
        //        Log.d("file_size","file_size_during_manipulation: "+String.valueOf(sizeOf(reduced_bitmap)));
                if(sizeOf(reduced_bitmap) > (500 * 1000)) {
                    return getResizedBitmap(reduced_bitmap, maxSize, sizeOf(reduced_bitmap));
                } else {
                    return reduced_bitmap;
                }
            }
Artemus answered 11/7, 2016 at 6:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.