Android Bitmap相关知识点——类型、创建、压缩、转换等

Bitmap

一、Bitmap的存储格式以及内存计算

当需要做性能优化或者防止OOM时,我们通常会使用RGB_565这种类型。
因为ALPHA_8类型的bitmap只有透明度,用处不多。ARGB_4444显示图片不清晰。ARGB_8888占用内存空间最多。

BItmap类型 一个像素所占内存
ALPHA_8 1字节 【8位( A:8)】
RGB_565 2字节 【16位(R:5;G:6;B:5)】
ARGB_4444 2字节【16位( A:4;R:4;G:4;B:4)】
ARGB_8888 4字节【32位( A:8;R:8;G:8;B:8)】

Bitmap所占内存大小 = 宽度像素 × 高度像素 × 一个像素所占的内存大小。

在Bitmap里有两个获取内存占用大小的方法:

  • getByteCount():API12加入,代表存储Bitmap的像素需要的最少内存。
  • getAllocationByteCount():API19加入,代表在内存中为BItmap分配的内存大小,代替了getByteCount()方法。

二、Bitmap的加载

如何加载一张图片呢?BitmapFactory 类提供了四种方法:decodeFile()decodeResource()decodeStream()decodeByteArray(),分别用于从文件系统、资源、输入流以及字节数组中加载一个Bitmap对象,其中decodeFile和decodeResource又间接调用了decodeStream方法,这四种方法对应着底层实现中相应的native方法。

decodeFile():

BitmapFactory.decodeFile(pathName: String!, opts: BitmapFactory.Options!)

decodeResource():

BitmapFactory.decodeResource(res: Resources!, resId: Int, opts: BitmapFactory.Options!)

decodeStream():

BitmapFactory.decodeStream(is: InputStream?, outPadding: Rect?, opts: BitmapFactory.Options?)

decodeByteArray():

BitmapFactory.decodeByteArray(data: ByteArray!, offset: Int,  length: Int, opts: BitmapFactory.Options!)

BitmapFactory.Options类

上面的四个方法都有一个相同的参数BitmapFactory.Options,这个参数作用很大。
通过这个参数可以对bitmap进行一些配置,例如,设置采样率来减少bitmap的像素,防止OOM。

BitmapFactory.Options类中一些重要的成员变量和方法:
inSampleSize:设置采样率,对图片进行缩放,例如:inSampleSize = 2时,加载出来的图片的宽高分别原来的二分之一,内存大小是原来的四分之一,inSampleSize的值需为2的指数。当设置的值不为2的指数时,系统会自动向下选取一个最接近的2的指数来代替。

outWidth:获取图片的宽度。

outHeight:获取图片的高度。

inJustDecodeBounds:布尔类型,当设置为true时,不获取图片,不分配内存,但可以获得图片的高度(options.outHeight)、宽度(options.outWidth)、MIME类型(options.outMineType)等。

inScreenDensity:获取当前屏幕的像素密度。

三、Bitmap的压缩方法

1、压缩格式

当对图片进行压缩时,有以下三种压缩格式:

CompressFormat 描述
Bitmap.CompressFormat.JPEG 表示以JPEG压缩算法进行图片压缩,压缩后的格式可以是 .jpg 或者 .jpeg,是一种有损压缩
Bitmap.CompressFormat.PNG 表示以PNG压缩算法进行压缩,是一种支持透明度的无损压缩格式
Bitmap.CompressFormat.WEBP 是一种支持有损压缩和无损压缩的图片文件格式,无损的webp格式图片体积比无损的png图片小

2、采样率压缩

如何压缩一张图片,使其所占内存变小?首先,最简单的方式就是通过设置采样率来缩小图片的尺寸,减小图片内存占用。
例如,ImageView的大小为100×100像素,而图片的原始大小为200×200,那么只需要将采样率inSampleSize设置为2即可。
如何确定采样率的值,可以通过下面这两个方法来计算得到一个适合的采样率。

