本篇文章主要搞定的问题:
-
(1)Bitmap占用的手机内存怎样计算? 占用内存的大小和那些因素相关?
-
(2)质量压缩
-
(3)尺寸压缩
Bitmap vs 内存
图片占用内存大小的相关因素:
-
(1)图片的宽高
-
(2)图片单位像素占用的字节数
++占用的内存 = 图片的宽(像素) x 图片的高(像素) x 单位像素占用的字节数++
图片常用的压缩格式及单位像素分别占用的字节数
** A -- 透明度 R --红色 G -- 绿色 B--蓝色
-
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个字节(Android默认的压缩格式)
-
RGB_56 : 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
-
RGBA_F16 : 8 个字节
质量压缩
质量压缩是保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的。
图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
质量压缩对png格式的图片没有作用, 因为png格式的图片是 无损压缩的。
质量压缩是耗时的操作,故一般不能放在主线程中进行操作,可能会导致ANR。
/**
* 将bitmap按照最大的file size保存到指定文件
*
* @param picFilePath 待保存的文件路径
* @param bitmap 图片
* @param maxSize 限制保存后的最大大小 单位B
*/
public static void saveBitmapToFileWithCompress(String picFilePath, Bitmap bitmap,
final int maxSize) {
File photoFile = new File(picFilePath);
FileOutputStream fileOutputStream = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int scale = 100;
try {
fileOutputStream = new FileOutputStream(photoFile);
if (bitmap != null) {
if (bitmap.compress(Bitmap.CompressFormat.JPEG, scale, baos)) {
int baosSize = baos.toByteArray().length;
while (baosSize > maxSize && scale > 0) {
baos.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, scale, baos);
baosSize = baos.toByteArray().length;
scale -= 5;
}
// 缩放后的数据写入到文件中
baos.writeTo(fileOutputStream);
fileOutputStream.flush();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
photoFile.delete();
e.printStackTrace();
} finally {
try {
if (fileOutputStream != null)
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
尺寸压缩
-
(1)通过修改inSimpleSize的值来压缩
-
(2)通过修改inPreferredConfig来压缩
-
(3)通过Bitmap的静态方法 createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
boolean filter) { ... }
修改inSimpleSize的值来压缩
看了很多文章, 主要有2种写法, 稍微有点区别。
// 第一种,简单明了, 他是 四舍五入的
// Math.round 四舍五入 : 原始数据的基础上 + 0.5 再向下取整。
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 (width > reqWidth || height > reqHeight) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
}
return inSampleSize;
}
// 第二种, 相当于都 舍 而没有 入 的情况, 是直接向下取整。
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,
int reqHeight) {
// 获取原始图片的宽高
final int height = options.outHeight;
final int width = options.outWidth;
// inSimpleSize 为1的时候表示不压缩; 调节为2表示宽高都是原宽高的1/2,
// 这样按照上边说的计算内存的公式 内存就变成之前的1/4。
int inSampleSize = 1;
if (width <= 0 || height <= 0) {
return inSampleSize;
}
// 使用默认的方式取样
if (reqWidth <= 0 || reqHeight <= 0) {
// 高大于宽
if (height >= width) {
reqWidth = mPicMinLen;
reqHeight = reqWidth * height / width;
} else {
reqHeight = mPicMinLen;
reqWidth = reqHeight * width / height;
}
}
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;
}
修改inPreferredConfig来压缩
单位像素占用内存大小从小到大排列: [ALPHA_8, RGB_56, ARGB_4444, ARGB_8888, RGBA_F16]
RGB_56: 通过改变内存占用更小的编码格式来达到压缩效果,但是它没有透明度!!!
ARGB_4444: 画质不咋的...
综合考虑使用RGB_56更能符合压缩的要求,相对ARGB_8888能减少一半的内存。
...
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
bitmap = BitmapFactory.decodeFile(imagePath, options);
...
通过 Bitmap的静态方法 createScaledBitmap(...) 来压缩
源码是这样的:
/**
* Creates a new bitmap, scaled from an existing bitmap, when possible. If the
* specified width and height are the same as the current width and height of
* the source bitmap, the source bitmap is returned and no new bitmap is
* created.
*
* @param src The source bitmap.
* @param dstWidth The new bitmap's desired width.
* @param dstHeight The new bitmap's desired height.
* @param filter true if the source should be filtered.
* @return The new scaled bitmap or the source bitmap if no scaling is required.
* @throws IllegalArgumentException if width is <= 0, or height is <= 0
*/
public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
boolean filter) {
Matrix m = new Matrix();
final int width = src.getWidth();
final int height = src.getHeight();
if (width != dstWidth || height != dstHeight) {
final float sx = dstWidth / (float) width;
final float sy = dstHeight / (float) height;
m.setScale(sx, sy);
}
return Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
}
从源码注释可以知道 如果期望的宽高和原Bitmap的宽高是一样的,那么就不会新创建一个bitmap。 有时候这个可能是个大坑,要注意!!!
如: newBitmap = Bitmap.createScaledBitmap (oldBitmap, width, height, true); 紧接着如果你做 oldBitmap.recycle();
此时如果你给的width,height 和oldBitmap一样, newBitmap 就是 oldBitmap!!! 调用recycle()之后,你的 newBitmap 也就被recycle()而不能用了。
如有错误, 请指出。
网友评论