图片的二次采样

来源:互联网 发布:高洛峰细说php怎么样 编辑:程序博客网 时间:2024/05/29 12:32

图片的二次采样


参考:http://www.bubuko.com/infodetail-987767.html


1.什么是二次采样?
二次采样 就是对图片进行压缩,避免图片加载时可能产生的OOM异常


2.为什么要二次采样?

不知道大家在开发App的过程中有没有遇到过类似于图片墙这样的功能?在做图片墙的时候你有没有遇到过OOM异常呢?遇到了又是怎么解决的?再比如我现在有一张100M大的图片,我想把这张图片用一个ImageView显示出来,那么你的ImageView能够显示出来这张图片吗?上面我们说的这两种情况其实都涉及到图片加载时内存溢出的问题,内存溢出可能发生在加载一张大图的时候,也有可能发生在加载多张普通小图的时候,如果我们不对图片做二次采样,那么OOM就是一把悬在头上的剑,随时可能会掉下。所以一定要对图片进行二次采样。事实上,我在手机上显示一张分辨率特别大的图片和显示一张分辨率小的图片(不要小的太离谱即可),对用户的视觉体验来说,并不会有多大变化,但是对我们手机的内存来说,影响却是非常巨大的。总而言之,二次采样就是为了避免图片加载时的OOM异常。

3.二次采样分别是哪两次?每次采样的目的是什么?
既然是二次采样,那当然要分为两步了,下面我们来说说每次采样的主要工作:

1.第一次采样

第一次采样我主要是想要获得图片的压缩比例,假如说我有一张图片是200*200,那么我想把这张图片的缩略图显示在一个50*50的ImageView上,那我的压缩比例应该为4,那么这个4应该怎么样来获得呢?这就是我们第一步的操作了,我先加载图片的边界到内存中,这个加载操作并不会耗费多少内存,加载到内存之后,我就可以获得这张图片的宽高参数,然后根据图片的宽高,再结合控件的宽高计算出缩放比例。
(第一次,先取出图片的宽高,不取它的真实大小,因此bitmap是空的 )

2.第二次采样

在第一次采样的基础上,我来进行二次采样。二次采样的时候,我把第一次采样后算出来的结果作为一个参数传递给第BitmapFactory,这样在加载图片的时候系统就不会将整张图片加载进来了,而是只会加载该图片的一张缩略图进来,这样不仅提高了加载速率,而且也极大的节省了内存,而且对于用户来说,他也不会有视觉上的差异。
(第二次,取出它的大小,并进行压缩处理 )

4.二次采样关键类介绍
1、BitmapFactory.Options中的属性

在进行图片压缩时,是通过设置BitmapFactory.Options的一些值来改变图片的属性的,下面我们来看看BitmapFactory.Options中常用的属性意思:

- options.inPreferredConfig - 设置Bitmap的偏好配置,值有Bitmap.Config.ARGB_8888,Bitmap.Config.ARGB_4444,Bitmap.Config.ARGB_8888,Bitmap.Config.RGB_565。默认为ARGB_8888,顾名思义,这是设置Bitmap的显示质量的。
- options.outHeight - 得到该Bitmap的高。
- options.outWidth - 得到该Bitmap的宽。
- options.outMimeType - 得到该Bitmap的MIME值。
- options.inSampleSize - 压缩比例,如果options.inSampleSize = 4;那么压缩后的图片的宽和高都为原来图片的1/4,压缩后的图片的像素为原来图片的1/16。
- options.inJustDecodeBounds - 官方文档上是这样介绍的:
  - If set to true, the decoder will return null (no bitmap), but
the out... fields will still be set, allowing the caller to query
the bitmap without having to allocate the memory for its pixels.
  - 意思就是:如果设置为true,那么使用BitmapFactory.decodeStream(InputStream is, Rect outPadding,
Options opts)或BitmapFactory.decodeXXX(....,Options opts)方法并不会真的返回一个Bitmap对象,而是返回null,虽然返回null,但是我们却可以通过options来获得该Bitmap的一些值,如它的宽、高、MimeType等值。这样就不必为Bitmap对象分配内存空间也可以获得它的Width和Height,从而节约内存。所以这个属性对我们压缩图片前进行检查大有帮助。常用的技巧就是为了避免图片过大而导致OOM,所以在我们加载图片之前就通过设置它为true,获取到图片的宽、高等值,然后进行一些判断检查等,决定图片是否压缩。我们来看看加载一张405x579图片的例子


