How to reduce an Image file size before uploading to a server

112,787

Solution 1

I use this function to reduce the size of image before uploading it, it reduces the image size to nearly 200 KB and keep the quality relatively good, u may modify it to fulfill your purpose by changing the REQUIRED_SIZE and inSampleSize:

public File saveBitmapToFile(File file){
    try {

        // BitmapFactory options to downsize the image
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        o.inSampleSize = 6;
        // factor of downsizing the image

        FileInputStream inputStream = new FileInputStream(file);
        //Bitmap selectedBitmap = null;
        BitmapFactory.decodeStream(inputStream, null, o);
        inputStream.close();

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

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
                        o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            scale *= 2;
        }

        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        inputStream = new FileInputStream(file);

        Bitmap selectedBitmap = BitmapFactory.decodeStream(inputStream, null, o2);
        inputStream.close();

        // here i override the original image file
        file.createNewFile();
        FileOutputStream outputStream = new FileOutputStream(file);

        selectedBitmap.compress(Bitmap.CompressFormat.JPEG, 100 , outputStream);

        return file;
    } catch (Exception e) {
        return null;
    }
}

NOTE: I resize and override the original file image in this function, u may write it to another file as well.

I hope it may help you.

Solution 2

This is working great Try this

private String decodeFile(String path,int DESIREDWIDTH, int DESIREDHEIGHT) {
        String strMyImagePath = null;
        Bitmap scaledBitmap = null;

        try {
            // Part 1: Decode image
            Bitmap unscaledBitmap = ScalingUtilities.decodeFile(path, DESIREDWIDTH, DESIREDHEIGHT, ScalingLogic.FIT);

            if (!(unscaledBitmap.getWidth() <= DESIREDWIDTH && unscaledBitmap.getHeight() <= DESIREDHEIGHT)) {
                // Part 2: Scale image
                scaledBitmap = ScalingUtilities.createScaledBitmap(unscaledBitmap, DESIREDWIDTH, DESIREDHEIGHT, ScalingLogic.FIT);
            } else {
                unscaledBitmap.recycle();
                return path;
            }

            // Store to tmp file

            String extr = Environment.getExternalStorageDirectory().toString();
            File mFolder = new File(extr + "/TMMFOLDER");
            if (!mFolder.exists()) {
                mFolder.mkdir();
            }

            String s = "tmp.png";

            File f = new File(mFolder.getAbsolutePath(), s);

            strMyImagePath = f.getAbsolutePath();
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(f);
                scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 75, fos);
                fos.flush();
                fos.close();
            } catch (FileNotFoundException e) {

                e.printStackTrace();
            } catch (Exception e) {

                e.printStackTrace();
            }

            scaledBitmap.recycle();
        } catch (Throwable e) {
        }

        if (strMyImagePath == null) {
            return path;
        }
        return strMyImagePath;

    }

ScalingUtilities.java

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;

/**
 * Class containing static utility methods for bitmap decoding and scaling
 *
 * @author 
 */
public class ScalingUtilities {

    /**
     * Utility function for decoding an image resource. The decoded bitmap will
     * be optimized for further scaling to the requested destination dimensions
     * and scaling logic.
     *
     * @param res The resources object containing the image data
     * @param resId The resource id of the image data
     * @param dstWidth Width of destination area
     * @param dstHeight Height of destination area
     * @param scalingLogic Logic to use to avoid image stretching
     * @return Decoded bitmap
     */
    public static Bitmap decodeResource(Resources res, int resId, int dstWidth, int dstHeight,
            ScalingLogic scalingLogic) {
        Options options = new Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        options.inJustDecodeBounds = false;
        options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth,
                dstHeight, scalingLogic);
        Bitmap unscaledBitmap = BitmapFactory.decodeResource(res, resId, options);

