图片压缩的两种方法


android中对于图片的处理非常的常见,总体上希望图片能占用内存小,View上显示刚刚好,并且上传服务器大小符合要求且图片不模糊。要达到这些要求我们得知道一些知识

图片的格式

  • 有损压缩:顾名思义有损压缩是会损伤图片质量的,在压缩过程中会保持颜色逐渐变化,删除图片中颜色突然变化部分。这样能减少图片在内存和磁盘中的内存空间,同时压缩的越厉害图片显示效果越差。并且图片后期不能恢复
  • 无损压缩:无损压缩是对文件本身的压缩,和压缩文件一样,是对数据存储的优化。压缩后图片可以恢复,所以图片在内存和磁盘上并不能减少大小。
图片格式是否是有损压缩支持透明色常用场景
JPEG有损不支持适合色彩丰富的大图压缩,适合使用logo、线图
PNG无损支持透明和半透明安卓中常用切图都适合使用PNG
WebP同时提供有损和无损支持无损的WebP图片比PNG小26%,有损的WebP图片比JPEG小25-34%,非常优秀,Android 4.0+默认支持WebP,Android 4.2.1+开始支持无损WebP和带alpha通道的WebP,对系统支持有一定要求

可以看到,其实目前来说在不适配较低机型情况下,WebP是最好的选择,不过在一些大公司上比如微信上,就用到了另外的UI显示方式,Android微信上的SVG,有兴趣的可以了解下。

图片优化

在谈到图片压缩,首先说一下图片所占内存大小的计算方式:width* height*一个像素的所占用的字节数,这个计算方式适合图片来源于网络、磁盘、文件、流。对于来自res中的图片就不适合这个计算方式,具体可以查看博客图片占据内存大小的计算方式
知道了图片计算方式后,就可以减少宽高或降低一个像素所占字节数。来达到图片优化。同时除了处理图片本身还可以从内存预警,手动清除,图片弱引用等方面展开。

说到降低一个像素所占字节数那么就先说一下像素点的数据格式

  • ALPHA_8 --(1B) 每个像素都需要1(8位)个字节的内存,只存储位图的透明度,没有颜色信息
  • RGB_4444 --(2B) A(Alpha)占4位的精度,R(Red)占4位的精度,G(Green)占4位的精度,B(Blue)占4位的精度,加起来一共是16位的精度,折合是2个字节,也就是一个像素占两个字节的内存,同时存储位图的透明度和颜色信息。不过由于该精度的位图质量较差,官方不推荐使用
  • ARGB_8888 – (4B) 这个类型的跟ARGB_4444的原理是一样的,只是A,R,G,B各占8个位的精度,所以一个像素占4个字节的内存。由于该类型的位图质量较好,官方特别推荐使用
  • RGB_565 – (2B) R占5位精度,G占6位精度,B占5位精度,一共是16位精度,折合2个字节。这里注意的时,这个类型存储的只是颜色信息,没有透明度信息

质量压缩

减少像素点大小&&质量压缩

ARGB_8888占内存、清晰度高、支持透明效果
RGB_565节省内存、清晰度适中、不支持透明效果
首先我找到一张JPG图片http://pic.netbian.com/uploads/allimg/180315/110404-1521083044b19d.jpg图片宽高 3840*2160,保存到手机是4.1M,

