android 图片压缩及Bitmap系列文章——Bitmap

工具类

获取Bitmap 并进行采样率压缩

 /**
     * 从uri 得到 bitmap
     * Decode image from uri using given "inSampleSize", but if failed due to out-of-memory then raise
     * the inSampleSize until success.
     *
     * @param resolver
     * @param uri
     * @return
     */
    public static Bitmap getBitmapFromUri(ContentResolver resolver, Uri uri) {

        BitmapFactory.Options bmOpts = null;
        try {
            //先对图片的最大宽高进行限定,防止OOM
            bmOpts = BitmapUtils.decodeImageForOption(resolver, uri);
        } catch (Exception e) {

        }
        int be = 1;//be=1表示不缩放

        if (null != bmOpts) {
            int w = bmOpts.outWidth;
            int h = bmOpts.outHeight;
            float hh = MAX_H_DEFAULT;
            float ww = MAX_W_DEFAULT;
            if (w >= h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
                be = (int) (bmOpts.outWidth / ww) + 1;
            } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
                be = (int) (bmOpts.outHeight / hh) + 1;
            }
            if (be <= 0) {
                be = 1;
            }
        } else {
            bmOpts = new BitmapFactory.Options();
            bmOpts.inSampleSize = 2;
        }

        //旋转图片 动作
        bmOpts.inSampleSize = be;
        //bmOpts.inPreferredConfig = Bitmap.Config.RGB_565;
        InputStream stream = null;
        try {
            stream = resolver.openInputStream(uri);
            return BitmapFactory.decodeStream(stream, new Rect(), bmOpts);
        } catch (Exception e) {
            bmOpts.inSampleSize *= 2;
        } finally {
            closeSafe(stream);
        }
        return null;
    }

    /**
     * Close the given closeable object (Stream) in a safe way: check if it is null and catch-log
     * exception thrown.
     *
     * @param closeable the closable object to close
     */
    private static void closeSafe(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException ignored) {
            }
        }
    }

    /**
     * 从路径中得到bitmap
     * @param filePath
     * @return
     */
    public static Bitmap getBitmapFromFile(String filePath) {
        Bitmap bm = null;
        try {
            int degree = 0;
            ExifInterface exif = new ExifInterface(filePath);
            int orientation = exif.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;
            }
            //先对图片的最大宽高进行限定,防止OOM
            BitmapFactory.Options bmOpts = new BitmapFactory.Options();
            bmOpts.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(filePath, bmOpts);
            bmOpts.inJustDecodeBounds = false;
            int w = bmOpts.outWidth;
            int h = bmOpts.outHeight;
            float hh = MAX_H_DEFAULT;
            float ww = MAX_W_DEFAULT;
            int be = 1;//be=1表示不缩放
            if (w >= h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
                be = (int) (bmOpts.outWidth / ww) + 1;
            } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
                be = (int) (bmOpts.outHeight / hh) + 1;
            }
            if (be <= 0) {
                be = 1;
            }
            //旋转图片 动作
            Matrix matrix = new Matrix();
            matrix.postRotate(degree);
            bmOpts.inSampleSize = be;
            //bmOpts.inPreferredConfig = Bitmap.Config.RGB_565;
            bm = BitmapFactory.decodeFile(filePath, bmOpts);
            if (degree == 0) {
                return bm;
            }
            // 创建新的图片
            return Bitmap.createBitmap(bm, 0, 0,
                    bm.getWidth(), bm.getHeight(), matrix, true);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.gc();
        }
        return null;
    }

同理,根据 BitmapFactory 类中的方法,我们可以知道有哪些创建bitmap 的方法;

大致有四类,从 文件、工程内资源、二进制数组、流 可获取bitmap 。 上面的代码给出了 从 文件路径 、Uri (转化为流Stream) 获取bitmap 。其思路都是类似,

  1. 获取bitmap,得到bitmapConfig 
  2. 根据宽高比获取采样比,
  3. 再次获取bitmap 并带上options 

1 . 获取bitmap,得到bitmapConfig  

 BitmapFactory.Options bmOpts = new BitmapFactory.Options();
 bmOpts.inJustDecodeBounds = true;
 // 获取到bitmap的过程中来得到config
 BitmapFactory.decodeByteArray(bytes, 0, bytes.length, bmOpts);
 // 通过file 
 BitmapFactory.decodeFile(filePath, bmOpts);
  ....
  还可以通过资源、二进制数组

2 根据宽高比获取采样比

 法 一:一次性确定采样率

    bmOpts.inJustDecodeBounds = false;
            int w = bmOpts.outWidth;
            int h = bmOpts.outHeight;
            float hh = max_h;
            float ww = max_w;
            int be = 1;//be=1表示不缩放
            if (w >= h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
                be = (int) (bmOpts.outWidth / ww) + 1;
            } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
                be = (int) (bmOpts.outHeight / hh) + 1;
            }
            if (be <= 0) {
                be = 1;
            }

法二:循环减少2倍

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int height = options.outHeight;//获取原始图片的高度
int width = options.outWidth;//获取原始图片的宽度
int inSampleSize = 1;//设置采样率
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}

3 再次获取bitmap 并带上options 

 bm = BitmapFactory.decodeFile(filePath, bmOpts);

上面的Uri 、 File 都有相应的获取bitmap 代码,注意:File 的有些不一样,最后加了一个 Bitmap.createBitmap

if (degree == 0) {
    return bm;
            }
// 创建新的图片
return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);

因为通过这个Bitmap 类createBitmap 方法可以带一个矩阵Matrix ,matrix可以进行图片变换,如拉伸、旋转、缩放操作,在上面的操作中,就有检测图片是否有旋转,有的话就直接恢复成之前的样子。