        return unscaledBitmap;
    }
    public static Bitmap decodeFile(String path, int dstWidth, int dstHeight,
            ScalingLogic scalingLogic) {
        Options options = new Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);
        options.inJustDecodeBounds = false;
        options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth,
                dstHeight, scalingLogic);
        Bitmap unscaledBitmap = BitmapFactory.decodeFile(path, options);

        return unscaledBitmap;
    }

    /**
     * Utility function for creating a scaled version of an existing bitmap
     *
     * @param unscaledBitmap Bitmap to scale
     * @param dstWidth Wanted width of destination bitmap
     * @param dstHeight Wanted height of destination bitmap
     * @param scalingLogic Logic to use to avoid image stretching
     * @return New scaled bitmap object
     */
    public static Bitmap createScaledBitmap(Bitmap unscaledBitmap, int dstWidth, int dstHeight,
            ScalingLogic scalingLogic) {
        Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(),
                dstWidth, dstHeight, scalingLogic);
        Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(),
                dstWidth, dstHeight, scalingLogic);
        Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(),
                Config.ARGB_8888);
        Canvas canvas = new Canvas(scaledBitmap);
        canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG));

        return scaledBitmap;
    }

    /**
     * ScalingLogic defines how scaling should be carried out if source and
     * destination image has different aspect ratio.
     *
     * CROP: Scales the image the minimum amount while making sure that at least
     * one of the two dimensions fit inside the requested destination area.
     * Parts of the source image will be cropped to realize this.
     *
     * FIT: Scales the image the minimum amount while making sure both
     * dimensions fit inside the requested destination area. The resulting
     * destination dimensions might be adjusted to a smaller size than
     * requested.
     */
    public static enum ScalingLogic {
        CROP, FIT
    }

    /**
     * Calculate optimal down-sampling factor given the dimensions of a source
     * image, the dimensions of a destination area and a scaling logic.
     *
     * @param srcWidth Width of source image
     * @param srcHeight Height of source image
     * @param dstWidth Width of destination area
     * @param dstHeight Height of destination area
     * @param scalingLogic Logic to use to avoid image stretching
     * @return Optimal down scaling sample size for decoding
     */
    public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
            ScalingLogic scalingLogic) {
        if (scalingLogic == ScalingLogic.FIT) {
            final float srcAspect = (float)srcWidth / (float)srcHeight;
            final float dstAspect = (float)dstWidth / (float)dstHeight;

            if (srcAspect > dstAspect) {
                return srcWidth / dstWidth;
            } else {
                return srcHeight / dstHeight;
            }
        } else {
            final float srcAspect = (float)srcWidth / (float)srcHeight;
            final float dstAspect = (float)dstWidth / (float)dstHeight;

            if (srcAspect > dstAspect) {
                return srcHeight / dstHeight;
            } else {
                return srcWidth / dstWidth;
            }
        }
    }

    /**
     * Calculates source rectangle for scaling bitmap
     *
     * @param srcWidth Width of source image
     * @param srcHeight Height of source image
     * @param dstWidth Width of destination area
     * @param dstHeight Height of destination area
     * @param scalingLogic Logic to use to avoid image stretching
     * @return Optimal source rectangle
     */
    public static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
            ScalingLogic scalingLogic) {
        if (scalingLogic == ScalingLogic.CROP) {
            final float srcAspect = (float)srcWidth / (float)srcHeight;
            final float dstAspect = (float)dstWidth / (float)dstHeight;

            if (srcAspect > dstAspect) {
                final int srcRectWidth = (int)(srcHeight * dstAspect);
                final int srcRectLeft = (srcWidth - srcRectWidth) / 2;
                return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight);
            } else {
                final int srcRectHeight = (int)(srcWidth / dstAspect);
                final int scrRectTop = (int)(srcHeight - srcRectHeight) / 2;
                return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight);
            }
        } else {
            return new Rect(0, 0, srcWidth, srcHeight);
        }
    }

    /**
     * Calculates destination rectangle for scaling bitmap
     *
     * @param srcWidth Width of source image
     * @param srcHeight Height of source image
     * @param dstWidth Width of destination area
     * @param dstHeight Height of destination area
     * @param scalingLogic Logic to use to avoid image stretching
     * @return Optimal destination rectangle
     */
    public static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
            ScalingLogic scalingLogic) {
        if (scalingLogic == ScalingLogic.FIT) {
            final float srcAspect = (float)srcWidth / (float)srcHeight;
            final float dstAspect = (float)dstWidth / (float)dstHeight;

            if (srcAspect > dstAspect) {
                return new Rect(0, 0, dstWidth, (int)(dstWidth / srcAspect));
            } else {
                return new Rect(0, 0, (int)(dstHeight * srcAspect), dstHeight);
            }
        } else {
            return new Rect(0, 0, dstWidth, dstHeight);
        }
    }

}

Solution 3

Here's a solution that's handled in memory and doesn't require actually generating a file on the file system.

