Android bitmap(二) 常见图片格式JPG PNG

参考
PNG、EPS、bmp、jpg等几种图片格式有什么区别
GIF/PNG/JPG和WEBP/base64/apng图片优点和缺点整理
移动端图片格式调研
Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存

一、色深,位深以及内存占用和图片文件大小的计算

参考
百度百科 颜色深度
百度百科 位深度
位深度、色深的区别以及图片大小的计算

1.颜色深度
颜色深度是指每个像素可以显示的颜色数,在计算机中,通常采用颜色深度这一概念来说明其处理色彩的能力。颜色深度基本上用量化数bit表示。bit数越高,每个像素可显示出的颜色数目就越多,色彩就越丰富,图像就越真实,文件也越大。

如果一个图片支持256种颜色(如GIF格式),那么就需要256个不同的值来表示不同的颜色,也就是从0到255。用二进制表示就是从00000000到11111111,总共需要8位二进制数。所以颜色深度是8。
如果是BMP格式,则最多可以支持红、绿、蓝各256种,总共24位。所以颜色深度是24。
还有PNG格式,这种格式除了支持24位的颜色外,还支持alpha通道(就是控制透明度用的),总共是32位。

2.位深


windows图片文件属性

计算机之所以能够显示颜色,是采用了一种称作“位”( bit ) 的记数单位来记录所表示颜色的数据。当这些数据按照一定的编排方式被记录在计算机中,就构成了一个数字图像的计算机文件。

3.区别
位深度指的是存储每个像素所用的位数,主要用于实际文件的存储
色深指的是每一个像素点用多少bit存储颜色,属于图片自身的一种属性

举个例子:
100像素*100像素的图片, 使用ARGB_8888,所以色深32位,保存时选择位深为24位
在内存中所占大小为:100 * 100 * (32 / 8)Byte
文件所占大小为 100 * 100 * ( 24/ 8 ) * 压缩效率 Byte

二、有损压缩和无损压缩

1.有损压缩
有损压缩可以减少图像在内存和磁盘中占用的空间,在屏幕上观看图像时,不会发现它对图像的外观产生太大的不利影响。因为人的眼睛对光线比较敏感,光线对景物的作用比颜色的作用更为重要,这就是有损压缩技术的基本依据。有损压缩的特点是保持颜色的逐渐变化,删除图像中颜色的突然变化。生物学中的大量实验证明,人类大脑会利用与附近最接近的颜色来填补所丢失的颜色。例如,对于蓝色天空背景上的一朵白云,有损压缩的方法就是删除图像中景物边缘的某些颜色部分。当在屏幕上看这幅图时,大脑会利用在景物上看到的颜色填补所丢失的颜色部分。利用有损压缩技术,某些数据被有意地删除了,而被取消的数据也不再恢复。
无可否认,利用有损压缩技术可以大大地压缩文件的数据,但是会影响图像质量。如果使用了有损压缩的图像仅在屏幕上显示,可能对图像质量影响不太大,至少对于人类眼睛的识别程度来说区别不大。可是,如果要把一幅经过有损压缩技术处理的图像用高分辨率打印机打印出来,那么图像质量就会有明显的受损痕迹。
2.无损压缩
无损压缩的基本原理是相同的颜色信息只需保存一次。压缩图像的软件首先会确定图像中哪些区域是相同的,哪些是不同的。包括了重复数据的图像(如蓝天)就可以被压缩,只有蓝天的起始点和终结点需要被记录下来。但是蓝色可能还会有不同的深浅,天空有时也可能被树木、山峰或其他的对象掩盖,这些就需要另外记录。
从本质上看,无损压缩的方法可以删除一些重复数据,大大减少要在磁盘上保存的图像尺寸。但是,无损压缩的方法并不能减少图像的内存占用量,这是因为,当从磁盘上读取图像时,软件又会把丢失的像素用适当的颜色信息填充进来。如果要减少图像占用内存的容量,就必须使用有损压缩方法。
无损压缩方法的优点是能够比较好地保存图像的质量,但是相对来说这种方法的压缩率比较低。但是,如果需要把图像用高分辨率的打印机打印出来,最好还是使用无损压缩。几乎所有的图像文件都采用各自简化的格式名作为文件扩展名。从扩展名就可知道这幅图像是按什么格式存储的,应该用什么样的软件去读/写等等。

三、GIF

