参考文章:http://www.codeceo.com/article/android-image-compression.html

在App开发过程中,免不了要上传图片到Server端。考虑到大图片带来的请求时长及传输失败风险,通常情况下都会对图片进行压缩处理后再上传。目前常用的两种压缩方式是:

1.通过降低图片质量;

2.通过减小图片分辨率;

详见开篇提到的参考文章。

这里再补充一下实现过程中的注意事项:

1.图片质量Quality不能无限制过低;需要根据压缩后图片的展示区域作调整。在200x200的展示区域下,图片质量不宜低于40;

2.有些图片的Exif信息里图片原始的方向是有转向的,比如具体展示时需要根据exif里的方向信息转向90度或者180度再显示。这些信息在图片压缩时可能会被压缩掉;从而导致这些图片展示时方向不对。

下面给出的代码实现综合考虑了上述因素后,实现原理如下:

1.根据图片的最大限制值来判断是否是需要压缩;

2.压缩前,先读取图片的exif信息;如果需要转向,则先对图片进行转向处理;

3.对纠正了转向之后的图片先进行图片分辨率的压缩,再进行图片质量的压缩;

下面是具体实现:

其中方法:

ArrayList<String> compress(Context context, ArrayList<String> srcPicList)

是入口方法。


package com.wuba.job.parttime.utils;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;

import com.wuba.commons.AppCommonInfo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

/**
 * Created by changwenna on 2016/11/25.
 */
public class PtBitmapUtils {
    private final static String PT_CACHE_TEMP_PIC_PATH = AppCommonInfo.sDatadir + File.separator + "ptjob" + File.separator + "temp" + File.separator;
    private static int UPLOAD_MAX_FILE_SIZE = 512000;// 500K
    private static int UPLOAD_QUALITY_LOW = 40;

    public static ArrayList<String> compress(Context context, ArrayList<String> srcPicList) {
        ArrayList<String> destPicList = new ArrayList<>();
        File file;
        for (int i = 0; i < srcPicList.size(); i++) {
            String sourceUrl = srcPicList.get(i);
            if (TextUtils.isEmpty(sourceUrl) || sourceUrl.startsWith("http")) {
                continue;
            }
            file = new File(sourceUrl);
            if (file.exists() && file.length() > UPLOAD_MAX_FILE_SIZE) {
                //超过限制,需要压缩
                String procedUrl = procPicFile(context, file.getAbsolutePath());
                if (TextUtils.isEmpty(procedUrl)) {
                    //压缩失败,则返回原图
                    destPicList.add(sourceUrl);
                }
                File procedFile = new File(procedUrl);
                if (procedFile.exists()) {
                    destPicList.add(procedFile.getAbsolutePath());
                }
            } else{
                destPicList.add(sourceUrl);
            }
        }
        return destPicList;
    }

