2.inSampleSize优化
1.Android中图片的压缩方法
===
2.inSampleSize优化
废话不多说先贴上常用的方法吧
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 heightRatio = Math.round((float) height / (float) reqHeight);
//宽度比例值
final int widthRatio = Math.round((float) width / (float) reqWidth);
//取比例值中的较大值作为inSampleSize
inSampleSize = heightRatio > widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
inSampleSize的默认值和最小值为1(当小于1时,解码器将该值当做1来处理),且在大于1时,该值只能为2的幂(当不为2的幂时,解码器会取与该值最接近的2的幂)。例如,当inSampleSize为2时,一个20001000的图片,将被缩小为1000500,相应地,它的像素数和内存占用都被缩小为了原来的1/4:
但是由于我们平时拍的照片都是长图,这种算法用在浏览相册图片的时候会导致压缩太过严重,导致照片不是很清楚
都是在1080 * 1776 (nexus 5)上load一张满屏图
width | height | Ratio |
---|---|---|
1536 | 2048 | 1 |
1944 | 2592 | 2 |
3264 | 2448 | 3 |
8264 | 2446 | 8 |
可以看出来如果我们图片是宽度大于高度的图片,图片ratio就会很大导致图片压缩太严重在显示可缩放的图片的时候,图片质量过于差。
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;
}
使用上面优化过后的方法后
width | height | Ratio |
---|---|---|
1536 | 2048 | 1 |
1944 | 2592 | 1 |
3264 | 2448 | 2 |
8264 | 2446 | 4 |
在宽大于高的时候对于图片质量有了较大的提升
使用这种算法不适宜用于一些不太在意图片质量,或者不支持缩放图片的地方。正常情况下还是可以用官方的方法。或者在方法中判断宽和高如果宽大于高使用下面的方法计算SampleSize
再贴一个SDK文档的算法,还没有用过~
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;
}
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;
}
}
//使用方式
opts.inSampleSize = computeSampleSize(opts, -1, 128*128);
延伸:一个Bitmap到底占用多大内存?系统给每个应用程序分配多大内存?
· Bitmap占用的内存为:像素总数 * 每个像素占用的内存。在Android中,Bitmap有四种像素类型:ARGB_8888、ARGB_4444、ARGB_565、ALPHA_8,他们每个像素占用的字节数分别为4、2、2、1。因此,一个20001000的ARGB_8888类型的Bitmap占用的内存为20001000*4=8000000B=8MB。
· Android根据设备屏幕尺寸和dpi的不同,给系统分配的单应用程序内存大小也不同,具体如下表(表格取自Android 4.4 Compatibility Definition Document (CDD)):
屏幕尺寸 | DPI | 应用内存 |
---|---|---|
small / normal / large | ldpi / mdpi | 16MB |
small / normal / large | tvdpi / hdpi | 32MB |
small / normal / large | xhdpi | 64MB |
small / normal / large | 400dpi | 96MB |
small / normal / large | xxhdpi | 128MB |
xlarge | mdpi | 32MB |
xlarge | tvdpi / hdpi | 64MB |
xlarge | xhdpi | 128MB |
xlarge | 400dpi | 192MB |
xlarge | xxhdpi | 256MB |
1.Android中图片的压缩方法
Bitmap
- Android中图片是以Bitmap(位图)形式存在,位图常见的文件格式有:.bmp .jpg .png .gif 。
- Bitmap的大小计算 = 图片的长度图片的宽度单位像素所占用的字节数。
- Bitmap的优缺点
- 优点:色彩变化丰富,可以改变任何形状的区域色彩显示效果。
- 缺点:放大和缩小都会引起像素的增加和缩小,这样会使图片出现失真或者锯齿形。另一个缺点就是像素越高数据量越大占用的内存越大。
常用压缩方法
质量压缩
- 质量压缩是不会改变图片的宽高(像素),是去改变图片的位深和透明度的。
- png是无损压缩的,所以质量压缩对png是不起作用的
/**
* 质量压缩
* 降低图片的质量,不会减少图片的像素,改变图片的位深和透明度,没有改变像素大小,所以不会减少占据的内存大小。
*/
private void qualityCompress(Bitmap.CompressFormat format, int quality, String type) {
try {
File file = new File(Environment.getExternalStorageDirectory(), "test_" + quality + format + "_" + type + ".jpg");
//得到一个文件输入流
mFileOutputStream = new FileOutputStream(file);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_pic);
// 图片的格式 quality是压缩完之后保存图片设置的质量参数 png是无损压缩,quality参数对png无效
bitmap.compress(format, quality, mFileOutputStream);
} catch (FileNotFoundException exception) {
exception.printStackTrace();
} finally {
if (mFileOutputStream != null) {
try {
mFileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
采样率压缩
- 降低图片的像素。数值越高,图片像素越低。
/**
* 尺寸压缩中的采样率压缩
* 改变了像素,减少了图片占用的内存,同样照片的清晰度也降低了。
* inSampleSize = 2 内存将减少 1/4 内存大小 = 图片宽/inSampleSize * 图片高/inSampleSize * 单位像素占用的字节大小
*/
private void loadImage() {
BitmapFactory.Options options = new BitmapFactory.Options();
//设置为true 并会将图片加载到内存中,但是可以获取到图片的宽和高 通过options
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.ic_pic, options);
int outWidth = options.outWidth;
int outHeight = options.outHeight;
Log.d(TAG, "loadImage: width = " + outWidth);
Log.d(TAG, "loadImage: height = " + outHeight);
//这个地方根据获取到大小和想要显示的大小做缩放
options.inSampleSize = calculateInSampleSize(options, 200, 200);
Log.d(TAG, "loadImage: inSampleSize = " + options.inSampleSize);
//设置为false 这回再去解码图片可以将其读取到内存中了。
options.inJustDecodeBounds = false;
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_pic, options);
File file = new File(Environment.getExternalStorageDirectory(), "text_" + "just_100_no_inSampleSize.jpg");
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// mImageView.setImageBitmap(mBitmap);
}
即:如果inJustDecoedBounds设置为true的话,解码bitmap时可以只返回其高、宽和Mime类型,而不必为其申请内存,从而节省了内存空间。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
/**
* 计算图片的缩放比例
*
* @param options
* @param reqHeight
* @param reqWidth
*/
private int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
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;
}
缩放法压缩
- 减小图片的像素,生成的图片file的大小也变小了。
/**
* 尺寸压缩中的 按缩放比压缩 这里的缩放比是事先定义好的 和采样率相比 采样率是根据给定的预期大小去计算缩放比
*
* @param mBitmap
* @param mFile
*/
private void compressBitmapToFile(Bitmap mBitmap, File mFile) {
//设置压缩倍数
int ratio = 2;
//压缩Bitmap到对应的尺寸 压缩格式 ARGB_8888 4字节 一个像素需要4个字节来存储
Bitmap resultBitmap = Bitmap.createBitmap(mBitmap.getWidth() / ratio, mBitmap.getHeight() / ratio, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(resultBitmap);
Rect rect = new Rect(0, 0, mBitmap.getWidth() / ratio, mBitmap.getHeight() / ratio);
canvas.drawBitmap(mBitmap, null, rect, null);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
File file = new File(Environment.getExternalStorageDirectory(), "test_compress_100.jpg");
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
try {
mFileOutputStream1 = new FileOutputStream(file);
mFileOutputStream1.write(byteArrayOutputStream.toByteArray());
mFileOutputStream1.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (mFileOutputStream1 != null) {
try {
mFileOutputStream1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}