GIF(Graphics Interchange Format)的原义是“图像互换格式”,是CompuServe公司在1987年开发的图像文件格式。GIF文件的数据,是一种基于LZW算法的连续色调的无损压缩格式。其压缩率一般在50%左右。
优点
  1. 优秀的压缩算法使其在一定程度上保证图像质量的同时将体积变得很小。
  2. 可插入多帧,从而实现动画效果。
  3. 可设置透明色以产生对象浮现于背景之上的效果。
缺点
  由于采用了8位压缩,最多只能处理256种颜色(2的8次方),故不宜应用于真彩图像。

四、PNG

便携式网络图片(Portable Network Graphics),简称PNG,是一种无损数据压缩位图图形文件格式。PNG 诞生在1995年,比JPEG晚几年。它本身的设计目的是替代GIF格式,所以它与GIF 有更多相似的地方。PNG格式是无损数据压缩的,PNG格式有8位、24位、32位三种形式,其中8位PNG支持两种不同 的透明形式(索引透明和alpha透明),24位PNG不支持透明,32位 PNG 在24位基础上增加了8位透明通道(32-24===8),因此可展现256级透明程度。
  PNG这种类型的图片就是为了取代GIF图片而生的, 除了GIF不支持动画的优势, 能用PNG的地方就用PNG, 原因是压缩比高,色彩好;相对于 JPEG 和 GIF 来说,它最大的优势在于支持完整的透明通道。
优点
  支持256色调色板技术以产生小体积文件
  最高支持48位真彩色图像以及16位灰度图像。
  支持Alpha通道的半透明特性。
  支持图像亮度的gamma校正信息。
  支持存储附加文本信息,以保留图像名称、作者、版权、创作时间、注释等信息。
  使用无损压缩。
  渐近显示和流式读写,适合在网络传输中快速显示预览效果后再展示全貌。
  使用CRC循环冗余编码防止文件出错。
  最新的PNG标准允许在一个文件内存储多幅图像。
缺点
  但也有一些软件不能使用适合的预测,而造成过分臃肿的PNG文件。

五、JPG/JPEG

JPEG是Joint Photographic Experts Group(联合图像专家组)的缩写,文件后辍名为“.jpg”或“.jpeg”,是最常用的图像文件格式,它诞生于 1992 年,由一个软件开发联合会组织制定,是一种有损压缩格式,能够将图像压缩在很小的储存空间,图像中重复或不重要的资料会被丢失,因此容易造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量明显降低,如果追求高品质图像,不宜采用过高压缩比例。
JPG和JPEG没有区别,全名、正式扩展名是JPEG。但因DOS、Windows95等早期系统采用的8.3命名规则只支持最长3字符的扩展名,为了兼容采用了.jpg。也因历史习惯和兼容性考虑,.jpg目前更流行。
JPEG2000作为JPEG的升级版,其压缩率比JPEG高约30%左右,同时支持有损和无损压缩。JPEG2000格式有一个极其重要的特征在于它能实现渐进传输,即先传输图像的轮廓,然后逐步传输数据,不断提高图像质量,让图像由朦胧到清晰显示。此外,JPEG2000还支持所谓的“感兴趣区域”特性,可以任意指定影像上感兴趣区域的压缩质量,还可以选择指定的部分先解压缩。
JPEG2000和JPEG相比优势明显,且向下兼容,因此可取代传统的JPEG格式。
优点
  JPEG/JFIF是最普遍在万维网(World Wide Web)上被用来储存和传输照片的格式。JPEG在色调及颜色平滑变化的相片或是写实绘画(painting)上可以达到它最佳的效果。在这种情况下,它通常比完全无失真方法作得更好,仍然可以产生非常好看的影像(事实上它会比其他一般的方法像是GIF产生更高品质的影像,因为GIF对于线条绘画(drawing)和图示的图形是无失真,但针对全彩影像则需要极困难的量化)。
缺点
  它并不适合于线条绘图(drawing)和其他文字或图示(iconic)的图形,因为它的压缩方法用在这些图形的型态上,会得到不适当的结果。给个活生生的例子:一张照片在Instagram反复上传下载90次之后, 在最后jpg图片完全变样了。
注意,jpg不支持透明度。

六、WebP

WebP 是 Google 在 2010 年发布的图片格式,希望以更高的压缩比替代 JPEG。它用 VP8 视频帧内编码作为其算法基础,取得了不错的压缩效果。它支持有损和无损压缩、支持完整的透明通道、也支持多帧动画,并且没有版权问题,是一种非常理想的图片格式。借由 Google 在网络世界的影响力,WebP 在几年的时间内已经得到了广泛的应用。看看你手机里的 App:微博、微信、QQ、淘宝、网易新闻等等,每个 App 里都有 WebP 的身影。Facebook 则更进一步,用 WebP 来显示聊天界面的贴纸动画。
IE暂不支持,具体参考WebP 浏览器支持

