在Android中我们可以用ImageView展示图片,不同的图片会有不同的形状和大小。有时候我们原图的分辨率很高,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们很多宝贵的内存,容易造成OOM,这时我们只需要展示缩略图就行,如何从原图获取缩略图呢?我们可以用图片压缩技术,压缩后的图片大小应该和用来展示它的控件大小相近。
BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法。这些方法会尝试为已经构建的bitmap分配内存,这时就会很容易导致OOM。为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.image, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
那我们怎样才能对图片进行压缩呢?通过设置BitmapFactory.Options中inSampleSize的值就可以实现。比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素。原本加载这张图片需要占用12M的内存,压缩后就只需要占用0.75M了(假设图片是ARGB_8888类型,即每个像素点占用4个字节)。下面的方法可以根据传入的宽和高,计算出合适的inSampleSize值:
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
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值了。之后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为false,就可以得到压缩后的图片了。
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
写一个简单的例子,将任意一张图片压缩成100*100的缩略图,并在ImageView上展示。
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.drawable.image, 100, 100));
Bitmap类提供了静态方法createBitmap,可以将Bitmap原图进行裁剪、缩放得到新的Bitmap。
/** * @param Bitmap source:要从中截图的原始位图 * @param int x: 起始x坐标 * @param int y:起始y坐标 * @param int width:要截的图的宽度 * @param int height:要截的图的高度 * @param Matrix m:矩阵可以用来实现缩放、旋转等高级方式截图 * @param boolean filter:当进行的不只是平移变换时,filter参数为true可以进行滤波处理,有助于改善新图像质量;flase时,计算机不做过滤处理 */
Bitmap Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter)
我们可以将X、Y的压缩比例系数设置为Matrix的scale值来实现缩放:
/** * 图像的放大缩小方法 * @param src 原位图对象 * @param scaleX 宽度比例系数 * @param scaleY 高度比例系数 * @return 返回位图对象 */
public static Bitmap zoomBitmap(Bitmap src, float scaleX, float scaleY) {
Matrix matrix = new Matrix();
matrix.setScale(scaleX, scaleY);
Bitmap t_bitmap = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
return t_bitmap;
}
同时,Bitmap类还提供了另外一个静态方法createScaledBitmap,直接提供缩放后需要的宽高即可实现缩放。
/** * @param Bitmap source:要从中截图的原始位图 * @param int width:需要缩放到的宽度 * @param int height:需要缩放到的高度 * @param boolean filter:filter参数为true可以进行滤波处理,有助于改善新图像质量;flase时,计算机不做过滤处理 */
Bitmap Bitmap.createScaledBitmap(Bitmap source,int width, int height, boolean filter);
缩放图像:
/** * 图像放大缩小--根据宽度和高度 * @param src * @param width * @param height * @return */
public static Bitmap zoomBimtap(Bitmap src, int width, int height) {
return Bitmap.createScaledBitmap(src, width, height, true);
}