开发者社区> kylinarm> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Android你不得不知道的图片压缩(一)

简介: 之前有个萌新在技术群里问图片压缩,然后我竟然还要查资料才回答他,没办法,谁让我也是个萌新,所以打算写一篇文章来复习一下图片相关的知识点。 一.URI 和 图片路径 一般来说从本地中拿到Bitmap就能展示图片到imageview,而且URI和图片在本地的路径都能拿到Bitmap,但是这两个不是同一个东西。
+关注继续查看

之前有个萌新在技术群里问图片压缩,然后我竟然还要查资料才回答他,没办法,谁让我也是个萌新,所以打算写一篇文章来复习一下图片相关的知识点。

一.URI 和 图片路径

一般来说从本地中拿到Bitmap就能展示图片到imageview,而且URI和图片在本地的路径都能拿到Bitmap,但是这两个不是同一个东西。
比如一张本地的图片:
URI:content://media/external/images/media/416651
路径:/storage/emulated/0/DCIM/Camera/b229c722-be7b-45e4-adda-5d7894480871.jpg

虽然不同,但是我们能够把URI转换成路径。

 String[] filePathColumn = {MediaStore.Images.Media.DATA};
            Cursor cursor = getContentResolver().query(uri,
                    filePathColumn, null, null, null);
            cursor.moveToFirst();
            int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
            //picturePath就是图片在储存卡所在的位置
            String picturePath = cursor.getString(columnIndex);
            cursor.close();

有人说这个方法只适用于API19以下的,我这里在19以上也能正常获取到。如果19以上获取不到路径就只能分情况去写。

二.获取本地图片并展示

1.首先要进入一个选择图片的页面

这样的页面可以自定义去写,但是一般系统都会提供一个简单的。

Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                startActivityForResult(intent, 0x1111);
2.然后在返回中做显示图片的操作
@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == 0x1111) {
            Uri uri = data.getData();
            ivShow.setImageURI(uri);
        }
    }

