Android Bitmap压缩的正确姿势

作者: Vghh | 来源:发表于2019-06-25 11:37 被阅读19次

    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群


    这里写图片描述

    相关文章

      网友评论

        本文标题:Android Bitmap压缩的正确姿势

        本文链接:https://www.haomeiwen.com/subject/qxltcctx.html