质量Quality压缩

    /**
     * 只进行质量压缩
     * @param bitmap
     * @return
     */
    public static byte[] compressMass(Bitmap bitmap) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        int q = 100;
        while (stream.toByteArray().length / 1024 > 200) {
            stream.reset();
            q -= 10;
            if (q <= 0) break;
            bitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
        }
        return stream.toByteArray();
    }

一般而言,质量数设置为80 ,便能够得到50——60% 的压缩率。注意,如果使用100,则会使照片大小变大,非常奇怪,由此可见,默认的照片可能不是100。

Matrix 压缩

一般的,通过采样率压缩可以达到缩放的目的,采用Matrix 也可以实现此效果。代码如下:

    /**
     * 空间尺寸压缩 给出最大宽高
     * 通过matrix
     * @param bitmap
     * @param w
     * @param h
     * @return
     */
    public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        if (width <= w && height <= h)
            return bitmap;
        LogUtil.debugLog("压缩前尺寸" + width + " x " + height);
        float scale ;
        Matrix matrix = new Matrix();
        if (width > w) {
            scale = ((float) w) / width;
            matrix.postScale(scale, scale);
        } else {
            scale = ((float) h) / height;
            matrix.postScale(scale, scale);
        }
        LogUtil.debugLog("压缩率" + scale * 100 + "%");
        return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
    }

有一个疑问,这种方式和采样率都可以进行尺寸缩放,不知道这二者的区别,这两个也可以一起用。个人感觉不论是采用率还是Matrix 都是使图片尺寸减少,从而达到压缩的目的,二者本质应该一样的;质量压缩和上述两个不一样,质量压缩宽高都不会变;

小结论:Bitmap 的宽高就是实际图片的宽高,这一点可以通过日志打印来确定。

使用场景

图片上传:这种情况只讲究图片大小越小越好,所以无论质量还是尺寸都是会减少图片磁盘中尺寸,即图片上传时,两种方式都需要进行。

示例代码:

   /**
     * 先尺寸压缩
     * 再质量压缩 (大于200kb 就压缩) 或者直接设置 80 也可以 (设置为 80 压缩率接近50 - 60 %  )
     * 一般而言,用原生相机拍照 用此方法压缩后,压缩率可达 95%
     * @param bitmap
     * @return
     */
    public static byte[] compress(Bitmap bitmap) {
        Bitmap zoomBitmap = CompressUtil.zoomBitmap(bitmap, DEFAULT_MAX_W, DEFAULT_MAX_W);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        zoomBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        int q = 100;
        while (stream.toByteArray().length / 1024 > 200) {
            stream.reset();
            q -= 10;
            if (q <= 0) break;
            LogUtil.debugLog("当前质量" + q + "%");
            zoomBitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
        }
        return stream.toByteArray();
    }

    /**
     * 只进行质量压缩
     * @param bitmap
     * @return
     */
    public static byte[] compressMass(Bitmap bitmap) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        int q = 100;
        while (stream.toByteArray().length / 1024 > 200) {
            stream.reset();
            q -= 10;
            if (q <= 0) break;
            bitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
        }
        return stream.toByteArray();
    }


    /**
     * 空间尺寸压缩 给出最大宽高
     * 通过matrix
     * @param bitmap
     * @param w
     * @param h
     * @return
     */
    public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        if (width <= w && height <= h)
            return bitmap;
        LogUtil.debugLog("压缩前尺寸" + width + " x " + height);
        float scale ;
        Matrix matrix = new Matrix();
        if (width > w) {
            scale = ((float) w) / width;
            matrix.postScale(scale, scale);
        } else {
            scale = ((float) h) / height;
            matrix.postScale(scale, scale);
        }
        LogUtil.debugLog("压缩率" + scale * 100 + "%");
        return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
    }

 

Bitmap uploadBitmap = ImageUtil.getBitmapFromUri(getActivity().getContentResolver(), frontImgUri);
byte[] photo = CompressUtil.compress(uploadBitmap);

  getBitmapFromUri 这个方法是前面写过的,这样在上传时,就把能够压缩的都做了。

图片展示:Bitmap 展示会消耗内存,而消耗的内存计算方式 宽 * 高 * 一个像素点占用的字节数, 可以通过

getByteCount() 、 getAllocationByteCount() 

 获取内存消耗,一个像素点占用的字节数和Bitmap的 Config 有关,具体如下

ALPHA_8 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度

ARGB_4444 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节

ARGB_8888 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节 (系统默认格式)

RGB_565 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节

这个模式仅仅是android 内部定义的,它并不是图片的格式,所以改变这个可以改变内存占用情况,但不会减少实际尺寸。

 

写在最后:实际上通过尺寸和质量压缩两种方式,可以使一张图片大小减少90% 以上,有的达到 95% . 一般的质量压缩设置为 80 , 图片的宽高最大可以设置为不超过 1200 ,尺寸大小约束为不超过 200 kb ;


参考资料:

https://www.jianshu.com/p/c77158b6e07e

https://www.jianshu.com/p/4b0ba08bfb58/ (有错误,压缩部分有错误)

https://www.jianshu.com/p/e907eca48334 

官方资料 (重要)

https://developer.android.com/topic/performance/graphics  

https://developer.android.com/topic/performance/graphics/load-bitmap.html 高效加载大图片

https://developer.android.com/topic/performance/graphics/cache-bitmap.html 缓存图片

https://developer.android.com/topic/performance/graphics/manage-memory.html#inBitmap 管理图片

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章