From some Fragment, after user selects an image file:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent) {
    super.onActivityResult(requestCode, resultCode, imageReturnedIntent);

    if (imageReturnedIntent == null
            || imageReturnedIntent.getData() == null) {
        return;
    }

    // aiming for ~500kb max. assumes typical device image size is around 2megs
    int scaleDivider = 4; 


    try {

        // 1. Convert uri to bitmap
        Uri imageUri = imageReturnedIntent.getData();
        Bitmap fullBitmap = MediaStore.Images.Media.getBitmap(getActivity().getContentResolver(), imageUri);

        // 2. Get the downsized image content as a byte[]
        int scaleWidth = fullBitmap.getWidth() / scaleDivider;
        int scaleHeight = fullBitmap.getHeight() / scaleDivider;
        byte[] downsizedImageBytes =
                getDownsizedImageBytes(fullBitmap, scaleWidth, scaleHeight);

        // 3. Upload the byte[]; Eg, if you are using Firebase
        StorageReference storageReference =
                FirebaseStorage.getInstance().getReference("/somepath");

        storageReference.putBytes(downsizedImageBytes);
    }
    catch (IOException ioEx) {
        ioEx.printStackTrace();
    }
}

public byte[] getDownsizedImageBytes(Bitmap fullBitmap, int scaleWidth, int scaleHeight) throws IOException {

    Bitmap scaledBitmap = Bitmap.createScaledBitmap(fullBitmap, scaleWidth, scaleHeight, true);

    // 2. Instantiate the downsized image content as a byte[]
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
    byte[] downsizedImageBytes = baos.toByteArray();

    return downsizedImageBytes;
}

Thanks to:

Solution 4

this code reduce size image

private Bitmap compressImage(Bitmap image) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//Compression quality, here 100 means no compression, the storage of compressed data to baos
        int options = 90;
        while (baos.toByteArray().length / 1024 > 400) {  //Loop if compressed picture is greater than 400kb, than to compression
            baos.reset();//Reset baos is empty baos
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);//The compression options%, storing the compressed data to the baos
            options -= 10;//Every time reduced by 10
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//The storage of compressed data in the baos to ByteArrayInputStream
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//The ByteArrayInputStream data generation
        return bitmap;
    }

Solution 5

You can use this library named Compressor.

Compress Image File

val compressedImageFile = Compressor.compress(context, actualImageFile)
Compress Image File to specific destination
val compressedImageFile = Compressor.compress(context, actualImageFile) {
    default()
    destination(myFile)
}

Custom Compressor

Using default constraint and custom partial of it

  val compressedImageFile = 
       Compressor.compress(context, actualImageFile) {
        default(width = 640, format = Bitmap.CompressFormat.WEBP)
    }

Full custom constraint

val compressedImageFile = Compressor.compress(context, actualImageFile) {
    resolution(1280, 720)
    quality(80)
    format(Bitmap.CompressFormat.WEBP)
    size(2_097_152) // 2 MB
}

Using own custom constraint

class MyLowerCaseNameConstraint: Constraint {
    override fun isSatisfied(imageFile: File): Boolean {
        return imageFile.name.all { it.isLowerCase() }
    }

    override fun satisfy(imageFile: File): File {
        val destination = File(imageFile.parent, imageFile.name.toLowerCase())
        imageFile.renameTo(destination)
        return destination
    }
}

val compressedImageFile = Compressor.compress(context, actualImageFile) {
    constraint(MyLowerCaseNameConstraint()) // your own constraint
    quality(80) // combine with compressor constraint
    format(Bitmap.CompressFormat.WEBP)
}

Create Kotlin extension -

fun Compression.lowerCaseName() {
    constraint(MyLowerCaseNameConstraint())
}

val compressedImageFile = Compressor.compress(context, actualImageFile) {
    lowerCaseName() // your own extension
    quality(80) // combine with compressor constraint
    format(Bitmap.CompressFormat.WEBP)
}

Compress image with Kotlin coroutines -

// e.g calling from activity lifecycle scope
lifecycleScope.launch {
    val compressedImageFile = Compressor.compress(context, actualImageFile)
}

// calling from global scope
GlobalScope.launch {
    val compressedImageFile = Compressor.compress(context, actualImageFile)
}

Run Compressor in main thread -

val compressedImageFile = Compressor.compress(context, actualImageFile, Dispatchers.Main)

