Android开发Android技术知识Android开发

Android Bitmap压缩的正确姿势

2019-06-25  本文已影响19人  Vghh

Android开发中经常有如下需求:

1.由file decode bitmap
如果硬盘中的图片文件过大,不进行压缩,直接decode到内存,会有内存溢出的风险。
可利用BitmapFactory.Options设置inSampleSize(采样率)即可。
计算采样率,通常缩小图片的宽或者高(减少像素点,图片内容完整,如果减少得过分了,图片文件会模糊)为原来的1/2、1/4、1/8,就是缩放2的倍数。

 /*
     * 由file转bitmap
     */

    public static Bitmap decodeBitmapFromFilePath(String path, int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//如此,无法decode bitmap
        BitmapFactory.decodeFile(path, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;//如此,方可decode bitmap

        return BitmapFactory.decodeFile(path, options);
    }
     /*
     * 计算采样率
     */
    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

使用示例:

BitmapUtils.decodeBitmapFromFilePath("图片路径", 800,800);

2.拍照或者相册选择图片后压缩上传服务器(比如3M图片压缩到50-200KB)

拍照或者相册选择图片后,能得到一个图片的路径,首先需要根据图片路径,利用采样率压缩一次,
如BitmapUtils.decodeBitmapFromFilePath("图片路径", 800,800);
如此操作得到的bitmap再压缩为字节输出流,然后写入file,就可以上传服务器了(当然,上传图片到服务器有多种方式)。
bitmap再压缩为字节输出流(可以判断字节流大小,再决定是否继续压缩)再写入file,是耗时操作,必须放到子线程中执行。
如下所示:

        private String pathSource;//原图文件路径
        private String pathCompressed;//压缩后的图片文件路径
        private int kb_max = 1000;//压缩到多少KB,不能精确,只能<=kb_max
        private int quality_max = 80;//压缩精度,尽量>=50
        private int reqWidth = 1000;//期望的图片宽度
        private int reqHeight = 1000;//期望的图片高度
final Bitmap bitmap = decodeBitmapFromFilePath(compressFileBean.getPathSource(), compressFileBean.getReqWidth(), compressFileBean.getReqHeight());

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int quality = 80;
            //压缩格式选取JPEG就行了,quality,压缩精度尽量不要低于50,否则影响清晰度
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);

            while (byteArrayOutputStream.toByteArray().length / 1024 > compressFileBean.getKb_max() && quality > compressFileBean.getQuality_max()) {
                // 循环判断如果压缩后图片是否大于kb_max kb,大于继续压缩,
                byteArrayOutputStream.reset();
                quality -= 10;
                bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
            }
            try {
                final File fileCompressed = createFile(compressFileBean.getPathCompressed());
                FileOutputStream fileOutputStream = new FileOutputStream(fileCompressed);
                fileOutputStream.write(byteArrayOutputStream.toByteArray());//写入目标文件
                fileOutputStream.flush();
                fileOutputStream.close();
                byteArrayOutputStream.close();
                if (fileCompressed != null && fileCompressed.length() > 0)
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            //压缩成功
                            compressFileCallback.onCompressFileFinished(fileCompressed, bitmap);
                        }
                    });
            } catch (final Exception e) {
                e.printStackTrace();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //压缩失败
                        compressFileCallback.onCompressFileFailed("压缩图片文件失败" + e.getMessage());
                    }
                });
            }

使用示例:

   BitmapUtils.compressFile(new BitmapUtils.CompressFileBean.Builder()
                .setFileSource(file.getAbsolutePath())
                .setFileCompressed(getExternalCacheDir() + File.separator + "Feedback" + File.separator + System.currentTimeMillis() + ".jpg")
                .setKb_max(100)
                .setQuality_max(50)
                .setReqWidth(800)
                .setReqHeight(800).build(), new BitmapUtils.CompressFileCallback() {
            @Override
            public void onCompressFileFinished(File file, Bitmap bitmap) {
            
              //  if (feedbackUI.getRvAdapter().getList_bean().size() == 3)
               //     feedbackUI.getRvAdapter().setHaveFootView(false);
               // feedbackUI.getRvAdapter().add(file);
            }

            @Override
            public void onCompressFileFailed(String s) {

            }
        });

小编封装了一个常用的BitmapUtils:

public class BitmapUtils {
    private BitmapUtils() {
    }

    //可以直接将网络连接得到的输入流读取到字节流,压缩为bitmpa
    public static Bitmap decodeBitmapFromBytes(byte[] data, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(data, 0, data.length, options);
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(data, 0, data.length, options);
    }

    /*
     * 计算采样率
     */
    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