fun decodeSampledBitmapFromResource(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int): Bitmap{
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeResource(res, resId, options)
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
        options.inJustDecodeBounds = false
        return BitmapFactory.decodeResource(res, resId, options)
    }

    private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
        val width = options.outWidth
        val height = options.outHeight
        println("width: $width ,height: $height")
        var inSampleSize = 1
        if (width > reqWidth || height > reqHeight) {
            val halfHeight = height / 2
            val halfWidth = width / 2
            while ((halfWidth / inSampleSize) >= reqWidth && (halfHeight / inSampleSize) >= reqHeight){
                inSampleSize *= 2
            }
        }
        println("inSampleSize: $inSampleSize")
        return inSampleSize
    }

这里用的是decodeResource()方法来加载图片,其它三个decode方法也支持采样率加载,并且处理方式也是类似的。
所以,想要通过采样率来高效的加载图片,主要的步骤流程是这样的:

  1. BitampFactory.OptionsinJustDecodeBounds参数设为true;
  2. BitampFactory.Options中取出图片的原始宽高信息,它们对应于outWidthoutHeight参数;
  3. 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize;
  4. BitampFactory.OptionsinJustDecodeBounds参数设置为false,然后重新加载图片。

3、质量压缩

采样率压缩会改变图片的原始尺寸,如果你想压缩图片大小的同时保持原来的图片尺寸,那可以采用下面这种方式来处理。
通过Bitmap对象的compress(format:Bitmap.CompressFormat, quality: Int, stream: OutputStream)方法,不仅可以进行图片质量的压缩,可以设置压缩后图片的格式。方法的第一个参数为压缩后的格式,第二个参数为压缩质量(0~100),100为不压缩图片质量,第三个参数就是要写入的输出流。
注意,如果设置格式为Bitmap.CompressFormat.PNG,那设置压缩质量quality是没有效果的,因为png为无损压缩。

具体使用方法,请看代码:

/**
     * 压缩Bitmap
     * quality为压缩质量(0~100)
     */
    fun compressBitmap(bitmap: Bitmap, quality: Int): Bitmap?{
        if (bitmap == null) {
            return null
        }
        var baos: ByteArrayOutputStream? = null
        try {
            baos = ByteArrayOutputStream()
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos)
            val bytes = baos.toByteArray()
            val isBm = ByteArrayInputStream(bytes)
            return BitmapFactory.decodeStream(isBm)
        } catch (e: OutOfMemoryError){
            e.printStackTrace()
        } finally {
            try {
                baos?.close()
            } catch (e: IOException){
                e.printStackTrace()
            }
        }
        return null
    }

四、Bitmap与Drawable相互转换

 /**
     * Drawable转Bitmap
     */
    fun drawableToBitmap(drawable: Drawable): Bitmap{
        // 获取drawable的宽高
        val width = drawable.intrinsicWidth
        val height = drawable.intrinsicHeight
        // 获取drawable的颜色类型
        val config = if(drawable.opacity != PixelFormat.OPAQUE) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
        // 建立bitmap对象
        val bitmap = createBitmap(width, height, config)
        // 创建对应的画布
        val canvas = Canvas(bitmap)
        drawable.setBounds(0, 0, width, height)
        // 将drawable内容画到画布上
        drawable.draw(canvas)
        return bitmap
    }

    /**
     * bitmap转drawable
     */
    fun bitmapToDrawable(resources: Resources, bitmap: Bitmap) = BitmapDrawable(resources, bitmap)

五、使用Matrix处理图片

通过Matrix可以对图片进行缩放、旋转、移动、裁剪等操作。
Matrix提供的一些常用方法

方法名 作用
setScale(sx:Float,sy:Float) 图片缩放,sx 和sy 为缩放的比例
setRotate(degrees:Float,px:Float,py:FLoat) 图片旋转,degrees为角度,px,py为旋转中心点坐标
setTranslate(dx:Float,dy:Float) 图片平移,dx 和dy 为移动的距离
setSkew(kx:Float,ky:Float) 图片倾斜,kx 和ky 为倾斜比例

使用方式:

/**
     * 缩放图片
     */
    fun scaleBitmap(bitmap: Bitmap): Bitmap{
        val matrix = Matrix()
        matrix.setScale(0.6f, 0.6f)
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
    }
上一篇:Lock wait timeout exceeded; try restarting transaction


下一篇:如何终止运行中的线程