调用imageView.setImageURI(uri)方法就能直接用uri来显示本地的图片,但是有时候我们有其它的需求,要用Bitmap
那么我们可以把uri转成Bitmap之后再显示,那么上面的代码可以改成这样。

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == 0x1111) {
            Uri uri = data.getData();
            try {
                Bitmap photoBmp = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
                ivShow.setImageBitmap(photoBmp);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

但是有没有发现这里AS默认给我们加了个try-catch,为什么?因为这样搞可能会造成OOM,我们可以先来看看这个图片的大小。

bitmap.getAllocationByteCount()计算出这张图片的大小为

img_3c8d2d8df3c8b3aa4267ad3bdc8e12c3.png
image.png

然后我们再看看手机内部他的大小,比较一下。
img_a01fe292cdad1f69e7a3d97bce6378df.png
image.png

对计算的进行大小转换


img_344736174bdd9e4f30a659008d4f60f9.png
image.png

如果没算错的话这张图片在内存中占5.6MB,在存储空间中占2.97MB,也就是说Bitmap和File不是同个东西,而且Bitmap是file的展开,也可以说存储时file对Bitmap进行了压缩。这是一个细节的问题,虽然影响不大,但是必须注意。
这和之后说的图片压缩有很大的关系?压缩为什么不止一种,为什么有的压缩能让文件变小,但是图片不会失真等等,要在那种情况下用哪种压缩?我个人觉得要看这些之前首先要大概了解file和Bitmap的不同。

http://blog.csdn.net/xjz729827161/article/details/53586273这里有篇文章,虽然讲得内容少,但是讲得很好。

3.imageview展示图片的方法

上面的例子中我用过imageview.setImageURI(uri)和imageview.setImageBitmap(bitmap)两种方法来展示图片到imageview中,那说明imageview肯定有其它的展示方法。我可以先找到源码中的set方法


img_20bf6b48021849825ddcbce92b738a0b.png
image.png

(1)setImageResource和setImageDrawable我想就不用再多说了。
(2)setIcon,Iocn就是应用图标,这个方法是在6.0之后才使用的,也不是很普遍
(3)关于Tint的就是改变颜色用的
(4)setImageLevel这个方法是要配合level-list来用,主要是改变图片的状态。

三.存储图片到本地

上面讲了从本地获取问题,这里就讲存储到本地(不讲网络的那个,主要讲从图片从内存到本地存储空间的操作)

保存图片到本地就是操作文件的操作,那就会用到IO流,其实你从本地拿出图片转Bitmap也用到IO流,只是它内部封装起来了而已。不管是什么形式的,是bitmap还是文件或者是设么,都会转成字节保存到本地形成本地文件,这是io的内容,不多说,那就只拿bitmap来做举例。

1.把Bitmap保存到本地

既然之前的流程是:文件->bitmap->imagerView 。那么这里我们就反着弄,从imagerView 拿到bitmap再存到本地。

(1)imageView获取Bitmap :

 ivShow.setDrawingCacheEnabled(true);
 Bitmap bm = ivShow.getDrawingCache();

我截了一张图来表示输入imageview的bitmap和从imageview拿到的bitmap有什么不同。

img_2ff17fc50f6ede0d796d26acec2939c9.png
image.png

注意:从图中可以看出有两种情况,第一种情况我是imageview太大没有展示完,第二种情况展示完,会发现没展示完的情况下,从imageview中获取到的bitmap和传进去的不同。这说明两个bitmap是不同的,和imageview的宽度和高度有关,这里要注意一下。

(2)bitmap用流存到本地

private void savePhoto(){
        ivShow.setDrawingCacheEnabled(true);
        Bitmap bm = ivShow.getDrawingCache();
        Log.v("mmp","bm大小:"+(bm.getRowBytes() * bm.getHeight()));
        // 保存
        File file = new File(Environment.getExternalStorageDirectory(),"newpic.jpg");
        if (file.exists()){
            file.delete();
        }
        try {
            FileOutputStream outputStream = new FileOutputStream(file);
            bm.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
            outputStream.close();
            Log.v("mmp","图片存到本地的大小:"+file.length());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

img_d6bf7be55f210aa0f7d55ca5549476cf.png
image.png

img_c5dd062a39a5c8ffe81e53a059690c7e.png
image.png

img_df23dbb968dd6a2eb623ab352775fff6.png
image.png

三张图分别是原图的大小,打印的文件大小和保存到本地的新突破的大小。其实我也有疑问为什么会不一样?这个问题虽然对我讲的内容没多大关系,但是我还是找到答案后再更新补充,这里的获取保存前后为什么会不一样。

(3)改变imageview大小查看结果

img_bec378d2d012363a8683679a8ab45e7e.png
image.png

如果我改变imageview的大小(变小),可以看到保存到本地的图片的大小也会不同。这是因为改变imageview的大小会改变获取到的bitmap的大小不同,导致存储到本地的图片也不同,所以要慎用从imageview中获取Bitmap

四.图片压缩

先看看压缩的基本方法bitmap.compress()
这个方法有3个参数:
(1)Bitmap.CompressFormat format 表示压缩格式,一般选jpeg
(2)int quality 表示一个压缩率,100表示不压缩
(3)OutputStream stream 流

我觉得主要是研究三点,压缩前后文件大小的变化、压缩前后Bitmap的大小变化和图片的质量

1.改变压缩率

之前写photoBmp.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
改成photoBmp.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);压缩一半看看。

(1)先对Bitmap的大小


img_b5eef5d9adb3b18082e3cc9f6ad0417b.png
image.png

从图中可以看出压缩前后Bitmap的大小不变。

(2)再对比文件的大小。


img_7a7553ae1bb4c2c0283e8e9ce61eba84.png
image.png

之前没压缩是100kb,这里显然是压缩后文件变小了。

(3)再看看展示的效果

为了能更好的显示效果,我把压缩率调得更低
10的情况


img_04c1a0eb6bc320bd0ba6b52d160f86e4.png
image.png

这样也还不是很明显,0的情况

img_eb6ab9d12c68c74e4e26beb751a4e38c.png
ZOPRTUCE@%%WYY6D%3(JD2E.png

这样就很容易看出图片炸了。说明改变压缩率来压缩图片,会改变图片的清晰度。

(4)总结

经过一系列的对比我们得出这样的结果:
改变压缩率,位图的大小不变,图片的质量变差(也可以说失真),文件的大小变小,图片的宽高不变。

那么这里我们就应该有一个疑问,不对,应该说必须要弄懂一个重要的问题:为毛图片质量变差了,bitmap的大小却不变,我们都知道bitmap = 单位长 X 宽 X 单位像素占用的字节数,那么这些都不变的情况下为什么图片质量变了

从图中第一眼可以看出,变得是什么?是颜色,有没有一种彩图变黑白的感觉。这种压缩后会损坏图片质量的压缩叫质量压缩也是一种有损压缩。


img_9cf75ff2bf41db9c415eee33b8d211e1.png
image.png

看看百度百科的定义,我觉得他默认是进行色度抽样的操作。如果你详细了解这个过程,你需要深度去了解jpg格式、色彩等内容,我这方面不是很了解,但是我敢确定,jpg文件的大小会和色彩有关。随便一说,这种有损压缩一般是没办法还原的。

2.采样率

在android中有一种压缩方式成为采样率压缩,其实这种压缩方法就是一种改变图片的宽高,按照我们上面的说法,改变尺寸,那bitmap也会变小,文件也会变小,下图中的第三张图就是对第二张图进行采样率压缩的结果,会发现它的宽高都缩小了一半,那Bitmap的大小就变成了之前的1/4.

img_6795e85b3832137e1b9c5418bba85679.png
image.png
 BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;

        Bitmap bm = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/newpic2.jpg"
                , options);
        Log.v("mmp","位图采样率压缩后大小:"+(bm.getRowBytes() * bm.getHeight()));
        ivBeilv.setImageBitmap(bm);

但是不同于普通的缩小尺寸的方法,因为普通的缩小尺寸的方法是,你先从文件中把图片转成bitmap到内存,再缩小。这样的话bitmap就是先会占很大内存,而BitmapFactory.decodeFile是先获取尺寸,然后再读取图片的像素,最后展示,这样获取的bitmap就直接是原来的1/4.

注意:关于像素,是个细节,直接这样定义可能不是很清晰,之后我会讲讲像素和展示图片的关系,这样就能更清楚明白采样率的原理了。

3.质量压缩 与 尺寸压缩

上面我只是用口头语言稍微解释了一些可以实现压缩的方法,但是用android的时候我们会经常听说不是用质量压缩就是用尺寸压缩,那么这两种压缩方式分别是怎样的。

其实第一种使用compress方法的压缩就是质量压缩,而尺寸压缩的方法很多,主要以改变图片尺寸为目的的压缩我觉得都应该被称为尺寸压缩。

不多扯这些无关紧要的东西,很多地方会说这样的一句话,质量压缩是通过降低图片的质量,尺寸压缩是通过降低图片的像素。

(1)尺寸压缩

就是通过改变尺寸来改变像素,像素变了,文件的大小自然会变。我们都知道,手机的分辨率是固定的,假如图像的分辨率是800 * 600,它长宽变短一半后就变成了400 * 300,那么800 * 600个点,怎么放到只有400 * 300个点歌点的位置呢?那就是来相邻的4个像素点,通过某种算法变成一个像素点,那就造成了像素的丢失,所以文件就变小了。

扩展一下,那如果是变大呢?图片变小是通过某种算法把某块区域的像素点变成一个像素点(比如缩小4倍的话相邻4块红色的像素点会变成1块红色的像素点),那图片尺寸变大就是根据某种算法去补充某些像素点,这些像素点的颜色会根据相邻两个像素点的颜色去计算。

那么这种方法就是有损的,为什么?你缩小时丢失了某些像素你能拿回来吗,不能。你变大时添加的颜色像素是固定的吗,不是。不信你可以把一张图片的高宽缩小个几十倍,然后拿到新图片后再把它的高宽扩大相同的倍数,看看效果。这是一个我缩小10倍再放大10倍的效果。

img_276f2754d7d54daf249802b960af95d8.png
image.png

注意,我说这个还要提醒一件事,那就是如果你用固定的imageview来放图片,然后每次对图片的操作都是从Imageview中获取的话,不要再次改变图片的大小了,不然会这样,打码的特效。

(2) 质量压缩

为什么要后面说质量压缩,因为质量压缩的原理我不懂,我找资料找不到,我只能说说我自己对质量压缩的看法。

这里的解释不一定正确,如果有大屌知道原理请指教

这里的解释不一定正确,如果有大屌知道原理请指教

这里的解释不一定正确,如果有大屌知道原理请指教

重要的事说三遍。我觉得质量压缩的原理主要出在颜色上,看看我之前质量压缩的图。

img_0df35594853549c67d9b23dca7650790.png
image.png

可以看出压缩后的颜色比没压缩的时候少了很多,我有两种猜想。

第一种猜想,压缩存储时改变了图片的格式,使每个像素点的字节变少,比如把ARGB_8888变成ARGB_4444,然后拿出来的时候再变回ARGB_8888格式。

第二种猜想,我们都知道压缩图片最简单的原理是,假如你图片的分辨率是1920 * 1080,第一行的颜色相同,如果一个一个像素点存储的话,第一行就占1920 * 色彩模式的每个像素点的字节 这么多的字节,而通过某种算法,他们都是一样的,所以第一行只占 一个素点的字节 。那质量压缩就是把一连块颜色相差不大的像素点变成同一种颜色,这样就能减少了存储空间,但是展示图片时颜色就变了。

当然这只是我的猜测,但是质量压缩我敢保证100%对颜色进行了某些操作。

4.总结

感觉讲的内容也挺多了,一些操作算法,无损压缩,封装之类的操作我打算等整理好之后再发。

其实看了这篇你至少可以知道简单的压缩要怎么去做,压缩并不难,难的是你要怎么去压缩不仅能让文件变小,而且还能保持图像的清晰度。我这也展示了滥用有损压缩的后果,但是某种情况下我觉得是可以直接使用有损压缩的,比如保存头像,就可以直接用尺寸压缩,不管你选的头像图片有多大,反正最后我只要你的缩略图,而且由大变小从视觉上你也看不出什么。

最后希望有大佬能告诉我质量压缩到底是做了什么操作才使文件变小的,谢谢。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Android 图片压缩
1、为什么Android的图片质量会比iPhone的差?
759 0
android 图片压缩
引用:http://104zz.iteye.com/blog/1694762 第一:我们先看下质量压缩方法:     Java代码    private Bitmap compressImage(Bitmap image) {              Byte...
567 0
Android异步加载图片详解之方式二(3)
main.xml如下:   listviewitem.xml如下:   ...
746 0
Android异步加载图片详解之方式一(2)
FileCache.java如下: package cn.loadImages; import java.io.File; import android.
736 0
Android异步加载图片详解之方式一(1)
MainActivity.java如下: package cn.ideallistview; import java.util.ArrayList; import android.
869 0
Android异步加载图片详解之方式一(3)
Utils.java如下: package cn.loadImages; import java.io.InputStream; import java.
785 0
Android异步加载图片详解之方式一(4)
main.xml如下: listviewitem.xml如下:  
727 0
Android异步加载图片详解之方式二(1)
MainActivity.java如下: package com.cn.perfectlistview; import java.util.ArrayList; import android.
808 0
Android异步加载图片详解之方式二(2)
FileCache.java如下: package com.cn.loadImages; import java.io.File; import java.
773 0
+关注
kylinarm
搬砖于造轮
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Android组件化实现
立即下载
《深入探索Android热修复技术原理》
立即下载
Android插件化:从入门到放弃
立即下载