七、移动端图片类型的支持情况

Android 的图片编码解码是由 Skia 图形库负责的,Skia 通过挂接第三方开源库实现了常见的图片格式的编解码支持。目前来说,Android 原生支持的格式只有 JPEG、PNG、GIF、BMP 和 WebP (Android 4.0 加入),在上层能直接调用的编码方式也只有 JPEG、PNG、WebP 这三种。目前来说 Android 还不支持直接的动图编解码。

八、Bitmap 在内存当中占用的大小

本部分参考Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存

每次工程里面增加一张图片的时候,我们都需要关心这货究竟要占多大的坑,占多大呢?Android API 有个方便的方法:

public final int getByteCount() {
    // int result permits bitmaps up to 46,340 x 46,340
    return getRowBytes() * getHeight();
}

通过代码跟踪,我们就不难知道,Bitmap 在内存当中占用的大小其实取决于:

  • 色彩格式,前面我们已经提到,如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节
  • 原始文件存放的资源目录(是 hdpi 还是 xxhdpi 可不能傻傻分不清楚哈)
  • 目标屏幕的密度(所以同等条件下,红米在资源方面消耗的内存肯定是要小于三星S6的)

再来看我们的例子:
一张522*686的PNG 图片,我把它放到 drawable-xxhdpi 目录下,在三星s6上加载,占用内存2547360B,其中 density 对应 xxhdpi 为480,targetDensity 对应三星s6的密度为640:
522/480 * 640 * 686/480 *640 * 4 = 2546432B
targetDensity 实际上是我们加载图片的目标 density,这个值的来源我们已经在前面给出了,就是 DisplayMetrics 的 densityDpi,如果是三星s6那么这个数值就是640。sx 和sy 实际上是约等于 scale 的,因为 scaledWidth 和 scaledHeight 是由 width 和 height 乘以 scale 得到的。我们看到 Canvas 放大了 scale 倍,然后又把读到内存的这张 bitmap 画上去,相当于把这张 bitmap 放大了 scale 倍。

九、想办法减小Bitmap 在内存当中占用的大小

本部分参考Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存

1 Jpg 和 Png
说到这里,肯定会有人会说,我们用 jpg 吧,jpg 格式的图片不应该比 png 小么?
这确实是个好问题,因为同样一张图片,jpg 确实比 png 会多少小一些(甚至很多),原因很简单,jpg 是一种有损压缩的图片存储格式,而 png 则是 无损压缩的图片存储格式,显而易见,jpg 会比 png 小,代价也是显而易见的。

可是,这说的是文件存储范畴的事情,它们只存在于文件系统,而非内存或者显存。说得简单一点儿,我有一个极品飞车的免安装硬盘版的压缩包放在我的磁盘里面,这个游戏是不能玩的,我需要先解压,才能玩——jpg 也好,png 也好就是个压缩包的概念,而我们讨论的内存占用则是从使用角度来讨论的。
所以,jpg 格式的图片与 png 格式的图片在内存当中不应该有什么不同
『啪!!!』
『谁这么缺德!!打人不打脸好么!』

肯定有人有意见,jpg 图片读到内存就是会小,还会给我拿出例子。当然,他说的不一定是错的。因为 jpg 的图片没有 alpha 通道!!所以读到内存的时候如果用 RGB565的格式存到内存,这下大小只有 ARGB8888的一半,能不小么。。。
不过,抛开 Android 这个平台不谈,从出图的角度来看的话,jpg 格式的图片大小也不一定比 png 的小,这要取决于图像信息的内容:
JPG 不适用于所含颜色很少、具有大块颜色相近的区域或亮度差异十分明显的较简单的图片。对于需要高保真的较复杂的图像,PNG 虽然能无损压缩,但图片文件较大
如果仅仅是为了 Bitmap 读到内存中的大小而考虑的话,jpg 也好 png 也好,没有什么实质的差别;二者的差别主要体现在:

  • alpha 你是否真的需要?如果需要 alpha 通道,那么没有别的选择,用 png。
  • 你的图色值丰富还是单调?就像刚才提到的,如果色值丰富,那么用jpg,如果作为按钮的背景,请用 png
  • 对安装包大小的要求是否非常严格?如果你的 app 资源很少,安装包大小问题不是很凸显,看情况选择 jpg 或者 png(不过,我想现在对资源文件没有苛求的应用会很少吧。。)
  • 目标用户的 cpu 是否强劲?jpg 的图像压缩算法比 png 耗时。这方面还是要酌情选择,前几年做了一段时间 Cocos2dx,由于资源非常多,项目组要求统一使用 png,可能就是出于这方面的考虑。

