Bitmap的那些事

Bitmap占用内存大小的计算方式:

Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数

Bitmap编码

Bitmap.config


image.png

其中,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个字节

RGB_565

表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节

Bitmap.Config主要作用是:以何种方式像素存储。不同的配置将会影响图像的画质(色彩深度),位数越高画质越高,显然在这里ARGB_8888是最占内存的。当然,画质越高也就越占内存了。

Tips:由于ARGB_4444的画质惨不忍睹,一般假如对图片没有透明度要求的话,可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。

采样率

inSampleSize:这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4。

图片压缩

前面我们讲了bitmap的大小:
Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数

方式一 改变bitmap.config

根据我们前面的知识:Bitmap.config可以决定一个像素点占用的字节数,我们举一个实际例子:

    BitmapFactory.Options options = new BitmapFactory.Options();
    //不获取图片,不加载到内存中,只返回图片属性
    options.inJustDecodeBounds = false;
    BitmapFactory.decodeResource(getResources(),R.mipmap.ic_profile_cover, options);
    //图片的宽高
    int outHeight = options.outHeight;
    int outWidth = options.outWidth;
    Log.d("mmm", "图片宽=" + outWidth + "图片高=" + outHeight);
    //图片格式压缩
    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    options.inJustDecodeBounds = false;
    options.inSampleSize=2;
    Bitmap bitmap =  BitmapFactory.decodeResource(getResources(),R.mipmap.ic_profile_cover, options);
    float bitmapsize = getBitmapSize(bitmap);
    Log.d("mmm","压缩后:图片占内存大小" + bitmapsize + "MB / 宽度=" + bitmap.getWidth() + "高度=" + bitmap.getHeight());

    BitmapFactory.Options options1 = new BitmapFactory.Options();
    options1.inPreferredConfig = Bitmap.Config.RGB_565;
    options1.inJustDecodeBounds = false;

    Bitmap bitmap1 =  BitmapFactory.decodeResource(getResources(),R.mipmap.ic_profile_cover, options1);
    float bitmapsize1 = getBitmapSize(bitmap1);
    Log.d("mmm","压缩后:图片占内存大小" + bitmapsize1 + "MB / 宽度=" + bitmap1.getWidth() + "高度=" + bitmap1.getHeight());

}

/**
 * 得到bitmap的大小
 */
public  int getBitmapSize(Bitmap bitmap) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {    //API 19
        return bitmap.getAllocationByteCount()/1024/1024;
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12
        return bitmap.getByteCount()/1024/1024;
    }
    // 在低版本中用一行的字节x高度
    return bitmap.getRowBytes() * bitmap.getHeight()/1024/1024;
}
image.png

我们观察两次打印的日志发现RGB_565所占用的内存只有ARGB_888的一半。
例如,如果图片中包含透明度,那么对该图片解码时使用的配置就需要支持透明度,默认会使用ARGB_8888来解码。

如果直接设置 RGB_565:

对于一张透明图片(png),内存、宽高不变,bitmap 也不会失去透明度。
对于一张非透明图片(png、jpg),宽高不变,内存减小。
注意:由于ARGB_4444的画质惨不忍睹,一般假如对图片没有透明度要求的话,可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。

方式二 采样率压缩(改变bitmap的宽高)

