浅析Android平台图像压缩方案

在介绍Android平台的压缩方案之前,先了解一下Bitmap的几个主要概念。

像素密度

像素密度指的是每英寸像素数目,在Bitmap里用mDensity/mTargetDensity,mDensity默认是设备屏幕的像素密度,mTargetDensity是图片的目标像素密度,在加载图片时就是 drawable 目录的像素密度。

色彩模式->色彩模式是数字世界中表示颜色的一种算法,在Bitmap里用Config来表示。

  • ARGB_8888:每个像素占四个字节,A、R、G、B 分量各占8位,是 Android 的默认设置;
  • RGB_565:每个像素占两个字节,R分量占5位,G分量占6位,B分量占5位;
  • ARGB_4444:每个像素占两个字节,A、R、G、B分量各占4位,成像效果比较差;
  • Alpha_8: 只保存透明度,共8位,1字节;

Bitmap的计算方式

memory=scaledWidth*scaledHeight*每个像素所占字节数

其中
scaledWidth : widthtargetDensity/density+0.5
scaledHeight: height
targetDensity/density+0.5

  • scaledWidth表示水平方向的像素值,
  • width表示屏幕宽度,
  • targetDensity表示手机的像素密度,这个值一般跟手机相关,
  • density表示decodingBitmap 的 density,这个值一般跟图片放置的目录有关(hdpi/xxhdpi)

scaledHeight同理

每个像素所占字节数:这个值跟色彩模式相关,默认 ARGB_8888 则是4个字节,

在Bitmap种有两个获取内存占用大小的方法

  • getByteCount():API12 加入,代表存储 Bitmap 的像素需要的最少内存。
  • getAllocationByteCount():API19 加入,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。

两者的区别:

在不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。在通过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大小,getAllocationByteCount() 表示被复用 Bitmap真实占用的内存大小(即 mBuffer 的长度)。

图片压缩方式

质量压缩

质量压缩的关键在于Bitmap.compress()函数,该函数不会改变图像的大小,但是可以降低图像的质量,从而降低存储大小,进而达到压缩的目的。

这里提到的图像的质量主要指的是图片的色彩空间

一般图像的色彩空间为RGB,主要通过RGB三原色通道来描述图片,其中又有ARGB格式,比起RGB多了一个透明度的通道。

Android下的质量压缩主要通过下面这个函数来实现的。

bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);

三个参数

  • CompressFormat format:压缩格式,它有JPEG、PNG、WEBP三种选择,JPEG是有损压缩,PNG是无损压缩,WEBP是Google推出的图像格式.
  • int quality:0~100可选,数值越大,质量越高,图像越大。
  • OutputStream stream:压缩后图像的输出流。

其中PNG是无损格式的,压缩效果不太理想,而WEBP会存在兼容性的问题。出于兼容性和效果来看,一般会选择JPEG作为压碎格式。

实例代码

// R.drawable.thumb 为 png 图片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
try {
    //保存压缩图片到本地
    File file = new File(Environment.getExternalStorageDirectory(), "aaa.jpg");
    if (!file.exists()) {
        file.createNewFile();
    }
    FileOutputStream fs = new FileOutputStream(file);
    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fs);
    Log.i(TAG, "onCreate: file.length " + file.length());
    fs.flush();
    fs.close();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
//查看压缩之后的 Bitmap 大小
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);
byte[] bytes = outputStream.toByteArray();
Bitmap compress = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Log.i(TAG, "onCreate: bitmap.size = " + bitmap.getByteCount() + "   compress.size = " + compress.getByteCount());

我们再来看看quality参数被设置为50前后,两张图片的对比.
压缩前的图片

image

压缩后的图片

image

从上述两图可以明显图片质量的差别,另外再通过log打印查看会压缩前后图片的所占用的大小是一样的。

bitmap.size = compress.size

Q:这里可能有人就会有疑惑,为什么压缩过后,两张图片的大小还会是一样的呢?

A:因为图片在内存中的存储方式和文件中的存储方式是不一样的。图片压缩只会影响文件的大小,在这个例子中,压缩过后存到磁盘的文件大小会比压缩之前的文件大小减小很多。

内存中所占的大小没有变化是因为bitmap没有变化的原因。