嗯,跑题了,我们其实想说的是怎么减少内存占用的。。这一小节只是想说,休想通过这个方法来减少内存占用。。。XD

2 使用 inSampleSize
有些朋友一看到这个肯定就笑了。采样嘛,我以前是学信号处理的,一看到 Sample 就抽抽。。哈哈开个玩笑,这个采样其实就跟统计学里面的采样是一样的,在保证最终效果满足要求的前提下减少样本规模,方便后续的数据采集和处理。
这个方法主要用在图片资源本身较大,或者适当地采样并不会影响视觉效果的条件下,这时候我们输出地目标可能相对较小,对图片分辨率、大小要求不是非常的严格。
<pre>
public static Bitmap getFitSampleBitmap(String file_path, int width, int height) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file_path, options);
options.inSampleSize = getFitInSampleSize(width, height, options);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(file_path, options);
}
public static int getFitInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) {
int inSampleSize = 1;
if (options.outWidth > reqWidth || options.outHeight > reqHeight) {
int widthRatio = Math.round((float) options.outWidth / (float) reqWidth);
int heightRatio = Math.round((float) options.outHeight / (float) reqHeight);
inSampleSize = Math.min(widthRatio, heightRatio);
}
return inSampleSize;
}
</pre>

3 使用矩阵
大图小用用采样,小图大用用矩阵。
还是用前面模糊图片的例子,我们不是采样了么?内存是小了,可是图的尺寸也小了啊,我要用 Canvas 绘制这张图可怎么办?当然是用矩阵了

<pre>
Matrix matrix = new Matrix();
matrix.preScale(2, 2, 0, 0);
canvas.drawBitmap(bitmap, matrix, paint);
</pre>

这样,绘制出来的图就是放大以后的效果了,不过占用的内存却仍然是我们采样出来的大小。如果我要把图片放到 ImageView 当中呢?一样可以,请看:
<pre>
Matrix matrix = new Matrix();
matrix.postScale(2, 2, 0, 0);
imageView.setImageMatrix(matrix);
imageView.setScaleType(ScaleType.MATRIX);
imageView.setImageBitmap(bitmap);
</pre>

4 合理选择Bitmap的像素格式
其实前面我们已经多次提到这个问题。ARGB8888格式的图片,每像素占用 4 Byte,而 RGB565则是 2 Byte。我们先看下有多少种格式可选:
格式 描述
ALPHA_8 只有一个alpha通道
ARGB_4444 这个从API 13开始不建议使用,因为质量太差
ARGB_8888 ARGB四个通道,每个通道8bit
RGB_565 每个像素占2Byte,其中红色占5bit,绿色占6bit,蓝色占5bit

这几个当中,
ALPHA8 没必要用,因为我们随便用个颜色就可以搞定的。
ARGB4444 虽然占用内存只有 ARGB8888 的一半,不过已经被官方嫌弃,失宠了。。『又要占省内存,又要看着爽,臣妾做不到啊T T』。
ARGB8888 是最常用的,大家应该最熟悉了。
RGB565 看到这个,我就看到了资源优化配置无处不在,这个绿色。。(不行了,突然好邪恶XD),其实如果不需要 alpha 通道,特别是资源本身为 jpg 格式的情况下,用这个格式比较理想。

5 高能:索引位图(Indexed Bitmap)

6 覆写 onDraw
其实我们一直在抱怨资源大,有时候有些场景其实不需要图片也能完成的。比如在开发中我们会经常遇到 Loading,这些 Loading 通常就是几帧图片,图片也比较简单,只需要黑白灰加 alpha 就齐了。
『排期太紧了,这些给我出一系列图吧』
『好,不过每张图都是 300*30 0的 png 哈,总共 5 张,为了适配不同的分辨率,需要出 xxhdpi 和 xxxhdpi 的两套图。。』
Orz。。。
如果是这样,你还是自定义一个 View,覆写 onDraw 自己画一下好了。。。

推荐阅读更多精彩内容