美文网首页
BitMap压缩以及二次采样

BitMap压缩以及二次采样

作者: Zane_Samuel | 来源:发表于2018-06-07 10:20 被阅读148次

    BitMap压缩以及二次采样

    标签: Android


    首先我们来了解一下什么是oom?

    1.什么是OOM?为什么会引起OOM?

    out of Memory(内存溢出)我们都知道Android系统会被每个App分配一个独立的空间,或者说分配一个Dalvik虚拟机(Dalvik是Google公司自己设计用于Android平台的虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。它可以支持已转换为 .dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且[1]每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。)这样每个APP都可以独立运行而不相互影响!而Android对于每个 Dalvik虚拟机都会有一个最大内存限制,如果当前占用的内存加上我们申请的内存资源超过了这个限制 ,系统就会抛出OOM错误!另外,这里别和RAM混淆了,即时当前RAM中剩余的内存有1G多,但是OOM还是会发生!别把RAM(物理内存)和OOM扯到一起!另外RAM不足的话,就是杀应用了,而不是仅仅是OOM了! 而这个Dalvik中的最大内存标准,不同的机型是不一样的

    这里可以获取系统分给你App多少内存

    ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
    Log.e("HEHE","最大内存:" + activityManager.getMemoryClass());
    //或者
    Runtime.getRuntime().maxMemory()来获取
    

    一、Bitmap:

    Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。
    常用方法:
    public void recycle()  // 回收位图占用的内存空间,把位图标记为Dead
    public final boolean isRecycled()  //判断位图内存是否已释放
    public final int getWidth() //获取位图的宽度
    public final int getHeight() //获取位图的高度
    public final boolean isMutable() //图片是否可修改
    public int getScaledWidth(Canvas canvas) //获取指定密度转换后的图像的度
    public int getScaledHeight(Canvas canvas) //获取指定密度转换后的图像的度
    public boolean compress(CompressFormat format, int quality, OutputStream stream) //按指定的图片格式以及画质,将图片转换为输出流。
    format:压缩图像的格式,如Bitmap.CompressFormat.PNG或 ·Bitmap.CompressFormat.JPEG
    quality:画质,0-100.0表示最低画质压缩,100以最高画质压缩。对于PNG等无损格式的图片,会忽略此项设置。
    stream: OutputStream中写入压缩数据。
    return: 是否成功压缩到指定的流。
    public static Bitmap createBitmap(Bitmap src)  //以src为原图生成不可变得新图像
    public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) //以src为原图,创建新的图像,指定新图像的高宽以及是否可变。
    public static Bitmap createBitmap(int width, int height, Config config) //创建指定格式、大小的位图
    public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) //以source为原图,创建新的图片,指定起始坐标以及新图像的高宽。
    BitmapFactory.Option可设置参数

    Option 参数类:

    public boolean inJustDecodeBounds //如果设置为true,不获取图片,不分配 内存,但会返回图片的高度宽度信息。
    如果将这个值置为true,那么在解码的时候将不会返回bitmap,只会返回这个bitmap 的尺寸。这个属性的目的是,如果你只想知道一个bitmap的尺寸,但又不想将其加载到内存时。这是一个非常有用的属性。
    public int 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。
    public int outWidth //获取图片的宽度值
    public int outHeight //获取图片的高度值
    表示这个Bitmap的宽和高,一般和inJustDecodeBounds一起使用来获得Bitmap的宽高,但是不加载到内存。
    public int inDensity //用于位图的像素压缩比
    public int inTargetDensity //用于目标位图的像素压缩比(要生成的位图)
    public byte[] inTempStorage  //创建临时文件,将图片存储
    public boolean inScaled //设置为true时进行图片压缩,从inDensity到inTargetDensity
    public boolean inDither  //如果为true,解码器尝试抖动解码
    public Bitmap.Config inPreferredConfig  //设置解码器
    这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes。
    public String outMimeType  //设置解码图像
    public boolean inPurgeable //当存储Pixel的内存空间在系统内存不足时是否可以被回收
    public boolean inInputShareable  //inPurgeable为true情况下才生效,是否可以共享一个InputStream
    public boolean inPreferQualityOverSpeed  //为true则优先保证Bitmap质量其次是解码速度
    public boolean inMutable  //配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段
    public int inScreenDensity  //当前屏幕的像素密度
    从资源中获取位图的方式有两种:通过BitmapDrawable或者BitmapFactory
    你可以创建一个构造一个BitmapDrawable对象,比如通过流构建BitmapDrawable:
    BitmapDrawable bmpMeizi = new BitmapDrawable(getAssets().open("pic_meizi.jpg"));
    Bitmap mBitmap = bmpMeizi.getBitmap();
    img_bg.setImageBitmap(mBitmap);

    工厂方法:

    public static Bitmap decodeFile(String pathName, Options opts)  //从文件读取图片
    public static Bitmap decodeFile(String pathName)
    public static Bitmap decodeStream(InputStream is)  //从输入流读取图片
    public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
    public static Bitmap decodeResource(Resources res, int id)  //从资源文件读取图片
    public static Bitmap decodeResource(Resources res, int id, 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) //从文件读取文件 与decodeFile不同的是这个直接调用JNI函数进行读取 效率比较高
    public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
    首先了解一下关于Bitmap的Config的理解
    A:透明度 R:红色 G:绿 B:蓝
    Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位
    Bitmap.Config ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位
    Bitmap.Config RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位
    Bitmap.Config ALPHA_8:每个像素占四位,只有透明度,没有颜色。

    bitmap内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
    起决定因素就是最后那个参数了,Bitmap'常见有2种编码方式:ARGB_8888和RGB_565,ARGB_8888每个像素点4个byte,RGB_565是2个byte,一般都采用ARGB_8888这种。那么常见的1080*1920的图片内存占用就是:
    1920 x 1080 x 4 = 7.9M

    常用的压缩方法:

    1.质量压缩

    private void compressQuality() {
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        mSrcSize = bm.getByteCount() + "byte";
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
        byte[] bytes = bos.toByteArray();
        mSrcBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }
    

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

    2.采样率压缩

    private void compressSampling() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;
        mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
    }
    

    采样率压缩其原理其实也是缩放bitamp的尺寸,通过调节其inSampleSize参数,比如调节为2,宽高会为原来的1/2,内存变回原来的1/4.

    3.放缩法压缩

    private void compressMatrix() {
        Matrix matrix = new Matrix();
        matrix.setScale(0.5f, 0.5f);
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        mSrcBitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
        bm = null;
    }
    

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

    4.RGB_565压缩

    private void compressRGB565() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
    }
    

    这是通过压缩像素占用的内存来达到压缩的效果,一般不建议使用ARGB_4444,因为画质实在是辣鸡,如果对透明度没有要求,建议可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。

    5.createScaledBitmap

    private void compressScaleBitmap() {
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        mSrcBitmap = Bitmap.createScaledBitmap(bm, 600, 900, true);
        bm = null;
    }
    

    将图片的大小压缩成用户的期望大小,来减少占用内存。

    为什么要二次采样

    默认情况下,bitmap每个像素点占用4个字节(ARGB_8888),比如一张3543×3503的图片差不多在内存中占用47M
    安卓系统给每个应用分配的内存都是有限的,可以使用Runtime.getRuntime().maxMemory()来获取
    内存有限空间,默认情况下图片存储又需要大量的空间,于是就容易产生OOM(内存溢出)

     private Bitmap getCompressBm(int id, int maxw, int maxh) {
            Bitmap bm = null;
            int iSamplesize = 1;
            //第一次采样
            BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
            //该属性设置为true只会加载图片的边框进来,并不会加载图片具体的像素点
            bitmapFactoryOptions.inJustDecodeBounds = true;
            bm = BitmapFactory.decodeResource(getResources(), id, bitmapFactoryOptions);
            Log.e("Tag", "onActivityResult: 压缩之前图片的宽:" + bitmapFactoryOptions.outWidth + "--压缩之前图片的高:"
                    + bitmapFactoryOptions.outHeight + "--压缩之前图片大小:" + bitmapFactoryOptions.outWidth * bitmapFactoryOptions.outHeight * 4 / 1024  + "kb");
            int iWidth = bitmapFactoryOptions.outWidth;
            int iHeight = bitmapFactoryOptions.outHeight;
            //对缩放比例进行调整,直到宽和高符合我们要求为止
            while (iWidth > maxw|| iHeight > maxh){
               //如果宽高的任意一方的缩放比例没有达到要求,都继续增大缩放比例
                //sampleSize应该为2的n次幂,如果给sampleSize设置的数字不是2的n次幂,那么系统会就近取
                iSamplesize = iSamplesize*2;//宽高均为原图的宽高的1/2  内存约为原来的1/4
                iWidth = iWidth/iSamplesize;
                iHeight = iHeight/iSamplesize;
            }
            //二次采样开始
            //二次采样时我需要将图片加载出来显示,不能只加载图片的框架,因此inJustDecodeBounds属性要设置为false
            bitmapFactoryOptions.inJustDecodeBounds = false;
            bitmapFactoryOptions.inSampleSize = iSamplesize;
            // 设置像素颜色信息
           // bitmapFactoryOptions.inPreferredConfig = Bitmap.Config.RGB_565;
            bm = BitmapFactory.decodeResource(getResources(),id, bitmapFactoryOptions);
    //默认的图片格式是Bitmap.Config.ARGB_8888
            Log.e("Tag", "onActivityResult: 图片的宽:" + bm.getWidth() + "--图片的高:"
                    + bm.getHeight() + "--图片大小:" + bm.getWidth() * bm.getHeight() * 4 / 1024  + "kb");
            return bm;//返回压缩后的照片
        }
    
    

    再一次感谢您花费时间阅读这份文章,祝您在这里记录、阅读、分享愉快!

    作者 @windrain_boy
    2017年06月06日

    相关文章

      网友评论

          本文标题:BitMap压缩以及二次采样

          本文链接:https://www.haomeiwen.com/subject/javwsftx.html