Bitmap基本认识

2020-07-05  本文已影响0人  echoSuny

Bitmap在绘图中是非常重要的一个概念。在我们熟知的Canvas中就保存着一个Bitmap对象。当我们调用Canvas的各种绘制函数时,最终时绘制到其中的Bitmap 上的。在重写onDraw()函数时,这个函数中带着一个Canvas对象,只需要调用这个Canvas的绘制方法就能画出各种内容。其实真正的原因是View对应着一个Bitmap,onDraw()函数中的Canvas就是通过这个Bitmap创建出来的。

Bitmap在绘图中的使用

        val bitmap = BitmapFactory.decodeResource(resources, R.drawable.flower)
        val bitmapDrawable = BitmapDrawable(bitmap)
        iv.setImageDrawable(bitmapDrawable)
        val bitmap = Bitmap.createBitmap(300,300,Bitmap.Config.ARGB_8888)
        val mCanvas = Canvas(bitmap)
        mCanvas.drawColor(Color.BLUE)

最终蓝色会画在这个宽高都为300的Bitmap上。Bitmap可以保存本地,也可以直接画在View上。

Bitmap的格式

Bitmap是位图,也就是由一个个的像素点构成的。那么就涉及到如果存储像素点以及相关的像素点之间能否压缩,这就涉及到了压缩算法。

1、如何存储像素点

一张位图所占用的内存 = 图片宽 x 图片高 x 一个像素点占用的字节数(宽高的单位为像素)。在Android当中,一个像素点所占用的字节数是由Bitmap.Config中的枚举来表示的:

2、压缩格式

Bitmap的压缩主要使用Bitmap.CompressFormat中的成员表示:

Bitmap的创建

1 通过BitmapFactory创建Bitmap

BitmapFactory用于从各种文件,资源,数据流,字节数组中创建Bitmap对象。这是一个工具类,提供了大量的函数用于从不同的数据源中解析、创建Bitmap。

public static Bitmap decodeResource(Resources res, int id) 
public static Bitmap decodeResource(Resources res, int id, Options opts) 
public static Bitmap decodeFile(String pathName) 
public static Bitmap decodeFile(String pathName , Options opts) 
public static Bitmap decodeByteArray(byte[] data, int offset, int length) 
public static Bitmap decodeByteArray(byte[] data , int offset, int length , Options opts) 
public static Bitmap decodeFileDescriptor(FileDescriptor fd) 
public static Bitmap decodeFileDescriptor (FileDescriptor fd , Rect outPadding, Options opts) 
public static Bitmap decodeStream(InputStream is) 
public static Bitmap decodeStream(InputStream is, Rect outPadding , Options opts) 
public static Bitmap decodeResourceStream(Resources res, TypedValue value , InputStream is, Rect pad, Options opts)

都是一些简单的函数调用,其中用的比较少的可能就是decodeFileDescriptor()了。它的简单使用如下:

String path =”/xxx/xxx/demo.jpg”;
File putStream is== new FileinputStream(path);
bmp = BitmapFactory. decodeFileDescriptor (is.getFD ());
if (bmp == null) { 
    //TODO 文件不存在
}

我们有了文件的路径了,为什么不直接使用decodeFile()直接获取bitmap呢?这是因为使用decodeFileDescriptor()比decodeFile()方式更节省内存。所以如果内存不是很足的话就使用decodeFileDescriptor()吧。

1.1BitmapFactory.Options

这个参数的作用非常大,可以设置采样率,改变图片的宽高等手段以达到减少像素的目的,从而更有效的防止OOM。

        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, R.drawable.flower, options)
        val btmWidth = options.outWidth
        val btmHeight = options.outHeight
        fun caculateSample(options: BitmapFactory.Options, dstWidth: Int, dstHeight: Int): Int {
            var sampleSize = 1
            val outWidth = options.outWidth
            val outHeight = options.outHeight
            if ((outWidth > dstWidth) || (outHeight > dstHeight)) {
                val ratioW = (outWidth / dstWidth).toFloat()
                val ratioH = (outHeight / dstHeight).toFloat()
                sampleSize = min(ratioW, ratioH).toInt()
            }
            return sampleSize
        }

假设目标ImageView的宽高为100 x 100,而图片的宽高为 300 x 400。如果按照高来压缩,那么压缩之后的图片的尺寸应该是 75 x 100,而ImageView的大小为100 x 100,那么显示在ImageView上的时候就要在横向上进行拉伸。本来图片设置采样率之后就会失真,现在再一拉伸,图片就会更失真。所以要按照宽来压缩,保证图片是大于等于目标View的大小的,这样不至于图片太失真,唯一不足的就是内存可能多占一点,但这也比看着一张扭曲的图片要好吧。所以上面的代码中选择了宽高比中最小的作为采样率返回。

1.2加载一张Bitmap究竟要占多少内存

