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

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

一.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()计算出这张图片的大小为

image.png

然后我们再看看手机内部他的大小,比较一下。
image.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方法


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有什么不同。

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();
        }
    }

image.png

image.png

image.png

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

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

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的大小


image.png

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

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


image.png

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

(3)再看看展示的效果

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


image.png

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

ZOPRTUCE@%%WYY6D%3(JD2E.png

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

(4)总结

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

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

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


image.png

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

2.采样率

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

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倍的效果。

image.png

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

(2) 质量压缩

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

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

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

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

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

image.png

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

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

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

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

4.总结

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容