文章最开始提到Bitmap的计算方式

memory=scaledWidth*scaledHeight*每个像素所占字节数

因为是压缩的质量,所有宽高都不变,而每个像素所占的字节数跟色彩空间有关,默认是ARGB_8888.宽高不变,色彩空间不重新设置,那么bitmap所占的大小就不会发生改变。

说道这里可能又会有个新疑问

Q:bitmap占用的大小不变,那为什么图片质量下降了呢?这是因为图片被压缩过了啊!

A:首先要知道JPEG格式是有损压缩的,JPEG格式的图片是不支持透明色彩的,这也是JPEG的大小会比PNG小很大,图片质量会比PNG差的原因。
在经过了bitmap.compress()这个流程时,JPEG会舍去透明属性.这样存放到磁盘时的文件大小就减小了.然后这个时候再通过BitmapFactory.decodeByteArray()把图片加载回来时,加载的是舍去了透明通道的图片,按理说应该采用 RGB_565或者RGB_888这样的色彩空间加载,但是你没有另外设置这个参数的话,加载的色彩格式会是默认ARGB_8888.图片都没有透明的色彩空间了,你再给它分配内存就只是浪费内存而已。

这也是为什么压缩前后,bitmap所占的大小相同,图片质量却有所差距的原因。

补充一个有趣的事件,在早期的Android平台下,对一张图片进行多次质量压缩,会得到一张变绿的图片。详情链接

补充一些Android下各格式图片的存储方式

WebP

Webp图片格式是Google推出的一个支持alpha通道的有损压缩格式,据Google官方表明,同质量情况下Webp图像要比JPEG、PNG图像小25%~45%左右,在支持上Android4.0+版本提供原生支持,使用libwebp库进行编解码。

GIF

GIF图像最广泛的应用是用于显示动画图像,它具备文件小且支持alpha通道的优点,不过它是由8位进行表示每个像素的色彩,仅支持256色,所以在对色彩要求比较高的场合不太适合。

Stream

图片的存储形式从File转到内存中时,图片内容以字节方式存储在Stream中,此时所占的内存大小为File文件大小。

Bitmap

在Android中,任何图片资源的显示对象都是通过bitmap来显示的,除了xml资源则是通过Canvas来绘制的,所以,对于某些纯色或者规则类的图像,可以通过xml进行描述或Canvas来绘制,这样所占用的内存比通过bitmap来显示将少几个等级。

Bitmap与Drawable的联系

关于Bitmap和Drawable的关系,可以看官方的解释,Drawable是一个抽象的概念,来描述某些具备可绘制的的对象,它是一个抽象类,而Bitmap是一个最简单的Drawable实体对象,Bitmap并不继承于Drawable,它们之间建立关联最终是通过BitmapDrawable对象,该对象会把具体的Bitmap实例对象渲染到Canvas上。Drawable更注重描述的是某绘制的行为,而Bitmap则是注重存储着图像的像素信息。

Bitmap存储空间

随着版本的变化以及存储空间的变化,Bitmap的存储空间主要有三个地方

Native Memory
Android2.3以下版本,bitmap像素数据存储在native内存中,释放内存需主动调用recycle()方法

Dalvik Heap
Android3.0+版本,在Android2.3版本引入了并发的垃圾回收器后,在3.0以后的版本bitmap的像素数据则存储在虚拟机堆中,不需要主动调用recycle()来回收内存,gc会主动回收

Ashmem
匿名共享内存空间,说到这个,就会联想起大名鼎鼎的Fresco图片库,它巧妙的利用了这一空间来进行Bitmap对象的存储,对于Ashmem空间,首先想到的是与App进程空间是隔离且互不影响的,这点在Android4.4以下版本是这样的,在Android4.4+后版本,Ashmem空间将会包含在App所占用的内存空间中。看Fresco源码也可以看出,对于4.4+版本,对于Bitmap的解码使用了另外的解码器。在Android4.4以下版本如何使用Ashmem进行bitmap的存储呢?通过DecodeOptions:

options.inPurgeable = true;
options.inInputShareable = true;

以及通过MemoryFile可将图片的字节数据存储在Ashmem中。

尺寸压缩

