Как уменьшить размер файла изображения перед загрузкой на сервер

83

Многие приложения позволяют делиться изображением, которое выбрано из галереи.

Они загружают исходный файл изображения? Что вроде 1-3 мб? Или они обрабатывают?

В любом случае, как я могу взять изображение из пути к файлу, уменьшить его размер, уменьшив разрешение, сохранить его где-нибудь еще и попытаться загрузить?

Я старался:

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);
}

Но правильно ли они поступают? Потому что я видел ответы, предлагающие compression operation takes rather big amount of time здесь

user1537779
источник
1
преобразовать изображение в base64
Шиванг Триведи 02
При использовании base64 мне снились кошмары :( Я столкнулся с проблемами памяти при преобразовании огромных (2 МБ) файлов в base64.
user1537779 02
3
Base64 увеличит размер файла. Ваше решение в целом верное.
Trebron 02
Ваш код очень помог! Потратил несколько дней на то, чтобы делать то, что делаешь. Наткнулся на это, и это работает! Большое спасибо!
user3079872 03

Ответы:

65

Я использую эту функцию, чтобы уменьшить размер изображения перед его загрузкой, она уменьшает размер изображения почти до 200 КБ и сохраняет относительно хорошее качество, вы можете изменить его для достижения своей цели, изменив REQUIRED_SIZE и 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.

MBH
источник
3
what is REQUIRED_SIZE ? is it percentage of scaling or fixed dimen of output image?
Usman Rana
9
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 !!
SlimenTN
1
Works well, clean and nice
Acheme Paul
5
Goon answer but.. am I the only one who think the function name is not appropriate?
mazend
2
@mazend You are right, you are free to edit my answer and put a better name :)
MBH
51

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);
        }
    }

}
Biraj Zalavadia
источник
Really useful Answer.
Arun Badole
4
this didn't help, how to use this?
Syed Raza Mehdi
thanks alot. This is the only answer which worked for me.
Däñish Shärmà
4
You are compressing the image as JPEG but naming it with ".png" extension. Not that it actually matters anyway... but still
Logain
1
@Biraj Zalavadia I am trying to use this code, but getting null on unscaledBitmap any idea why it might return null.
reetu
7

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;
    }
emad pirayesh
источник
this solution has low performance because of calling compress method several times.
mehdi
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
Gene Bo
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.
Thiago Silva
5

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:

Gene Bo
источник
1
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!
Androidz
2

Here is my solution

/*
* This procedure will replace the original image
* So you need to do a tmp copy to send before reduce
*/
public static boolean reduceImage(String path, long maxSize) {
    File img = new File(path);
    boolean result = false;
    BitmapFactory.Options options = new BitmapFactory.Options();
    Bitmap bitmap = null;
    options.inSampleSize=1;
    while (img.length()>maxSize) {
        options.inSampleSize = options.inSampleSize+1;
        bitmap = BitmapFactory.decodeFile(path, options);
        img.delete();
        try
            {
                FileOutputStream fos = new FileOutputStream(path);
                img.compress(path.toLowerCase().endsWith("png")?
                                Bitmap.CompressFormat.PNG:
                                Bitmap.CompressFormat.JPEG, 100, fos);
                fos.close();
                result = true;
             }catch (Exception errVar) { 
                errVar.printStackTrace(); 
             }
    };
    return result;
}

EDIT Removed other procedures calls

San Juan
источник
There are a lot of file operations here.
Tim Cooper
I don't understand what you mean
San Juan
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.
Tim Cooper
1
@TimCooper Where can I find your solution?
San Juan
0

here's the method iam using in kotlin:

Note: i tried it with 3 images each of them was 6 mb and in one call

 private fun Bitmap.compress(cacheDir: File, f_name: String): File? {
    val f = File(cacheDir, "user$f_name.jpg")
    f.createNewFile()
    ByteArrayOutputStream().use { stream ->
        compress(Bitmap.CompressFormat.JPEG, 70, stream)
        val bArray = stream.toByteArray()
        FileOutputStream(f).use { os -> os.write(bArray) }
    }//stream
    return f
}
Mahmoud Ayman
источник
0

@MBH's answer has a strange and unnecessary way of getting a result that can cause the while loop to be bypassed if the file has a greater number of pixels than 6X REQUIRED_SIZE = 75;. o.inSampleSize = 6; is the cause of this, and only succeeds in convoluting what the allowed number of axial pixels (6*75 = 450) is. Below is a refurbished version of MBH's answer that also includes an option to not only adjust by pixels, but by storage size:

//This is the number of pixels that the width and height of the image will be allotted
//Note that the number of pixels implies (but does not exclusively determine) the file size of the image
private int REQUIRED_SIZE = 720;
//Method allows image files to be compressed to a standard maximum size
public void saveBitmapToFile(){

    try {

        //Initialize BitmapFactory options to downsize the image
        BitmapFactory.Options o = new BitmapFactory.Options();
        //Image dimensions do not change with change in pixel density
        o.inJustDecodeBounds = true;

        //Retrieve file and decode into bitmap, then close the stream when decode complete
        FileInputStream inputStream = new FileInputStream(imageFile);
        BitmapFactory.decodeStream(inputStream, null, o);
        inputStream.close();

        //Start with 1:1 scale size
        int scale = 1;

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

        //Create new Bitmap options instance to adjust the image scale...(1/2)
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        //...then apply with new input stream
        inputStream = new FileInputStream(imageFile);
        Bitmap selectedBitmap = BitmapFactory.decodeStream(inputStream, null, o2);
        inputStream.close();

        //Overwrite original file (type 'File') then compress the output file
        imageFile.createNewFile();
        FileOutputStream outputStream = new FileOutputStream(imageFile);
        selectedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);

        //maxImageSize is arbitrary file size (byte) limit
        if(imageFile.length() < maxImageSize) {

            //Set image in place and get the uri
            imagePost.setImageURI(imageUri);

            //Reset in case decreased previously
            REQUIRED_SIZE = 720;

        } else {

            Toast.makeText(PostActivity.this, "Your image is " + (imageFile.length() - maxImageSize) / 1000 + "kb too large to upload. Reattempting...", Toast.LENGTH_LONG).show();

            REQUIRED_SIZE = REQUIRED_SIZE - 240;

            //Loop method until goal file size achieved
            saveBitmapToFile();

        }

    } catch (Exception ignored) {

    }

}
Michael Plischke
источник
-1

Use this method that returns a bitmap image compressed to around 200 KB. You can configure it to get a bitmap image of whatever size you want.

public static Bitmap scaleImage(Context context, Uri photoUri) throws IOException {
    InputStream is = context.getContentResolver().openInputStream(photoUri);
    BitmapFactory.Options dbo = new BitmapFactory.Options();
    dbo.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(is, null, dbo);
    is.close();

    int rotatedWidth, rotatedHeight;
    int orientation = getOrientation(context, photoUri);

    if (orientation == 90 || orientation == 270) {
        rotatedWidth = dbo.outHeight;
        rotatedHeight = dbo.outWidth;
    } else {
        rotatedWidth = dbo.outWidth;
        rotatedHeight = dbo.outHeight;
    }

    Bitmap srcBitmap;
    is = context.getContentResolver().openInputStream(photoUri);
    if (rotatedWidth > MAX_IMAGE_DIMENSION || rotatedHeight > MAX_IMAGE_DIMENSION) {
        float widthRatio = ((float) rotatedWidth) / ((float) MAX_IMAGE_DIMENSION);
        float heightRatio = ((float) rotatedHeight) / ((float) MAX_IMAGE_DIMENSION);
        float maxRatio = Math.max(widthRatio, heightRatio);

        // Create the bitmap from file
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = (int) maxRatio;
        srcBitmap = BitmapFactory.decodeStream(is, null, options);
    } else {
        srcBitmap = BitmapFactory.decodeStream(is);
    }
    is.close();

    /*
     * if the orientation is not 0 (or -1, which means we don't know), we
     * have to do a rotation.
     */
    if (orientation > 0) {
        Matrix matrix = new Matrix();
        matrix.postRotate(orientation);

        srcBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(),
                srcBitmap.getHeight(), matrix, true);
    }

    String type = context.getContentResolver().getType(photoUri);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    if (type.equals("image/png")) {
        srcBitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
    } else if (type.equals("image/jpg") || type.equals("image/jpeg")) {
        srcBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
    }
    byte[] bMapArray = baos.toByteArray();
    baos.close();
    return BitmapFactory.decodeByteArray(bMapArray, 0, bMapArray.length);
}
Vishal Kumar
источник
1
Where is the getOrientation method implementation ?
Gaurav Sarma
1
getOrientation is missing
codeKiller
1
Please add all the required/depended methods in the answer.
Mohammedsalim Shivani