enter image description here

Share:
112,787
user1537779
Author by

user1537779

Updated on March 31, 2021

Comments

  • user1537779
    user1537779 about 3 years

    Lot of application allow sharing an image, which is picked from the gallery.

    Do they upload the original image file? Which is like 1-3 mb? Or do they process?

    In any case, how can I take the image from a filepath, reduce its size by lowering the resolution and save it some where else and the try to upload?

    I tried:

    Bitmap photo = decodeSampledBitmapFromFile(filePath, DESIRED_WIDTH,
                        DESIRED_HEIGHT);
    
    FileOutputStream out = new FileOutputStream(filePath);
    photo.compress(Bitmap.CompressFormat.JPEG, 100, out);
    
    public static Bitmap decodeSampledBitmapFromFile(String path, int reqWidth,
            int reqHeight) {
    
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);
    
        final int height = options.outHeight;
        final int width = options.outWidth;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        int inSampleSize = 1;
    
        if (height > reqHeight) {
            inSampleSize = Math.round((float) height / (float) reqHeight);
        }
        int expectedWidth = width / inSampleSize;
    
        if (expectedWidth > reqWidth) {
            inSampleSize = Math.round((float) width / (float) reqWidth);
        }
        options.inSampleSize = inSampleSize;
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(path, options);
    }
    

    But is this they right way to do? Because I have seen answers suggesting compression operation takes rather big amount of time here

  • Arun Badole
    Arun Badole over 9 years
    Really useful Answer.
  • Syed Raza Mehdi
    Syed Raza Mehdi about 9 years
    this didn't help, how to use this?
  • Däñish Shärmà
    Däñish Shärmà almost 8 years
    thanks alot. This is the only answer which worked for me.
  • Logain
    Logain almost 8 years
    You are compressing the image as JPEG but naming it with ".png" extension. Not that it actually matters anyway... but still
  • Kishan Vaghela
    Kishan Vaghela almost 8 years
    follow this if you got OutOfMemory
  • Gaurav Sarma
    Gaurav Sarma over 7 years
    Where is the getOrientation method implementation ?
  • Akshay Kumar
    Akshay Kumar about 7 years
    @Biraj Zalavadia Where and how to call this ------------------
  • Akshay Kumar
    Akshay Kumar about 7 years
    @MBH How to all this method and where i will get the file argument from
  • MBH
    MBH about 7 years
    @AkshayKumar the parameter file, will be the file that you will write to. new File("your_image_to_be_comopressed_full_path");. The output file is the same file if successfully done. Null if something went wrong. NOTICE that to not override the existing image, remove the line file.createNewFile(); and write the image to another file instead.
  • codeKiller
    codeKiller almost 7 years
    getOrientation is missing
  • Usman Rana
    Usman Rana almost 7 years
    what is REQUIRED_SIZE ? is it percentage of scaling or fixed dimen of output image?
  • reetu
    reetu over 6 years
    @Biraj Zalavadia I am trying to use this code, but getting null on unscaledBitmap any idea why it might return null.
  • SlimenTN
    SlimenTN about 6 years
    Your method is great but I don't know why it's rotating the image !!! I get a rotated image after reducing it's size using this function !!
  • Mohammedsalim Shivani
    Mohammedsalim Shivani about 6 years
    Please add all the required/depended methods in the answer.
  • Tim Cooper
    Tim Cooper about 6 years
    There are a lot of file operations here.
  • San Juan
    San Juan about 6 years
    I don't understand what you mean
  • Tim Cooper
    Tim Cooper about 6 years
    There is a loop and you potentially write the image to the file-system multiple times, once for each iteration in the loop. Could it be done in memory instead? I think the file will be approx. 1/4 the size each iteration so it's not a big deal. I think your method will work and I did something similar but without a loop.
  • San Juan
    San Juan about 6 years
    @TimCooper Where can I find your solution?
  • mehdi
    mehdi almost 6 years
    this solution has low performance because of calling compress method several times.
  • Mabz
    Mabz over 5 years
    @reetu I got the message too and I suspect it is because I am using the Uri.getPath() operation to get the filename.
  • Acheme Paul
    Acheme Paul over 5 years
    Works well, clean and nice
  • Nainal
    Nainal over 5 years
    @MBH what should be the REQUIRED_SIZE for image size upto 4MB?
  • MBH
    MBH over 5 years
    @Nainal it’s been a while and i forgot the code i wrote :( you may checkout by testing anyway :)
  • Awadesh
    Awadesh over 5 years
    how can we define int DESIREDWIDTH, int DESIREDHEIGHT, I have to maintain the image originality and show on different devices, please help
  • HendraWD
    HendraWD about 5 years
    I think you should think of cleanup mechanism of your temporary image and folder. It will be better if the folder using cache/data folder of the application.
  • varun
    varun about 5 years
    @MBH I can't say if those are sad and happy ASCII emoticons or just parentheses preceded by a colon... hmmmmmmm... xD
  • varun
    varun about 5 years
    Ah yes, it works elegantly. For some reason, using image.compress(Bitmap.CompressFormat.FORMAT, QUALITY, fileOutputStream) did not work. However, your solution which uses the BitmapFactory produces 8x size reduction without compromising quality! Thanks!
  • Gene Bo
    Gene Bo almost 5 years
    This answer was helpful in understanding the code nec. for image scaling, which helped me put together the answer I posted here as well. In case it helps - the part that didn't work for me is that the final Bitmap bitmap was still the same size as the original input Bitmap image
  • mazend
    mazend about 4 years
    Goon answer but.. am I the only one who think the function name is not appropriate?
  • MBH
    MBH about 4 years
    @mazend You are right, you are free to edit my answer and put a better name :)
  • Thiago Silva
    Thiago Silva about 4 years
    It helped me to complete my code as it comes with great insights like the max size and other things. Its really very helpfull although its not a complete code.
  • Ast
    Ast about 4 years
    MBH as @Slimen says if I take a picture in protrait mode, it is rotated...but I can't understand why
  • Androidz
    Androidz almost 4 years
    Nice solution! I recommend just adding size check and setting scaleDivider based on this value as images very a lot are not constant on 2MB!
  • adegolyer
    adegolyer over 3 years
    For those with rotation problems: You can get image orientation from the originals exif data and write it to the copy.
  • PERAM BRAHMA REDDY
    PERAM BRAHMA REDDY about 3 years
    private ImageCodecInfo GetEncoder(ImageFormat format) { /*/ Hope this will work.Thanks in advance /*/ ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); foreach (ImageCodecInfo codec in codecs) { if (codec.FormatID == format.Guid) { return codec; } } return null; }
  • Fattie
    Fattie about 3 years
    Thanks so much GeneBo! This is like a top-100 answer on the entire site, buried here in obscurity. Sending a token bounty, thanks. I was just wondering, for completeness could U add a line preserving exif data (for us iOS programmers passing by :) ) Gene ROCKS !! what an awesome answer.
  • Gene Bo
    Gene Bo about 3 years
    Thanks for the kind word :) . exif data is outside my range. This search seemed to turn up some answers on SO: image preserving exif data android .. opting for Android to follow w/this answer. Perhaps you see a way to tie one of those in here?
  • Fattie
    Fattie about 3 years
    Got it. @GeneBo I sent a small bounty to say thanks - cheers
  • Gene Bo
    Gene Bo about 3 years
    Hi @Fattie, always good to hear an answer on SO helps someone, but wow what an unexpected surprise I found when checking back in after the weekend. That's very generous of you. You replenished the bounties I've given out in the last year - pretty cool, I appreciate it. And with your strong rank here, I take the gesture as a compliment .. no joke, Thank you
  • Emon Hossain Munna
    Emon Hossain Munna over 2 years
    Hey Emon Thanks Mate! Its Your Friend Of Friends (Habib UITS)
  • Gk Mohammad Emon
    Gk Mohammad Emon over 2 years
    Hi @EmonHossainMunna , Nice to meet you. See you soon.
  • KJEjava48
    KJEjava48 about 2 years
    @GeneBo Can we choose any compression format instead of Bitmap.CompressFormat.JPEG ?? Does this should be the same of the actual file ??
  • Bita Mirshafiee
    Bita Mirshafiee about 2 years
    for image rotation problem, do this: val oldExif = ExifInterface(file.path) val exifOrientation = oldExif.getAttribute(ExifInterface.TAG_ORIENTATION) if (exifOrientation != null) { val newExif = ExifInterface(file.path) newExif.setAttribute(ExifInterface.TAG_ORIENTATION, exifOrientation) newExif.saveAttributes() }