我们知道通过改变采样率可以对bitmap宽高进行一定比例的压缩
下面我们举一个简单的例子:

    BitmapFactory.Options options = new BitmapFactory.Options();
    //不获取图片,不加载到内存中,只返回图片属性
    options.inJustDecodeBounds = false;
    BitmapFactory.decodeResource(getResources(),R.mipmap.ic_profile_cover, options);
    //图片的宽高
    int outHeight = options.outHeight;
    int outWidth = options.outWidth;
    Log.d("mmm", "图片宽=" + outWidth + "图片高=" + outHeight);
    //图片格式压缩
    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    options.inJustDecodeBounds = false;
    Bitmap bitmap =  BitmapFactory.decodeResource(getResources(),R.mipmap.ic_profile_cover, options);
    float bitmapsize = getBitmapSize(bitmap);
    Log.d("mmm","压缩后:图片占内存大小" + bitmapsize + "MB / 宽度=" + bitmap.getWidth() + "高度=" + bitmap.getHeight());

    BitmapFactory.Options options1 = new BitmapFactory.Options();
    options1.inPreferredConfig = Bitmap.Config.ARGB_8888;
    options1.inJustDecodeBounds = false;
    options1.inSampleSize=2;
    Bitmap bitmap1 =  BitmapFactory.decodeResource(getResources(),R.mipmap.ic_profile_cover, options1);
    float bitmapsize1 = getBitmapSize(bitmap1);
    Log.d("mmm","压缩后:图片占内存大小" + bitmapsize1 + "MB / 宽度=" + bitmap1.getWidth() + "高度=" + bitmap1.getHeight());

}
image.png

通过上面的例子我们可以看到当我们把inSampleSize设置成2的时候,图片的宽高变为原来的1/2,图片大小变成原来的1/4。所以我们可以通过采样率对图片进行压缩。

方式三 质量压缩

    Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.mipmap.test);
    iv_1.setImageBitmap(bitmap);
    float bitmapsize = getBitmapSize(bitmap);
    Log.d("mmm", "压缩后1:图片占内存大小" + bitmapsize + "MB / 宽度=" + bitmap.getWidth() + "高度=" + bitmap.getHeight());
    //压缩图像后,显示
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 1, bos);
    byte[] bytes = bos.toByteArray();
    Bitmap bitmap1 = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    iv_2.setImageBitmap(bitmap1);
    float bitmapsize1 = getBitmapSize(bitmap1);
    Log.d("mmm", "压缩后2:图片占内存大小" + bitmapsize1 + "MB / 宽度=" + bitmap1.getWidth() + "高度=" + bitmap1.getHeight());



public  Bitmap compressImage(Bitmap image) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
    image.compress(Bitmap.CompressFormat.JPEG, 50, baos);
    Log.d("mmm", "压缩比例"+"baos:"+baos.toByteArray().length);
    int options = 100;
    while ( baos.toByteArray().length / 1024>100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩
        baos.reset();//重置baos即清空baos
        image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中
        options -= 10;//每次都减少10
        Log.d("mmm", "压缩比例"+"options:"+options);
    }
    //把压缩后的数据baos存放到ByteArrayInputStream中
    ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
    Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null); //把ByteArrayInputStream数据生成图片
    return bitmap;
    
}
image.png

我们可以看到压缩前和压缩后bitmap大小并没有改变,因为质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
我们可以看到有个参数:quality,可以调节你压缩的比例,但是还要注意一点就是,质量压缩堆png格式这种图片没有作用,因为png是无损压缩。

缩放压缩法

/***
 * 缩放图片到指定宽和高
 *
 * @param bitmap
 * @param newWidth  缩放后的宽度
 * @param newHeight 缩放后的高度
 * @param isCanBig  是否允许放大(否则只做缩小处理)
 * @return
 */
public static Bitmap zoomImage(Bitmap bitmap, float newWidth, float newHeight, boolean isCanBig) {
    // 获取这个图片的宽和高
    float width = bitmap.getWidth();
    float height = bitmap.getHeight();
    // 如果原始尺寸比设定的小且不允许放大,则不用压缩
    if (!isCanBig && width <= newWidth && height <= newHeight) {
        return bitmap;
    }
    // 创建操作图片用的matrix对象
    Matrix matrix = new Matrix();
    // 计算宽高缩放率
    float scaleWidth = newWidth / width;
    float scaleHeight = newHeight / height;
    // 缩放图片动作
    matrix.postScale(scaleWidth, scaleHeight);
    Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, (int) width, (int) height, matrix, true);
    return newBitmap;
}

缩放压缩使用的是通过矩阵对图片进行裁剪,也是通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容