尺寸压缩本质上就是一个重新采样的过程,放大图像称为上采样,缩小图像称为下采样,Android提供了两种图像采样方法,邻近采样和双线性采样。

邻近采样

邻近采样采用邻近点插值算法,用一个像素点代替邻近的像素点,

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Bitmap compress = BitmapFactory.decodeFile("/sdcard/test.png", options);

其中options.inSampleSize的值代表着压缩后一个像素点代替原来的几个像素点,比如options.inSampleSize=2,一个像素点会代替原来的2个像素点,注意这里的2个像素点仅仅指水平方向或者竖直方向上的。即原来2x2的像素,压缩后仅使用一个像素点来代替。

网上找了张图

压缩前的图片

image

压缩后的图片

image

压缩前红绿相间的图片,经过压缩后,完全变成了绿色.这时因为邻近点插值算法直接选择其中一个像素作为生成像素,另外一个像素直接抛弃,这样才会造成图片变成纯绿色的情况。

考虑到邻近采样的方法有些暴力,Android平台提供了另一种尺寸压缩方案

双线性采样

双线性采样采用双线性插值算法,相比邻近采样简单粗暴的选择一个像素点代替其他像素点,双线性采样参考源像素相应位置周围2x2个点的值,根据相对位置取对应的权重,经过计算得到目标图像。

使用实例

Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);

或者

Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);

压缩效果

压缩前

image

压缩后

image

可以看出压缩后的图片不会像邻近采样那般只有纯粹的一种颜色,而是参考了像素源周围2x2个点的像素,并取其权重得到目标图像。

双线性采样相比邻近采样而言,图片的保真度会高些,但压缩的速率不及前者,因为前者不需要计算直接选择了其中一个像素作为生成像素。

双立方/双三次采样 (Android原生不支持)

双立方/双三次采样使用的是双立方/双三次插值算法。双立方/双三次插值算法参考了源像素某点周围 4x4 个像素。

双立方/双三次插值算法经常用于图像或者视频的缩放,它能比双线性内插值算法保留更好的细节质量。

双立方/双三次插值算法在平时的软件中是很常用的一种图片处理算法,但是这个算法有一个缺点就是计算量会相对比较大,是前三种算法中计算量最大的,软件 photoshop 中的图片缩放功能使用的就是这个算法。

Lanczos 采样 (原生不支持)###

Lanczos 采样和 Lanczos 过滤是 Lanczos 算法的两种常见应用,它可以用作低通滤波器或者用于平滑地在采样之间插入数字信号,Lanczos 采样一般用来增加数字信号的采样率,或者间隔采样来降低采样率。

采样效果 从低到高依次

邻近采样--双线性采样--双立方/双三次采样--Lanczos 采样

Android平台图像压缩方案
QQ音乐团队分享:Android中的图片压缩技术详解
也谈图片压缩
为什么图片反复压缩后会普遍会变绿而不是其他颜色
Android之优雅地加载大图片
内存占用/GPU渲染性能优化手记

另外

个人的github
闲暇之余写的故事

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

推荐阅读更多精彩内容

  • 摘要:对android 上图片压缩,其实总结起来基本可以分为两类压缩:尺寸压缩和质量压缩, 尺寸压缩其实也可以理解...
    男爵是只猫丶阅读 8,779评论 2 14
  • 如以上DEMO截图所示效果,我们对于这种类似的功能肯定不算陌生,因为这可以说是实际开发中一类非常常见的功能需求了。...
    Machivellia阅读 2,038评论 1 13
  • 之前有个萌新在技术群里问图片压缩,然后我竟然还要查资料才回答他,没办法,谁让我也是个萌新,所以打算写一篇文章来复习...
    键盘上的麒麟臂阅读 5,612评论 11 18
  • 7.1 压缩图片 一、基础知识 1、图片的格式 jpg:最常见的图片格式。色彩还原度比较好,可以支持适当压缩后保持...
    AndroidMaster阅读 2,503评论 0 13
  • 三月潮湿的江南 绿苔已爬上了门楣 窄窄的门厅,光滑的石阶 再未曾响起匆匆的足音 这里的事物都老了 朱漆的小门也已斑...
    代暮秋阅读 271评论 0 1