Android图片压缩技术

在Android中我们可以用ImageView展示图片,不同的图片会有不同的形状和大小。有时候我们原图的分辨率很高,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们很多宝贵的内存,容易造成OOM,这时我们只需要展示缩略图就行,如何从原图获取缩略图呢?我们可以用图片压缩技术,压缩后的图片大小应该和用来展示它的控件大小相近。

BitmapFactory工厂类

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类

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);
    }

你可能感兴趣的:(android,压缩,技术)