    /**
     * 裁剪
     *
     * @param bitmap 原图
     * @return 裁剪后的图像
     */
    public static Bitmap cropBitmap(Bitmap bitmap, float hRatioW) {
        int w = bitmap.getWidth(); // 得到图片的宽,高
        int h = bitmap.getHeight();
        return Bitmap.createBitmap(bitmap, 0, 0, w, (int) (w * hRatioW), null, false);
    }

    /**
     * 按比例缩放图片
     *
     * @param origin 原图
     * @param ratio  比例
     * @return 新的bitmap
     */
    public static Bitmap scaleBitmap(Bitmap origin, float ratio) {
        if (origin == null) {
            return null;
        }
        int width = origin.getWidth();
        int height = origin.getHeight();
        Matrix matrix = new Matrix();
        matrix.preScale(ratio, ratio);
        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
        return newBM;
    }

    /*
     * 质量压缩法:将图片文件压缩,压缩是耗时操作
     */
    public static void compressFile(CompressFileBean compressFileBean, CompressFileCallback compressFileCallback) {
        new CompressFileThread(compressFileBean, compressFileCallback).start();
    }
    /*
     * 由file转bitmap
     */

    public static Bitmap decodeBitmapFromFilePath(String path, int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//如此,无法decode bitmap
        BitmapFactory.decodeFile(path, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;//如此,方可decode bitmap

        return BitmapFactory.decodeFile(path, options);
    }

    /**
     * 根据路径 创建文件
     *
     * @param pathFile
     * @return
     * @throws IOException
     */
    private static File createFile(String pathFile) throws IOException {
        File fileDir = new File(pathFile.substring(0, pathFile.lastIndexOf(File.separator)));
        File file = new File(pathFile);
        if (!fileDir.exists()) fileDir.mkdirs();
        if (!file.exists()) file.createNewFile();
        return file;
    }
    /**
     * 读取图片的旋转的角度
     *
     * @param path 图片绝对路径
     * @return 图片的旋转角度
     */

    public static int getBitmapDegree(String path) {

        int degree = 0;

        try {

            // 从指定路径下读取图片,并获取其EXIF信息

            ExifInterface exifInterface = new ExifInterface(path);

            // 获取图片的旋转信息

            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,

                    ExifInterface.ORIENTATION_NORMAL);

            switch (orientation) {

                case ExifInterface.ORIENTATION_ROTATE_90:

                    degree = 90;

                    break;

                case ExifInterface.ORIENTATION_ROTATE_180:

                    degree = 180;

                    break;

                case ExifInterface.ORIENTATION_ROTATE_270:

                    degree = 270;

                    break;

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

        return degree;

    }
    /**
     * 将图片按照某个角度进行旋转
     *
     * @param bm     需要旋转的图片
     * @param degree 旋转角度
     * @return 旋转后的图片
     */

    public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {

        Bitmap returnBm = null;


        // 根据旋转角度,生成旋转矩阵

        Matrix matrix = new Matrix();

        matrix.postRotate(degree);

        try {

            // 将原始图片按照旋转矩阵进行旋转,并得到新的图片

            returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);

        } catch (OutOfMemoryError e) {

        }

        if (returnBm == null) {

            returnBm = bm;

        }

        if (bm != returnBm) {

            bm.recycle();

        }

        return returnBm;

    }
    /**
     * 对View进行量测,布局后截图
     *
     * @param view
     * @return
     */

    public static Object[] captureViewToBitmap(View view) {

        view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));


        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
        view.layout(0, 0, width, height);


        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);

        Canvas canvas = new Canvas(bitmap);
        view.draw(canvas);

        Object[] objects = new Object[3];
        objects[0] = bitmap;
        objects[1] = width;
        objects[2] = height;
        return objects;
    }

    /**
     * 对View进行量测,布局后截图
     *
     * @param view
     * @return
     */

    public static Bitmap captureViewToBitmap(View view, int reqWidth, int reqHeight) {

        view.layout(0, 0, reqWidth, reqHeight);

        Bitmap bitmap = Bitmap.createBitmap(reqWidth, reqHeight, Bitmap.Config.ARGB_4444);
//        CYLogUtils.log("bitmap原始width", bitmap.getWidth());
//        CYLogUtils.log("bitmap原始height", bitmap.getHeight());
        Canvas canvas = new Canvas(bitmap);
        view.draw(canvas);

        return bitmap;
    }
    /*
   压缩bitmap,会被压缩到指定宽高
    */
    public static Bitmap compressBitmap(Bitmap bitmap, int reqWidth, int reqHeight) {

        return Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, true);
    }
    private static class CompressFileThread extends Thread {
        private Handler handler_deliver = new Handler(Looper.getMainLooper());
        private CompressFileBean compressFileBean;
        private CompressFileCallback compressFileCallback;

        public CompressFileThread(CompressFileBean compressFileBean, CompressFileCallback compressFileCallback) {
            this.compressFileBean = compressFileBean;
            this.compressFileCallback = compressFileCallback;
        }

        @Override
        public void run() {
            super.run();
            final Bitmap bitmap = decodeBitmapFromFilePath(compressFileBean.getPathSource(), compressFileBean.getReqWidth(), compressFileBean.getReqHeight());

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int quality = 80;
            //压缩格式选取JPEG就行了,quality,压缩精度尽量不要低于50,否则影响清晰度
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);

            while (byteArrayOutputStream.toByteArray().length / 1024 > compressFileBean.getKb_max() && quality > compressFileBean.getQuality_max()) {
                // 循环判断如果压缩后图片是否大于kb_max kb,大于继续压缩,
                byteArrayOutputStream.reset();
                quality -= 10;
                bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
            }
            try {
                final File fileCompressed = createFile(compressFileBean.getPathCompressed());
                FileOutputStream fileOutputStream = new FileOutputStream(fileCompressed);
                fileOutputStream.write(byteArrayOutputStream.toByteArray());//写入目标文件
                fileOutputStream.flush();
                fileOutputStream.close();
                byteArrayOutputStream.close();
                if (fileCompressed != null && fileCompressed.length() > 0)
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            //压缩成功
                            compressFileCallback.onCompressFileFinished(fileCompressed, bitmap);
                        }
                    });
            } catch (final Exception e) {
                e.printStackTrace();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //压缩失败
                        compressFileCallback.onCompressFileFailed("压缩图片文件失败" + e.getMessage());
                    }
                });
            }

        }

        private void runOnUiThread(Runnable run) {
            handler_deliver.post(run);
        }

    }

    public static class CompressFileBean {
        private String pathSource;//原图文件路径
        private String pathCompressed;//压缩后的图片文件路径
        private int kb_max = 1000;//压缩到多少KB,不能精确,只能<=kb_max
        private int quality_max = 80;//压缩精度,尽量>=50
        private int reqWidth = 1000;//期望的图片宽度
        private int reqHeight = 1000;//期望的图片高度

        private CompressFileBean(Builder builder) {
            this.pathSource = builder.getFileSource();
            this.pathCompressed = builder.getFileCompressed();
            this.kb_max = builder.getKb_max();
            this.quality_max = builder.getQuality_max();
            this.reqWidth = builder.getReqWidth();
            this.reqHeight = builder.getReqHeight();
        }

        public static class Builder {
            private String pathSource;//原图文件路径
            private String pathCompressed;//压缩后的图片文件路径
            private int kb_max = 1000;//压缩到多少KB,不能精确,只能<=kb_max
            private int quality_max = 80;//压缩精度,尽量>=50
            private int reqWidth = 1000;//期望的图片宽度
            private int reqHeight = 1000;//期望的图片高度

            public String getFileSource() {
                return pathSource;
            }

            public Builder setFileSource(String pathSource) {
                this.pathSource = pathSource;
                return this;
            }

            public String getFileCompressed() {
                return pathCompressed;
            }

            public Builder setFileCompressed(String pathCompressed) {
                this.pathCompressed = pathCompressed;
                return this;


            }

            public int getKb_max() {
                return kb_max;
            }

            public Builder setKb_max(int kb_max) {
                this.kb_max = kb_max;
                return this;
            }

            public int getQuality_max() {
                return quality_max;
            }

            public Builder setQuality_max(int quality_max) {
                this.quality_max = quality_max;
                return this;
            }

            public int getReqWidth() {
                return reqWidth;
            }

            public Builder setReqWidth(int reqWidth) {
                this.reqWidth = reqWidth;
                return this;
            }

            public int getReqHeight() {
                return reqHeight;
            }

            public Builder setReqHeight(int reqHeight) {
                this.reqHeight = reqHeight;
                return this;
            }

            public CompressFileBean build() {
                return new CompressFileBean(this);
            }
        }

        public String getPathSource() {
            return pathSource;
        }

        public String getPathCompressed() {
            return pathCompressed;
        }

        public int getKb_max() {
            return kb_max;
        }

        public int getQuality_max() {
            return quality_max;
        }

        public int getReqWidth() {
            return reqWidth;
        }

        public int getReqHeight() {
            return reqHeight;
        }
    }

    public static interface CompressFileCallback {
        //图片压缩成功
        public void onCompressFileFinished(File file, Bitmap bitmap);
        //图片压缩失败
        public void onCompressFileFailed(String errorMsg);
    }
}

各位老铁有问题欢迎及时联系、指正、批评、撕逼

Github:
https://github.com/AnJiaoDe
简书:
https://www.jianshu.com/u/b8159d455c69

微信公众号


这里写图片描述

QQ群


这里写图片描述
上一篇下一篇

猜你喜欢

热点阅读