同时找到一张PNGhttps://pic.ibaotu.com/00/70/48/30V888piCw87.jpg-0.jpg!ww7002图片宽高 5017*3543,保存到手机是2.8M,

	 /**
     * 加载网络图片
     *
     * @param url
     * @return
     */
    public static Bitmap getBitmapByUrl(String url) {
        URL imgUrl = null;
        Bitmap bitmap = null;
        try {
            imgUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) imgUrl
                    .openConnection();
            conn.setDoInput(true);
            conn.connect();
            InputStream is = conn.getInputStream();
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }


    /**
     * 根据本地图片地址加载图片
     *
     * @param url
     * @return
     */
    public static Bitmap getBitmapByPhone(String url) {
        Bitmap bitmap = BitmapFactory.decodeFile(url);

        return bitmap;
    }


    public static Bitmap getBitmapByPhoneCompress(String url, Bitmap.CompressFormat format, int quality) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Bitmap bitmap = BitmapFactory.decodeFile(url);
        bitmap.compress(format, quality, baos);
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
        return bitmap;
    }

    /**
     * 计算图片大小 适用于网络,本地等图片
     * 内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
     *
     * @param bitmap
     * @return
     */
    public static int getBitmapSize(Bitmap bitmap) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {    //API 19
            return bitmap.getAllocationByteCount();
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12
            return bitmap.getByteCount();
        }
        // 在低版本中用一行的字节x高度
        return bitmap.getRowBytes() * bitmap.getHeight();                //earlier version
    }


    public static Bitmap getBitmap565(String url) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        Bitmap bitmap = BitmapFactory.decodeFile(url, options);
        return bitmap;
    }


    /**
     * 根据图片路径进行压缩保存图片
     *
     * @param bitmap
     * @param format
     * @param quality
     * @param name
     * @param type
     */
    public static void saveImage(Bitmap bitmap, Bitmap.CompressFormat format, int quality, String name, String type) {
        String dir = Environment.getExternalStorageDirectory().getAbsolutePath();
        File dirFile = new File(dir);
        if (!dirFile.exists()) {
            dirFile.mkdirs();
        }

        File file = new File(dir, name + quality + type);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            bitmap.compress(format, quality, fos);
            fos.flush();
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

我们看下面的数据能得到信息:
首先我们根据图片在内存中的计算公式:JPG图片38402160=82944004=33177600,刚好等于JPG的图片内存大小。也就是说这个
JPG的图片一个像素占用4个字节,RGB_565的图片格式占用2个字节33177600/2=16588800,刚好等于上面16588800。

PNG图片50173543=177752314=71100924,刚好等于PNG的图片内存大小。也就是说PNG的图片一个像素占用4个字节。

图片类型图片手机大小图片内存大小设置图片格式为565后内存压缩90,压缩格式JPEG内存压缩50,压缩格式JPEG内存压缩90,压缩格式PNG内存压缩50,压缩格式PNG内存
JPG4211KB331776001658880033177600331776003317760033177600
PNG2881KB711009247110092471100924711009247110092471100924

结论:质量压缩不能减少在内存中的大小,JPG的图片如果原先每个像素占用4个字节设置图片格式后可能减少内存大小。

图片类型图片手机大小压缩90,压缩格式JPEG手机大小压缩50,压缩格式JPEG手机大小压缩90,压缩格式PNG手机大小压缩50,压缩格式PNG手机大小
JPG4211KB926.3kb203.5kb8.9M8.9M
PNG2881KB404.6kb224.1kb2.9M2.9M

结论:质量压缩后保存到手机设置压缩格式JPEG能减少在手机中的大小。如果JPG格式图片设置了压缩格式为PNG,那么JPG图片在手机中会更加大

 
            Log.e("LOG", "========JPG手机中小size" + new File(dir_JPG).length() / 1024 + "KB");
            Log.e("LOG", "========PNG手机中大小size" + new File(dir_PNG).length() / 1024 + "KB");

            Log.i("LOG", "========JPG内存中大小size" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhone(dir_JPG)));
            Log.i("LOG", "========PNG内存中大小size" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhone(dir_PNG)));

            Log.i("LOG", "========JPG设置图片格式为565后内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmap565(dir_JPG)));//本地图片修改图片格式大小
            Log.i("LOG", "========PNG设置图片格式为565后内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmap565(dir_PNG)));//本地图片修改图片格式大小

            Log.i("LOG", "========JPG压缩90,压缩格式JPEG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_JPG, Bitmap.CompressFormat.JPEG, 90)));
            Log.i("LOG", "========JPG压缩50,压缩格式JPEG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_JPG, Bitmap.CompressFormat.JPEG, 50)));

            Log.i("LOG", "========JPG压缩90,压缩格式PNG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_JPG, Bitmap.CompressFormat.PNG, 90)));
            Log.i("LOG", "========JPG压缩50,压缩格式PNG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_JPG, Bitmap.CompressFormat.PNG, 50)));


            Log.i("LOG", "========PNG压缩90,压缩格式JPEG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_PNG, Bitmap.CompressFormat.JPEG, 90)));
            Log.i("LOG", "========PNG压缩50,压缩格式JPEG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_PNG, Bitmap.CompressFormat.JPEG, 50)));

            Log.i("LOG", "========PNG压缩90,压缩格式PNG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_PNG, Bitmap.CompressFormat.PNG, 90)));
            Log.i("LOG", "========PNG压缩50,压缩格式PNG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_PNG, Bitmap.CompressFormat.PNG, 50)));


            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.JPEG, 100, "j_image", ".JPG");//3.9M
            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.JPEG, 90, "j_image", ".JPG");//926.3kb
            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.JPEG, 50, "j_image", ".JPG");///203.5kb

            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.PNG, 100, "j2_image", ".JPG");//8.9M
            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.PNG, 90, "j2_image", ".JPG");//8.9M
            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.PNG, 50, "j2_image", ".JPG");//8.9M

            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.JPEG, 100, "p_image", ".PNG");//1M
            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.JPEG, 90, "p_image", ".PNG");//404.6kb
            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.JPEG, 50, "p_image", ".PNG");//224.1kb

            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.PNG, 100, "p2_image", ".PNG");//2.9M
            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.PNG, 90, "p2_image", ".PNG");//2.9M
            BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.PNG, 50, "p2_image", ".PNG");//2.9M


