之前有个萌新在技术群里问图片压缩,然后我竟然还要查资料才回答他,没办法,谁让我也是个萌新,所以打算写一篇文章来复习一下图片相关的知识点。
一.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
如果没算错的话这张图片在内存中占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大小查看结果
如果我改变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.总结
感觉讲的内容也挺多了,一些操作算法,无损压缩,封装之类的操作我打算等整理好之后再发。
其实看了这篇你至少可以知道简单的压缩要怎么去做,压缩并不难,难的是你要怎么去压缩不仅能让文件变小,而且还能保持图像的清晰度。我这也展示了滥用有损压缩的后果,但是某种情况下我觉得是可以直接使用有损压缩的,比如保存头像,就可以直接用尺寸压缩,不管你选的头像图片有多大,反正最后我只要你的缩略图,而且由大变小从视觉上你也看不出什么。
网友评论
1.采样率压缩又叫邻近采样压缩,inSampleSzie为2的倍数,这种方式比较粗暴,直接选择两个相邻的颜色像素其中的一个像素作为生成像素,另一个像素直接抛弃,这对于某些只有两种颜色的图片会导致压缩完后就剩下一种颜色,比方说红绿相间的图片压缩完后就变成了绿色了.
2.通过Matrix压缩图片,这种方法比较灵活,既可以截取图片的一部分也可以压缩图片大小.Matrix进行缩放处理之后的图片不是像采样率压缩一样纯粹的一种颜色,而是两种颜色的混合.这也叫做双线性采样,它使用的是双线性內插值算法,这个算法不像邻近点插值算法一样,直接粗暴的选择一个像素,而是参考了源像素相应位置周围 2x2 个点的值,根据相对位置取对应的权重,经过计算之后得到目标图像.
对比采样率压缩,Matrix压缩具有抗锯齿功能,而且不会让图片出现严重失真.比方说红绿相间的图片压缩完后就不会只剩下一种颜色.
3.质量压缩,这种是在保持像素的前提下,改变图片的位深,色度及透明度等属性(我也只知道这么多)去改变图片文件的大小.需要提一下的是如果使用CompressFormat.PNG的话,那么quality 这个参数就会被忽略,文件大小也就不会发生改变,因为PNG是无损压缩.