美文网首页
# Android学习笔记———在Android平台上的图片加载

# Android学习笔记———在Android平台上的图片加载

作者: 问荆_ | 来源:发表于2019-10-13 16:02 被阅读0次

    标签(空格分隔): Android


    图像数字化

    在现实生活中,我们通过眼睛看见的景象是连续不断的自然景象,我们想要通过计算机得到或处理这些真实的图像的话,我们就必须对这些图像进行数字化,图像数字化是用离散来表示连续,图像数字化分为三个步骤:

    • 采样:真实的图像行和列是连续的,我们每行和每列按照一定的控件间隔采样尽量多的色块,这每个色块就是像素。
    • 量化:用一个数字去表示一个色块对应的颜色,如果这个数字的位数越多,那么我们能表示的颜色信息就越详细
    • 编码:通常意思就是压缩编码,通过对之前得到的数字矩阵通过各种变换,达到压缩数据的目的。

    图像在文件中的存储

    我们都知道存储图像实际上就是存储它的像素值,将一张图像放的很大的话,我们会发现一个个排列有序的色块,用不同的二进制序列表示不同的颜色,这样来看的话,一幅图像就是一个由数字组成的颜色矩阵,通过一定规则存储这些颜色矩阵,我们就存储了这个颜色矩阵对应的图像。当然,通过一定的方式对这个颜色矩阵进行变换,我们就可以对这幅图像进行变换(伸缩、旋转、压缩等等)以达到我们想要的图片效果。

    基于Android操作图像

    1. 加载图片
      Android中提供了BitmapFactory这个类支持对图片的操作,这个类提供了四个方法:decodeFile(),decodeResource(),decodeSream()和decodeByteArray(),分别分别用于支持从文件系统、资源、输入流和字节数组中加载出一个Bitmap对象,这样我们就拿到了图片的对象,我们可以通过ImageView提供的方法setImageBitmap()将图片设置给该控件完成图片的加载。下面举个栗子:
    //从文件中加载bitmap
    
    private void loadBitmap (){
            Bitmap test = BitmapFactory.decodeFile("/storage/131B-1A02/IMG_20191011_135824.jpg");
            //Rect rect = new Rect(0,0,test.getWidth(),test.getHeight());
            showBitmap(test);
        }
    
        private void showBitmap(Bitmap bitmap){
            ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams)ivTest.getLayoutParams();
            params.width = bitmap.getWidth();
            params.height = bitmap.getHeight();
            ivTest.setImageBitmap(bitmap);
        }
        
        //从网上通过url加载bitmap
         private void loadBitmapFromHttp(final String url){
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    HttpURLConnection connection = null;
                    try {
                        URL path = new URL(url);
                        connection = (HttpURLConnection)path.openConnection();
                        connection.setRequestMethod("GET");
                        connection.setConnectTimeout(6000);
                        connection.setReadTimeout(6000);
    
                        InputStream in = connection.getInputStream();
                        final Bitmap gank = BitmapFactory.decodeStream(in);
                        Log.d("loadBitmap:",""+(gank==null));
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                if(gank!=null)
                                showBitmap(gank);
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }finally {
                        if(connection!=null)
                        connection.disconnect();
                    }
                }
            }).start();
        }
    

    2 . 高效的加载图片

    我们都知道,在Android值用于显示图像的控件是ImageView,把资源图像设置后,图像会根据ImageView的宽高伸缩。如果过资源图像分辨率很大,而ImageView的宽高很小,这时候我们就可以对图像进行压缩去更加高效的展示图像。我们可以通过Android中提供的BitmapFactory.Options这个类,这个类中有一个inSampleSize参数,这个参数决定了图像的压缩比,他有一下属性:

    • 这个参数只能是2的幂,不规范的设置都会向下取整转化为其相近的2的幂
    • 一个图像是1024 * 1024 * 8(8M)的,设置inSampleSize为2的话,宽和高的像素才都会少一半,最后压缩为512 * 512 * 8(2M)
    • 可以通过设置Option中另外一个标志位inJustDecodeBounds,预加载一次图片(设置了inJustDecodeBounds标志位的预加载是轻量级的操作),计算出需要的inSampleSize,然后在正常加载图像。

    下面举个例子:

     private Bitmap resizeBitmapFromFile(String path , int scaleFactor){
            final  BitmapFactory.Options options= new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(path,options);
            int width = options.outWidth;
            int height = options.outHeight;
            int requireWidth = width >> scaleFactor;
            int requireHeight = height >> scaleFactor;
            int inSampleSize = 1;
    
            if (height>requireHeight ||  width> requireWidth){
                final int halfHeight=height/2;
                final int halfWidth=width/2;
                //计算最大的采样率,采样率为2的指数
                while ((halfWidth/inSampleSize)>=requireWidth && (halfHeight/inSampleSize)>=requireHeight){
                    inSampleSize = inSampleSize << 1;
                }
            }
            options.inSampleSize = inSampleSize;
            options.inJustDecodeBounds = false;
            return BitmapFactory.decodeFile(path,options);
        }
    

    3 . 图像的变换

    前面讲了如何加载Bitmap,但是有些时候我们拿到的Bitmap并不适合直接拿来使用,我们要通过一些图像变换或者其他处理之后才能满足使用的需求。

    最常见的栗子就是圆角图像——用户想要设置圆形头像,但是我们从他指定的位置读出的图像都是矩形的,我们就需要进行一些操作去满足矩形图像转化成为圆形图像的应用,下面提供一个简单的思路:

    private Bitmap roundBitmap(Bitmap bitmap,float rx,float ry){
            //首先创建一个可变的位图对象,颜色空间是RGB,
            Bitmap result = Bitmap.createBitmap(requireWidth,requireHeight,
                    Bitmap.Config.ARGB_8888);
            final Paint paint = new Paint();
            //创建一个画布,并指定该画布能绘制之前我们创建的位图
            Canvas canvas = new Canvas(result);
            paint.setAntiAlias(true);
            RectF rectF = new RectF(0,0,requireWidth,requireHeight);
            //先画一个圆角矩形的图层
            canvas.drawRoundRect(rectF,rx,ry,paint);
            //然后指定图层的叠加模式为,形状取下层,图像取上层
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
            canvas.drawBitmap(bitmap,0,0,paint);
            return result;
        }
    

    这里的重点在于13行的图层叠加模式的使用,Android提供了多种不同的模式,通过这个方法设置不同的模式可以完成很多意向不到的变化。

    再举一个栗子:用户觉得只有圆形头像太单调,他想要一个笑脸的头像。刚听到用户的需求,某底层程序小王只有暗自叹气,但是冷静过后,想了想之前圆角图像的实现,发现好像并不是很难,撸着袖子开干:

    private Bitmap smileBitmap(Bitmap bitmap){
            Bitmap result = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),
                    Bitmap.Config.ARGB_8888);
            final Paint paint = new Paint();
            Canvas canvas = new Canvas(result);
            paint.setAntiAlias(true);
            RectF rectF = new RectF(0,0,bitmap.getWidth()/2,bitmap.getHeight()/2);
            canvas.drawArc(rectF,180,360,false,paint);
            RectF rectF1 = new RectF(bitmap.getWidth()/2,0,bitmap.getWidth(),bitmap.getHeight()/2);
            canvas.drawArc(rectF1,180,360,false,paint);
            RectF rectF2 = new RectF(0,0,bitmap.getWidth(),bitmap.getHeight());
            canvas.drawArc(rectF2,0,180,false,paint);
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
            canvas.drawBitmap(bitmap,0,0,paint);
            return result;
        }
    

    做完这个需求之后,他老是不安心,总觉得“贪得无厌”的用户还有更多的需求,他准备把这个问题抽象出来,用户无非是想要各种类型的头像,那么他可以准备各种图片框供用户选择,什么圆角图片框,三角图片框,笑脸图片框,花型图片框,心形图片框等等。用户选择完图片后,他可以直接把这图片套进这个图片框中就行了。

    过了几天这边又发现了几个问题,用户下线了之后,头像还是彩色的,让人觉得体验不好,需要下线后把图像转化成灰色的提醒一下别人。 接受这个需求的码农挠了挠脑袋,决定从图像的底层入手,把RGB的混合相加彩色空间,平均一下实现单通道的灰度图像:

     private Bitmap bitmapToGray(Bitmap bitmap){
            Bitmap result = bitmap.copy(Bitmap.Config.ARGB_8888,true);
            int width = result.getWidth();
            int height = result.getHeight();
    
            // 保存所有的像素的数组,图片宽×高
            int[] pixels = new int[width * height];
            result.getPixels(pixels,0,width,0,0,width,height);
            for(int i =0;i < pixels.length;i++){
                int a = (pixels[i] & 0xff000000)>>24; //透明通道
                int red  = (pixels[i] & 0x00ff0000) >> 16; //红色通道
                int green = (pixels[i] & 0x0000ff00) >> 8; //绿色通道
                int blue = pixels[i] & 0x000000ff; //蓝色通道
                int average = (red + green + blue )/3; // 转换成为灰度图像需要平均三个通道
                pixels[i] = (a << 24) | (average << 16) | (average << 8) | average;
            }
            result.setPixels(pixels,0,width,0,0,width,height);
            return result;
        }
    

    作者(小白)水平有限,如有问题还请大佬指点。

    相关文章

      网友评论

          本文标题:# Android学习笔记———在Android平台上的图片加载

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