上面提到Bitmap所占的内存计算公式为:宽 x 高 x 每个像素点的字节大小。可往往事情并没有这么简单。这是因为Android系统在加载图片的时候会根据需要动态的缩放图片的尺寸。我们知道在Android项目的资源文件夹下面会有很多的资源文件夹来适配不同屏幕的分辨率。例如:drawable、drawable-ldpi、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等。这些文件夹和所对应的参数的关系如下:
插入一些关于屏幕各种尺寸单位的关系:

        val bitmap = BitmapFactory.decodeResource(resources, R.drawable.timg)
        Log.d("----->", "btmW: ${bitmap.width}")
        Log.d("----->", "btmH: ${bitmap.height}")
        Log.d("----->", "btmSize: ${bitmap.byteCount}")

这份日志是在分辨率为1920 x 1080,dpi为420的模拟器上的结果。由于xhdpi对应的dpi为320,则最终加载到内存中的图片宽就为500 * 420 / 320 = 656,刚好和打印的宽一致。
上面是把图片放在不同分辨率的文件夹下。那如果把图片直接放到SD卡当中呢?Android对此的处理是:不进行缩放。原本是多少像素,生成的bitmap就还是多少像素。

        val bitmap = BitmapFactory.decodeResource(resources, R.drawable.timg, options)
        Log.d("----->", "btmW: ${bitmap.width}")
        Log.d("----->", "btmH: ${bitmap.height}")
        Log.d("----->", "btmSize: ${bitmap.byteCount}")

        val path = "/storage/emulated/0/timg.jpeg"
        val btm = BitmapFactory.decodeFile(path)
        Log.d("----->", "sd-btmW: ${btm.width}")
        Log.d("----->", "sd-btmH: ${btm.height}")
        Log.d("----->", "sd-btmSize: ${btm.byteCount}")

总结:
(1)不同名称的资源文件夹是为了适配不同的屏幕分辨的,当屏幕分辨率与所在文件资源文件夹对应的分辨率相同时,直接使用图片,不会对图片进行缩放
(2)当屏幕分辨率与图片所在文件夹对应的分辨率不同时,会进行缩放 ,缩放比例是:屏幕分辨率/文件夹所对应的分辨率
(3)当从本地文件中加载图片时,不会对图片进行缩放
在了解了有关图片缩放的概念之后,就可以更好的学习下面几个BitmapFactory.Options中的其他几个变量:

        val options = BitmapFactory.Options()
        options.inDensity = 1
        options.inTargetDensity = 2
        val bitmap = BitmapFactory.decodeResource(resources, R.drawable.timg, options)
        Log.d("----->", "btmW: ${bitmap.width}")
        Log.d("----->", "btmH: ${bitmap.height}")
        Log.d("----->", "btmSize: ${bitmap.byteCount}")

        val path = "/storage/emulated/0/timg.jpeg"
        val btm = BitmapFactory.decodeFile(path, options)
        Log.d("----->", "sd-btmW: ${btm.width}")
        Log.d("----->", "sd-btmH: ${btm.height}")
        Log.d("----->", "sd-btmSize: ${btm.byteCount}")

可以看到不管是从资源文件夹还是从SD卡加载都根据我们设置的参数而进行了相应的缩放。这说明如果设置了这两个参数,不管是不是真的屏幕分辨率,都会按照我们设置的进行修改。

2 Bitmap静态方法创建Bitmap
3 Bitmap常用方法
copy(Bitmap.Config config , boolean isMutable) 
createBitmap(int width ,int height , Bitmap.Config config) 
createScaledBitmap(Bitmap src , int dstWidth , int dstHeight, boolean filter) 
// API 17 中引入
createBitmap (DisplayMetrics display, int width , int height , Bitmap.Config config)

其中createScaledBitmap()函数如果目标宽高和原图像的宽高是一样时,返回源图像,不会创建新的。此时如果源图像时可更改的,那返回的Bitmap就是可更改的。反之就是不可更改的。必须进行缩放之后才会无论源图像是否是可改的,都会返回可更改的。
注意:只有isMutable()返回true的Bitmap才可以作为画布。否则把不可更改的作为画布在上面绘制是会报错的。

        paint.maskFilter = BlurMaskFilter(6f, BlurMaskFilter.Blur.NORMAL)
        val alphaBitmap = bitmap.extractAlpha(paint, offsetXY)
        canvas.drawBitmap(alphaBitmap, 0f, 0f, paint)
        canvas.drawBitmap(bitmap, -offsetXY[0].toFloat(), -offsetXY[1].toFloat(), null)

可以看到图片的周围多了一圈红色的光晕效果。

if (bmp ! = null 品品 !bmp.isRecycle()) { 
    bmp.recycle(); //回收图片所占的内存
    bmp = null; 
}
        val mDensity = bitmap.density
        Log.d("----->", "before == width: ${bitmap.width}  height: ${bitmap.height}")
        canvas.drawBitmap(bitmap,0f,0f,null)
        bitmap.density = mDensity *2
        Log.d("----->", "after == width: ${bitmap.width}  height: ${bitmap.height}")
        canvas.drawBitmap(bitmap,0f,0f+bitmap.height,null)
宽高没有变化
上一篇下一篇

猜你喜欢

热点阅读