5.关键代码断如下:

A:直接处理从网络上获取得到的流数据,进行二次采样
public Bitmap commpressPic(InputStream inputStream, int targetWidthPx, int targetHeightPx) {
        Bitmap result=null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        //设置为true,只取得宽度与高度的数据
        options.inJustDecodeBounds=true;

        try {
            byte[] data=inputStream2ByteArr(inputStream);
        //进行转换
//        Bitmap bitmap=BitmapFactory.decodeStream(inputStream,null,options);
            Bitmap bitmap=BitmapFactory.decodeByteArray(data,0,data.length,options);
        Log.d("zzz","bitmap:"+bitmap);

        int picHeight=options.outHeight;
        int picWidth=options.outWidth;
        Log.d("zzz","图片实际的高度(px):"+picHeight+" 图片实际的宽度(px):"+picWidth);

        //计算缩放比率
        int simpleSize=1;
        if(picHeight>targetHeightPx || picWidth>targetWidthPx){

            int heightRate=picHeight/targetHeightPx;
            int widthRate=picWidth/targetWidthPx;

            Log.d("zzz","heightRate:"+heightRate+" widthRate:"+widthRate);
            //选取两者之间最小的
            simpleSize=heightRate<widthRate?heightRate:widthRate;

            Log.d("zzz","simpleSize:"+simpleSize);
        }

        options.inSampleSize=simpleSize;
        options.inJustDecodeBounds=false;
        //设置图片质量  rgb_565 一个像素在内存占两个字节   rgb_8888 一个像素在内存占四个字节
        options.inPreferredConfig= Bitmap.Config.RGB_565;

        //原因是decodeStream不能重复解析同一个网络流的inputStream
//        result=BitmapFactory.decodeStream(inputStream,null,options);
            result=BitmapFactory.decodeByteArray(data,0,data.length,options);
        Log.d("zzz","result:"+result);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private byte[] inputStream2ByteArr(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buff = new byte[1024];
        int len = 0;
        while ( (len = inputStream.read(buff)) != -1) {
            outputStream.write(buff, 0, len);
        }
        inputStream.close();
        outputStream.close();
        return outputStream.toByteArray();
    }

B:在sd卡上图片

 //压缩图片
    public void compressPic(String picPath,int picWidthpx,int picHeightpx){
        //sd卡图片的路径
        String path= Environment.getExternalStorageDirectory().getPath()+"/timg.jpg";
       /* Log.d("zzz","path:"+path);
        imgview.setImageURI(Uri.parse(path));*/

        Log.d("zzz","sd卡:"+Environment.getExternalStorageDirectory().getPath());

        //设置图片的宽度与高度
        int requestWidth=picWidthpx;
        int requestHeight=picHeightpx;

        BitmapFactory.Options options=new BitmapFactory.Options();
//        设置只加载边框
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeFile(path,options);

        //得到边框的宽与高
        int outWidth=options.outWidth;
        int outHeight=options.outHeight;

        //计算缩放比例
        int scaleX=outWidth/requestWidth;
        int scaleY=outHeight/requestHeight;
        Log.d("zzz","scaleX:"+scaleX+" scaleY:"+scaleY);

        //初使化压缩比例
        int sampleSize=1;
        if(scaleX>scaleY && scaleY>1){
            sampleSize=scaleX;
        }else if(scaleY>scaleX && scaleX>1){
            sampleSize=scaleY;
        }
        //进行第二次压缩
        options.inJustDecodeBounds=false;
        //设置压缩的比例
        options.inSampleSize=sampleSize;
        //设置图片质量  rgb_565 一个像素在内存占两个字节   rgb_8888 一个像素在内存占四个字节
        options.inPreferredConfig= Bitmap.Config.RGB_565;
        //以指定的选项压缩图片
        Bitmap newBitmap=BitmapFactory.decodeFile(path,options);

        //设置图片
        imgview.setImageBitmap(newBitmap);
    }
原创粉丝点击