工具类
获取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 。其思路都是类似,
- 获取bitmap,得到bitmapConfig
- 根据宽高比获取采样比,
- 再次获取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 管理图片