    /**
     * 是否需要调整图片方向
     *
     * @param filePath
     * @return
     */
    public static boolean needRotate(String filePath) {
        try {
            ExifInterface exif = new ExifInterface(filePath);
            int orientation = exif.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_UNDEFINED);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    return true;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    return true;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    return true;
            }
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    public static void rotatedBitmap(Context context, String filePath, String dstFilePath) throws IOException {
        File file = new File(dstFilePath);
        if (file.getParentFile() != null && !file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        ExifInterface exif = new ExifInterface(filePath);
        int orientation = exif.getAttributeInt(
                ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_UNDEFINED);
        Matrix matrix = null;
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                matrix = new Matrix();
                matrix.postRotate(90);
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                matrix = new Matrix();
                matrix.postRotate(270);
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                matrix = new Matrix();
                matrix.postRotate(180);
                break;
        }
        Bitmap bmp = getSmallBitmap(context,
                Uri.fromFile(new File(filePath)));
        if (matrix != null) {
            Bitmap bmp2 = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(),
                    bmp.getHeight(), matrix, true);
            bmp.recycle();
            FileOutputStream fos = new FileOutputStream(dstFilePath);
            bmp2.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            fos.close();
            bmp2.recycle();
        } else {
            FileOutputStream fos = new FileOutputStream(dstFilePath);
            FileInputStream fis = new FileInputStream(filePath);
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
            fos.close();
            fis.close();
        }
    }

    /**
     * 通过压缩图片质量来压缩图片
     * 常用于图片上传
     *
     * @param image
     * @param dstFilePath
     */
    public static void compressImageByQuality(Bitmap image, String dstFilePath) {
        if (image == null) {
            return;
        }
        if (TextUtils.isEmpty(dstFilePath)) {
            return;
        }
        File sourecFile = new File(dstFilePath);
        if (!sourecFile.exists()) {
            return;
        }
        int quality = 100;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        while (baos.toByteArray().length / 1024 > UPLOAD_MAX_FILE_SIZE) {
            baos.reset();
            try {
                image.compress(Bitmap.CompressFormat.JPEG, quality, baos);
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
            if (quality <= UPLOAD_QUALITY_LOW) {
                break;
            }
            quality -= 10;
        }
        image.recycle();
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
        FileOutputStream fos;
        try {
            fos = new FileOutputStream(dstFilePath);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        bitmap.recycle();
    }

    /**
     * 默认先进行像素压缩,如果还达高于大小的限制,再进行图片质量的压缩
     *
     * @param srcPath
     * @param dstFilePath
     */
    public static void compressImage(String srcPath, String dstFilePath) {
        if (TextUtils.isEmpty(srcPath) || TextUtils.isEmpty(dstFilePath)) {
            return;
        }
        File sourecFile = new File(srcPath);
        if (!sourecFile.exists()) {
            return;
        }
        if (sourecFile.length() / 1024 <= UPLOAD_MAX_FILE_SIZE) {
            //原始文件不需要压缩
            return;
        }

        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        newOpts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(srcPath, newOpts);

        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        //现在主流手机比较多是800*480分辨率,所以目标高和宽我们设置为:
        float hh = 800f;
        float ww = 480f;
        //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1; //be=1表示不缩放
        if (w > h && w > ww) {
            //如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {
            //如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0) {
            be = 1;
        }
        newOpts.inSampleSize = be;
        newOpts.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
        FileOutputStream fos;
        try {
            fos = new FileOutputStream(dstFilePath);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        File dstFile = new File(dstFilePath);
        if (!dstFile.exists()) {
            return;
        }
        if (dstFile.length() <= UPLOAD_MAX_FILE_SIZE) {
            return;
        }
        compressImageByQuality(bitmap, dstFilePath);
    }

    public static Bitmap getSmallBitmap(Context context, Uri uri) {
        return getBitmap(context, uri, -1, 128 * 128);
    }

    public static Bitmap getBitmap(Context context, Uri uri, int maxWidth,
                                   int maxHeight) {
        try {
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            InputStream is = context.getContentResolver().openInputStream(uri);
            BitmapFactory.decodeStream(is, null, options);
            is.close();

            // Calculate inSampleSize
            options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);

            // Decode bitmap with inSampleSize set
            options.inJustDecodeBounds = false;

            is = context.getContentResolver().openInputStream(uri);
            Bitmap bmp = BitmapFactory.decodeStream(is, null, options);
            is.close();
            return bmp;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int initialSize = computeInitialSampleSize(options, reqWidth, reqHeight);
        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 2;
        }
        return roundedSize;
    }

    private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        double w = options.outWidth;
        double h = options.outHeight;

        int lowerBound = (maxNumOfPixels == -1) ? 1 :
                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength == -1) ? 128 :
                (int) Math.min(Math.floor(w / minSideLength),
                        Math.floor(h / minSideLength));
        if (upperBound < lowerBound) {
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) &&
                (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

    /**
     * @param context
     * @param mySourecUrl
     * @return
     */
    public static String procPicFile(Context context, String mySourecUrl) {
        String procedUrl = null;
        if (TextUtils.isEmpty(mySourecUrl)) {
            return procedUrl;
        }
        File sourecFile = new File(mySourecUrl);
        if (!sourecFile.exists()) {
            return procedUrl;
        }
        if (sourecFile.length() / 1024 <= UPLOAD_MAX_FILE_SIZE) {
            return mySourecUrl;
        }
        // 压缩前准备:调整图片方向,以免压缩后丢掉了exif信息导致图片显示方向异常
        if (needRotate(mySourecUrl)) {
            String urlRotated = PT_CACHE_TEMP_PIC_PATH + "tmp_rotate" + SystemClock.currentThreadTimeMillis() + ".jpg";
            File rotatedFile = new File(urlRotated);
            if (rotatedFile.exists()) {
                rotatedFile.delete();
                try {
                    rotatedFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                rotatedBitmap(context, mySourecUrl, urlRotated);
                procedUrl = urlRotated;
            } catch (Exception e) {
                e.printStackTrace();
                procedUrl = mySourecUrl;
            }
        } else {
            procedUrl = mySourecUrl;
        }

        String urlCompressed = PT_CACHE_TEMP_PIC_PATH + "tmp_compress" + SystemClock.currentThreadTimeMillis() + ".jpg";
        File compressFile = new File(urlCompressed);
        if (compressFile.exists()) {
            compressFile.delete();
        }
        //开始压缩图片文件
        compressImage(procedUrl, urlCompressed);
        File compressedFile = new File(urlCompressed);
        if (compressedFile.exists()) {
            procedUrl = urlCompressed;
        } else {
            procedUrl = null;
        }
        return procedUrl;
    }
}