分辨率压缩

分辨率压缩很好理解,原来图片是38402160,压缩到20001000,根据图片内存公式,那么图片自然在内存中就减少了.

	public static Bitmap bitmapFactory(String url, int width, int height) {

        // 配置压缩的参数
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; //获取当前图片的边界大小,而不是将整张图片载入在内存中,避免内存溢出
        BitmapFactory.decodeFile(url, options);
        options.inJustDecodeBounds = false;
        inSampleSize的作用就是可以把图片的长短缩小inSampleSize倍,所占内存缩小inSampleSize的平方
        options.inSampleSize = caculateSampleSize(options, width, height);
        Bitmap bm = BitmapFactory.decodeFile(url, options); // 解码文件
        return bm;
    }

    /**
     * 计算出所需要压缩的大小
     *
     * @param options
     * @param reqWidth  我们期望的图片的宽,单位px
     * @param reqHeight 我们期望的图片的高,单位px
     * @return
     */
    private static int caculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int sampleSize = 1;
        int picWidth = options.outWidth;
        int picHeight = options.outHeight;
        if (picWidth > reqWidth || picHeight > reqHeight) {
            int halfPicWidth = picWidth / 2;
            int halfPicHeight = picHeight / 2;
            while (halfPicWidth / sampleSize > reqWidth || halfPicHeight / sampleSize > reqHeight) {
                sampleSize *= 2;
            }
        }
        return sampleSize;
    }

     Log.i("LOG", "========JPG设置2000*1000分辨率后内存" + BitmapUtil.getBitmapSize(BitmapUtil.bitmapFactory(dir_JPG, 2000, 1000)));//8294400
            Log.i("LOG", "========JPG设置1000*500分辨率后内存" + BitmapUtil.getBitmapSize(BitmapUtil.bitmapFactory(dir_JPG, 1000, 500)));//2073600

            Log.i("LOG", "========PNG设置2000*1000分辨率后内存" + BitmapUtil.getBitmapSize(BitmapUtil.bitmapFactory(dir_PNG, 2000, 1000)));//17766672
            Log.i("LOG", "========PNG设置1000*500分辨率后内存" + BitmapUtil.getBitmapSize(BitmapUtil.bitmapFactory(dir_PNG, 1000, 500)));// 4439160


            BitmapUtil.saveImage(BitmapUtil.bitmapFactory(dir_JPG, 2000, 1000), Bitmap.CompressFormat.JPEG, 100, "j_image", ".JPG");//1920*1080 1M
            BitmapUtil.saveImage(BitmapUtil.bitmapFactory(dir_JPG, 1000, 500), Bitmap.CompressFormat.JPEG, 100, "j2_image", ".JPG");//960*540 253.4kb

            BitmapUtil.saveImage(BitmapUtil.bitmapFactory(dir_PNG, 2000, 1000), Bitmap.CompressFormat.JPEG, 100, "p_image", ".PNG");//2508*1771 361.7kb
            BitmapUtil.saveImage(BitmapUtil.bitmapFactory(dir_PNG, 1000, 500), Bitmap.CompressFormat.JPEG, 100, "p2_image", ".PNG");//1254*885 131kb

图片类型压缩比例2000*1000压缩比例1000* 500
JPG实际比例:920*1080 手机大小:1M实际比例:960*540 手机大小:253.4kb
PNG实际比例:2508*1771 手机大小:361.7kb实际比例:1254*885 手机大小:131kb

结论:分辨率压缩能减少图片在手机和内存中的大小,其实减小了图片大小,根据减少后的实际大小就能计算出图片在内存中的大小。根据表格中的实际比例,其实算出来的图片内存大小就是和我注释中的一样。那么根据以上原理,我们可以根据不用需求去处理内存OOM或者是上传图片限制大小。

注意:上面的保存图片都使用的是Bitmap.CompressFormat.JPEG

如何计算一张图片在内存中大小
图片的压缩
Android图片压缩以及优化
Android支持的图片格式
一张图片的内存大小是怎么计算的

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值