美文网首页
android基础 -- 图片

android基础 -- 图片

作者: BrokenDust | 来源:发表于2018-10-09 22:29 被阅读0次

    一.android图片格式

      Android 平台支持的图片格式主要有:JPEG、PNG、GIF、BMP和WebP(从android4.0开始支持),但Android开发中能够使用的编码格式只有三种:可查看Bitmap类的CompressFormat枚举值来确定。

    public static enum CompressFormat{
        JPEG,
        PNG,
        WEBP;
    

    一.android图片格式

      Android 平台支持的图片格式主要有:JPEG、PNG、GIF、BMP和WebP(从android4.0开始支持),但Android开发中能够使用的编码格式只有三种:可查看Bitmap类的CompressFormat枚举值来确定。

    public static enum CompressFormat{
        JPEG,
        PNG,
        WEBP;
    
        private CompressFormat(){
    
        }
    }
    
    • JPEG 一种有损压缩图片标准格式,不支持透明和多帧动画。控制压缩比,可以调整图片的大小;

    • PNG 一种武松压缩图片格式,支持图片透明通道。占用空间比较大,App瘦身的时候首要考虑减小的对象

    • GIF 支持多帧动画

    • WebP 支持有损和无损压缩、支持完整的透明通道、也支持多帧动画,能够大幅缩小图片文件的大小。 Android 4.2.1+对Webp完全支持,Anroid 4.0+ 到 Android 4.2.1+ 只支持完全不透明的Webp图 Android 4.0 一下默认不支持Webp 但是官方提供了相关支持源码: https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-0.5.1.tar.gz

    二.android 图片质量的区别

      在android的Bitmap.Config中有四个枚举类型:ALPHA_8、ARGB_4444、ARGB_8888和RGB_565

    • ALPHA_8: 每个像素都需要占一个字节的内存,之存储位图的透明信息,没有颜色信息

    • ARGB_4444:A、R、G、B 四个通道各占4位精度,加起来是16位精度,折合2个字节,既一个像素占2个字节的内存,精度较差,不推荐使用

    • ARGB_8888:A、R、G、B 四个通道各占8位精度,加起来是32位精度,折合4个字节,既一个像素占4个字节的内存,精度较高,推荐使用

    • RGB_565:R占5位,G占6位,B占5位,一共16位,和两个字节,它之存储颜色信息,并无透明信息

    三.图片占用内存的计算

      一张1920×1080 2.0M的*图片所占的内存空间并不是我们以为的2M,一张图片的所占的内存之和宽高以及图片质量相关,假设这张图片质量为ARGB_8888,那么它所占的内存应该是 1920×1080×4/(1024×1024) ≈ 7.9m,远远大于2.0m,对于一个多图的应用,这样很容易就引起我们常见的oom,所以就引出了下边的问题,大图怎样加载到内存

    四.加载大图到内存

      我们在编写android应用的时候每个应用都是分配有内存的,超过这个分配的内存量就会OOM,每个厂商机型给每个应用分配的内存大小都是不同的,可以使用下边的代码获取分配的内存大小

    int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024)

      因此我们在加载图片的时候最好先将其进行压缩,特别是图片比较多的应用。压缩后的图片大小应该和展示他的空间大小相近,在一个很小的空间上展示一个高分辨率的图片并不会给我们带来多少视觉上的好处,相反他浪费了我们宝贵的内存空间,带来一系列的负面影响。

      在android中我们加载一张图片到内存中用的核心类就是BitmapFactory这个类,这个类提供了诸如(decodeFile,decodeResource,decodeStrem等等)这些方法,可以根据实际图片的来源进行选择。 每个方法都可以传入一个可选参数---BitmapFactory.Options这个选项配置类,将这个对象的参数inJustDecodeBounds属性设置为true,就可以不为解析的图片对象分配内存。这样我们就可以根据BitmapFactory.Options中的outWidth和outHeight及outMineType得出图片的实际的宽高以及MIME类型值。

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
            int height = options.outHeight;
            int width = options.outWidth;
            String imageType = options.outMimeType;
    

      利用这个特性我们可以获取图片的大小,再根据实际需要的进行压缩,但是怎样进行压缩呢,这就需要BitmapFactory.Options中的另外一个参数inSimpleSize,比如我们前边说的1920×1080的图片,我们设置inSimpleSize为4,就能将图片压缩为480×270,这样所需要的内存大小为 480×270×4/(1024×1024) ≈ 0.5m ,所以获取合适的inSimpleSize值就能大大节省我们的内存空间,减小OOM的概率,但怎样获取合适的inSimpleSize值,一般我们计算方式为

    public static int calculateInSampleSize(BitmapFactory.Options options,
        int reqWidth, int reqHeight) {
        // 源图片的高度和宽度
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            // 计算出实际宽高和目标宽高的比率
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
            // 一定都会大于等于目标的宽和高。
            inSampleSize = Math.min(heightRatio,widthRatio);
        }
        return inSampleSize;
    }
    

      使用这个方法,有固定的套路,首先将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片。然后将BitmapFactory.Options连同期望的宽度和高度一起传递到到calculateInSampleSize方法中,就可以得到合适的inSampleSize值了。之后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为false,就可以得到压缩后的图片了。如下:

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {
        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        // 调用上面定义的方法计算inSampleSize值
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        // 使用获取到的inSampleSize值再次解析图片
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }
    

    五.对图片进行缓存

      对于像ListView,Recycler,ViewPager这些列表控件加载大量的图片,为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。这个时候缓存技术是不错的选择,对于android一般探究的是内存缓存和本地文件缓存,对于内存缓存android官方提供了一个类LruCache(android-support-v4),这个类使用了之中叫最近最少使用的算法,他的核心是由LinkedHashMap实现。最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。这个技术代替古老的软引用和弱引用。

    // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
    // LruCache通过构造函数传入缓存值,以KB为单位。
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    // 使用最大可用内存值的1/8作为缓存的大小。
    int cacheSize = maxMemory / 8;
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // 重写此方法来衡量每张图片的大小,默认返回图片数量。
            return bitmap.getByteCount() / 1024;
        }
    };
    
    mMemoryCache.put(key,bitmap);
    

      对于内存缓存我们使用的核心就是这个,通过重写LruCache的sizeOf方法,告诉我这个图片大小,在由LruCahce内部去处理是否进行释放之前的图片。而对于本地文件缓存JakeWharton大神也为我们实现了一个https://github.com/JakeWharton/DiskLruCache 他的原理其实跟LruCache的实现类似,都是在放进去之前比较需求的空间是否会超过目前剩余的空间,如果没有超过就放进去,如果超过就释放那些最近最少使用的那些应用或文件。

    六.对图片的一些常规操作

    • 复制图片

      tips:在Android中,直接从资源文件加载到的图片是不能进行操作的,只能进行显示想要进行操作,可以复制一张图片到内存,然后操作复制到的图片

    private Bitmap copyBitmap(){
        //解析原图
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kebi);
        //搞一个复制图,这个时候只是一个框框,并没有实际内容
        Bitmap copyBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
        //生成一个copyBitmap大小的canvas
        Canvas canvas = new Canvas(copyBitmap);
        //使用canvas照着原图进行绘画
        canvas.drawBitmap(bitmap,new Matrix(),new Paint());
        return copyBitmap;
    }
    
    • 旋转图片

      对一个图片进行旋转获得Bitmap

    private Bitmap rotateImage(int rot){
        //解析原图
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kebi);
        //搞一个复制图,这个时候只是一个框框,并没有实际内容
        Bitmap copyBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
        //生成一个copyBitmap大小的canvas
        Canvas canvas = new Canvas(copyBitmap);
    
        Matrix matrix = new Matrix();
        //旋转图片其实就是对矩阵进行转换   以中心点顺时针进行旋转   图片的多种变换都是来操作这个Matrix进行的
        matrix.setRotate(rot,bitmap.getWidth()/2,bitmap.getHeight()/2);
        canvas.drawBitmap(bitmap,matrix,new Paint());
        return copyBitmap;
    }
    
    如: 旋转
    • 缩放图片

      对一个图片进行缩放获得Bitmap

    private Bitmap scaleImage(float ratio){
        //解析原图
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kebi);
        //搞一个复制图,这个时候只是一个框框,并没有实际内容
        Bitmap copyBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
        //生成一个copyBitmap大小的canvas
        Canvas canvas = new Canvas(copyBitmap);
    
        Matrix matrix = new Matrix();
        //缩放图片其实就是对矩阵进行转换   根据中心点等比例缩放
        matrix.setScale(ratio,ratio,bitmap.getWidth()/2,bitmap.getHeight()/2);
        canvas.drawBitmap(bitmap,matrix,new Paint());
        return copyBitmap;
    }
    
    如: 缩放
    • 对图片进行镜像处理

      对一个图片进行镜像处理获得Bitmap

    private Bitmap imageImage(){
        //解析原图
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kebi);
        //搞一个复制图,这个时候只是一个框框,并没有实际内容
        Bitmap copyBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
        //生成一个copyBitmap大小的canvas
        Canvas canvas = new Canvas(copyBitmap);
    
        Matrix matrix = new Matrix();
        matrix.setValues(new float[]{//这是矩阵的默认值
                -1,0,0,
                0,1,0,
                0,0,1
        });
        //将图片移入屏幕
        matrix.postTranslate(bitmap.getWidth(), 0);
        canvas.drawBitmap(bitmap,matrix,new Paint());
        return copyBitmap;
    }
    
    如: 镜像
    • 倒影处理

      对一个图片进行倒影处理获得Bitmap

    private Bitmap invertedImage(){
        //解析原图
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kebi);
        //搞一个复制图,这个时候只是一个框框,并没有实际内容
        Bitmap copyBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
        //生成一个copyBitmap大小的canvas
        Canvas canvas = new Canvas(copyBitmap);
    
        Matrix matrix = new Matrix();
        //这是倒影矩阵的默认值
        matrix.setValues(new float[]{
                1,0,0,
                0,-1,0,
                0,0,1
        });
        matrix.postTranslate(0, bitmap.getHeight());
        canvas.drawBitmap(bitmap,matrix,new Paint());
        return copyBitmap;
    }
    
    如: 倒影
    • 偏色处理

      对一个图片进行偏色处理获得Bitmap

    private Bitmap filterImageColor(){
        //解析原图
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kebi);
        //搞一个复制图,这个时候只是一个框框,并没有实际内容
        Bitmap copyBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
        //生成一个copyBitmap大小的canvas
        Canvas canvas = new Canvas(copyBitmap);
        Paint paint = new Paint();
    
        ColorMatrix cm = new ColorMatrix();
        cm.set(//默认颜色矩阵,通过修改rgba来对图片颜色进行处理
                new float[]{
                        0,1,1,0,0,
                        0,1,0,0,0,
                        0,0,1,0,0,
                        0,0,0,1,0,
                }
        );
        /*
        颜色矩阵计算公式:
        red   = 1*128 + 0*128 + 0*128 + 0*0 +0
        blue  = 0*128 + 1*128 + 0*128 + 0*0 +0
        green = 0*128 + 0*128 + 1*128 + 0*0 +0
        alpha = 0*128 + 0*128 + 0*128 + 1*0 +0  透明度
        */
        paint.setColorFilter(new ColorMatrixColorFilter(cm));
    
        canvas.drawBitmap(bitmap,new Matrix(),paint);
        return copyBitmap;
    }
    
    如: 偏色
    • 灰度处理

      对一个图片灰度处理获得Bitmap

    public Bitmap convertGreyImg(Bitmap img) {
        int width = img.getWidth(); //获取位图的宽
        int height = img.getHeight(); //获取位图的高
        int []pixels = new int[width * height]; //通过位图的大小创建像素点数组
        img.getPixels(pixels, 0, width, 0, 0, width, height);
        int alpha = 0xFF << 24;
        //下边这段是
        for(int i = 0; i < height; i++) {
            for(int j = 0; j < width; j++) {
                int grey = pixels[width * i + j];
                int red = ((grey & 0x00FF0000 ) >> 16);
                int green = ((grey & 0x0000FF00) >> 8);
                int blue = (grey & 0x000000FF);
                grey = (int)((float) red * 0.3 + (float)green * 0.59 + (float)blue * 0.11);
                grey = alpha | (grey << 16) | (grey << 8) | grey;
                pixels[width * i + j] = grey;
            }
        }
        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
        result.setPixels(pixels, 0, width, 0, 0, width, height);
        return result;
    }
    
    如: 灰度处理
    • 圆角处理

      给一个如偏加上圆角

     public  Bitmap getRoundedCornerBitmap(Bitmap bitmap, float roundPx) {
    
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
                bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
    
        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        final RectF rectF = new RectF(rect);
    
        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
    
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);
    
        return output;
    }
    
    如: 圆角
    • 倒影

      给一个图片加上倒影

    public Bitmap createReflectionImageWithOrigin(Bitmap bitmap) {
        final int reflectionGap = 4;
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
    
        Matrix matrix = new Matrix();
        matrix.preScale(1, -1);
    
        Bitmap reflectionImage = Bitmap.createBitmap(bitmap, 0, height / 2,
                width, height / 2, matrix, false);
    
        Bitmap bitmapWithReflection = Bitmap.createBitmap(width,
                (height + height / 2), Bitmap.Config.ARGB_8888);
    
        Canvas canvas = new Canvas(bitmapWithReflection);
        canvas.drawBitmap(bitmap, 0, 0, null);
        Paint deafalutPaint = new Paint();
        canvas.drawRect(0, height, width, height + reflectionGap, deafalutPaint);
    
        canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null);
    
        Paint paint = new Paint();
        LinearGradient shader = new LinearGradient(0, bitmap.getHeight(), 0,
                bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff,
                0x00ffffff, Shader.TileMode.CLAMP);
        paint.setShader(shader);
        // Set the Transfer mode to be porter duff and destination in
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        // Draw a rectangle using the paint with our linear gradient
        canvas.drawRect(0, height, width, bitmapWithReflection.getHeight()
                + reflectionGap, paint);
        return bitmapWithReflection;
    }
    
    如: 倒影
    • 加水印

      给一个图片加上水印

     private Bitmap createBitmap(Bitmap src, Bitmap watermark) {
        String tag = "createBitmap";
        if (src == null) {
            return null;
        }
    
        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();
        int watermarkWidth = watermark.getWidth();
        int watermarkHeight = watermark.getHeight();
        // create the new blank bitmap
        Bitmap newb = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);// 创建一个新的和SRC长度宽度一样的位图
        Canvas cv = new Canvas(newb);
        cv.drawBitmap(src, 0, 0, null);// 在 0,0坐标开始画入src
        cv.drawBitmap(watermark, srcWidth/2 , srcHeight/2 , null);// 在src的右下角画入水印
        cv.save(Canvas.ALL_SAVE_FLAG);// 保存上边的操作
        cv.restore();// 存储
        return newb;
    }
    
    如: image
    • 将一个view转换成 Bitmap

      view---》bitmap 这个调用的时候需要注意调用时机,在该view已经显示到界面上了才有效,一般会配合view.getViewTreeObserver().addOnGlobalLayoutListener(listener)来使用效果更佳

    private Bitmap getViewBitmap(View target) {
    
        target.clearFocus();
        target.setPressed(false);
    
        boolean willNotCache = target.willNotCacheDrawing();
        target.setWillNotCacheDrawing(false);
        int color = target.getDrawingCacheBackgroundColor();
        target.setDrawingCacheBackgroundColor(0);
        if (color != 0) {
            target.destroyDrawingCache();
        }
        target.buildDrawingCache();
        //        //注意获取target的width和hight的时机
        //        Bitmap bitmap1 = Bitmap.createBitmap(target.getWidth(), target.getHeight(), Bitmap.Config.ARGB_8888);
        //        Canvas canvas = new Canvas(bitmap1);
        //        target.draw(canvas);
        Bitmap cacheBitmap = target.getDrawingCache();
        if (cacheBitmap == null) {
            return null;
        }
        Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
        target.destroyDrawingCache();
        target.setWillNotCacheDrawing(willNotCache);
        target.setDrawingCacheBackgroundColor(color);
        return bitmap;
    }
    

    参考:

    1. 郭霖:Android高效加载大图、多图解决方案,有效避免程序OOM
    2. 啸天AskSky: Android 图片图片处理

    相关文章

      网友评论

          本文标题:android基础 -- 图片

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