Bitmap压缩方法

1、图片内存:

getRowBytes():每一行所占的空间数。
getByteCount():BitMap的大小。

bitmap的内存空间 = 图片宽度 * 图片高度 * 单位像素所占字节数(Byte)

详见:https://www.jianshu.com/p/0fbcadfd4213?winzoom=1

如果不需要alpha通道,特别是资源本身为jpg格式的情况下,用RGB_565格式解码更加节省内存。

2、图片采样率

        /**
         * If set to a value > 1, requests the decoder to subsample the original
         * image, returning a smaller image to save memory. The sample size is
         * the number of pixels in either dimension that correspond to a single
         * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
         * an image that is 1/4 the width/height of the original, and 1/16 the
         * number of pixels. Any value <= 1 is treated the same as 1. Note: the
         * decoder will try to fulfill this request, but the resulting bitmap
         * may have different dimensions that precisely what has been requested.
         * Also, powers of 2 are often faster/easier for the decoder to honor.
         */
        public int inSampleSize;

inSampleSize的值必须大于1。若inSampleSize的值为2,表示目标图片的宽、高都为原图的 1 / 2,此时内存占用只有原图的 1 / 4。

最新的官方文档中指出,采样率inSampleSize 的值,应当总是2的指数,比如1,2,4,8,16等,若传递给系统的值不是2的指数,那么系统会向下取整并选择一个最接近2的指数来代替。因此,在大部分情况下设置图片的采样率,只能达到节省内存的目的,并不能精确地控制图片的尺寸。

采样率计算:

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int width = options.outWidth;
        final int height = options.outHeight;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            //使用需要的宽高的最大值来计算比率
            final int suitedValue = reqHeight > reqWidth ? reqHeight : reqWidth;
            final int heightRatio = Math.round((float) height / (float) suitedValue);
            final int widthRatio = Math.round((float) width / (float) suitedValue);

            inSampleSize = heightRatio > widthRatio ? heightRatio : widthRatio;//用最大
        }

        return inSampleSize;
    }

官方文档方法:

    /**
     * 计算压缩的比例
     *
     * @param options        解析图片所需的BitmapFactory.Options
     * @param minSideLength  调整后图片最小的宽或高值,一般赋值为 -1
     * @param maxNumOfPixels 调整后图片的内存占用量上限
     * @return
     */
    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }
        return roundedSize;
    }

    /**
     * 计算原始大小
     *
     * @param options        解析图片所需的BitmapFactory.Options
     * @param minSideLength  调整后图片最小的宽或高值,一般赋值为 -1
     * @param maxNumOfPixels 调整后图片的内存占用量上限
     * @return
     */
    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 the larger one when there is no overlapping zone.
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

3、图片质量

/**
     * 质量压缩方法
     *
     * @param image
     * @return
     */
    public static Bitmap compressImage(Bitmap image) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        int options = 90;

        while (baos.toByteArray().length / 1024 > 100) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
            baos.reset(); // 重置baos即清空baos
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 这里压缩options%,把压缩后的数据存放到baos中
            options -= 10;// 每次都减少10
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
        return bitmap;
    }
  • CompressFormat.JPEG,

  • CompressFormat.PNG, PNG 格式是无损的,它无法再进行质量压缩,quality 这个参数就没有作用了,会被忽略,所以最后图片保存成的文件大小不会有变化;

  • CompressFormat.WEBP ,这个格式是 google 推出的图片格式,它会比 JPEG 更加省空间,经过实测大概可以优化 30% 左右。

对比:

  • 采样率压缩会改变图片的尺寸和内存;
  • 质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。

注意一点就是,质量压缩堆png格式这种图片没有作用,因为png是无损压缩。

日志详见:https://blog.csdn.net/harryweasley/article/details/51955467

4、综合使用(压缩至50k以下)

 /**
     * 压缩并保存图片
     * <p>
     * 图片小于50k
     *
     * @param localUrl 本地图片路径(例如:/storage/emulated/0/Pictures/multi_image_20180808_130928.jpg)
     * @param path     目标文件路径(例如:/storage/emulated/0/zhcx/img/cgzf/)
     * @param filename 文件名称(例如:33000019_0.jpg)
     */
    public static void saveCgzfPic(String localUrl, String path, String filename) {

        //获得图片的宽和高,但并不把图片加载到内存当中
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(localUrl, options);

        options.inSampleSize = computeSampleSize(options, -1, (int) (0.5 * 1024 * 1024));

        //使用获取到的inSampleSize再次解析图片
        options.inJustDecodeBounds = false;

        Bitmap bitmap = BitmapFactory.decodeFile(localUrl, options);

        LogUtil.myD("inSampleSize:" + options.inSampleSize);
        File rootFile = new File(path);
        if (!rootFile.exists()) {
            rootFile.mkdirs();
        }
        File file = new File(rootFile, filename);
        try {
            if (!file.exists()) {
                file.createNewFile();
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
            //质量压缩
            int quality = 95;

            while (baos.toByteArray().length / 1024 > 50) { // 循环判断如果压缩后图片是否大于50kb,大于继续压缩
                LogUtil.d("caowj", "length:" + baos.toByteArray().length + ",,,quality:" + quality);
                baos.reset(); // 重置baos即清空baos
                bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);// 这里压缩options%,把压缩后的数据存放到baos中

                //每次减少5%质量
                if (quality > 5) {//避免出现options<=0
                    quality -= 5;
                } else {
                    break;
                }
            }
            LogUtil.d("caowj", "2length:" + baos.toByteArray().length + ",,,2quality:" + quality);

            //保存图片
            FileOutputStream fos = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos);

//// TODO: 2018/8/8 问题:byte数组解析成bitmap后,再次解析成byte数组,变大了,为什么?
//            ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中
//            L.d("caowj", "3length:" + baos.toByteArray().length);
//
//            Bitmap bitmap2 = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
//
//            bitmap2.compress(Bitmap.CompressFormat.JPEG, 100, baos);
//            L.d("caowj", "sss:" + baos.toByteArray().length);
//            bitmap2.compress(Bitmap.CompressFormat.JPEG, 100, fos);

            fos.flush();
            fos.close();
            if (bitmap.isRecycled()) {
                bitmap.recycle();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Bitmap最全详解

猜你喜欢

转载自blog.csdn.net/zhijiandedaima/article/details/81530145