1. 图片压缩格式
1.1. 常见的图片格式
格式 | 质量 | 透明 | 压缩方式 |
---|---|---|---|
png | 较高 | 支持 | 图片压缩算法。把相似的颜色压缩在一起 |
jpg/jpeg | 良好 | 不支持 | 图形压缩算法。把相似的颜色合并在一起,用同一种颜色代替 |
bmp | 高 | 不支持 | 24位位图: 每个像素用24位二进制表示,占3个字节256色位图:每个像素用8位二进制表示,占1个字节16色位图: 每个像素有16种颜色选择单色位图: 每个像素有1种颜色选择 |
1.2. WebP格式
Google在2010年提出。更小的体积下提供肉眼无差别的质量。同时具备了无损和有损的压缩模式、Alpha透明以及动画的特性。在Android 4.0中支持有损的WebP图像,在Android 4.3及更高支持无损和透明的WebP图像。
2. Android内存中的图片
图片的内存大小是指图片完全加载到内存所占用的大小,并非所占用的磁盘大小。
2.1. 位图配置Bitmap.Config
位图配置描述了像素的存储方式,影响质量(颜色深度)、透明度和占用内存的大小。
类型 | 描述 | 组合方式 | 单像素位宽 |
---|---|---|---|
ALPHA_8 | 无色彩,有透明度 | A-8 | 8位 |
ARGB_4444 | 质量差。API-29中弃用 | A-4 R-4 G-4 B-4 | 16位 |
ARGB_8888 | 有色彩,有透明度,提供最佳质量。应尽可能使用它 | A-8 R-8 G-8 B-8 | 32位 |
HARDWARE | 特殊配置,当位图仅存储在图形内存中时。API-26中添加 | ||
RGBA_F16 | 有色彩,有透明度,适合用于宽域和HDR内容。API-26中添加 | A-16 R-16 G-16 B-16 | 64位 |
RGB_565 | 低颜色保真度,无透明度 | R-5 G-6 B-5 | 16位 |
2.2. 计算占用内存大小
举例:100 × 200 的图片
2.2.1. 理论值
占用内存字节 = 单像素字节 × 图片宽度 × 图片高度
例如:ARGB_8888
配置下占用大小为 4B × 100 × 200 = 80000B = 78.125KB
2.2.2. 实际值
【res目录图片】
系统加载res目录(drawable、mipmap)的资源图片时,先根据存放的不同目录做分辨率转换,再使用转换后的新图宽高运用理论公式计算。
新图高度 = 原图高度 × (设备dpi ÷ 目录dpi)
新图宽度 = 原图宽度 × (设备dpi ÷ 目录dpi)
占用内存字节 = 单像素字节 × 图片宽度 × 图片高度 × (设备dpi ÷ 目录dpi)²
res目录后缀 | ldpi | mdpi | hdpi | xhdpi | xxhdpi |
---|---|---|---|---|---|
dpi值 | 120 | 160 | 240 | 320 | 480 |
例如:ARGB_8888
配置,设备 dpi = 480
,xhdpi
目录下,占用大小为 4B × 100 × 200 × (480 ÷ 320)² = 180000B = 175.78125KB
- 相同图片,相同 dpi 的设备,不同目录,则图片占用的内存空间大小不一样
- 相同图片,不同 dpi 的设备,相同目录,则图片占用的内存空间大小不一样
【非res目录图片】
系统加载非 res 目录(磁盘、SD卡、asserts目录、网络下载)的资源图片时,不会做分辨率转换,而是直接使用理论公式计算占用内存大小。
- 不同的图片加载框架,相同图片可能会占用不同的内存空间大小
3. 图片压缩
减小图片所占用的磁盘/内存空间的大小。
以读取SD卡本地图片并重新保存到SD卡为例。
3.1. 质量压缩
通过改变图片的位深和透明度(即质量)来减小图片占用的磁盘空间大小,不会改变图片的像素,所以不适合作为缩略图,一般用于图片上传。
File sdFile = Environment.getExternalStorageDirectory();
File originFile = new File(sdFile, "originImg.jpg");
// 从本地SD卡读取图片文件,也可以使用BitmapFactory.decode系列方法
Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 图片格式:Bitmap.CompressFormat 中枚举的3中类型
// 质量:0-100的整数。PNG类型图片因为是无损的,所以此参数无效
originBitmap.compress(【图片格式】, 【质量】, bos);
FileOutputStream fos = null;
try {
// 压缩后的文件输出流
fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
fos.write(bos.toByteArray());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 两个流执行flush()和close()
}
3.2. 采样率压缩
通过设置 BitmapFactory.Options.inSampleSize
,来减小图片的分辨率(即图像的dpi值,非像素值),进而减小图片所占用的磁盘空间和内存大小。
File sdFile = Environment.getExternalStorageDirectory();
File originFile = new File(sdFile, "originImg.jpg");
// 从本地SD卡读取图片文件,也可以使用BitmapFactory.decode系列方法
final BitmapFactory.Options options = new BitmapFactory.Options();
// 设置此参数是仅仅读取图片的宽高到options中,不会将整张图片读到内存中,防止OOM
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);
final int originWidth = options.outWidth; // 图片的原始宽度
final int originHeight = options.outHeight; // 图片的原始高度
options.inJustDecodeBounds = false;
options.inSampleSize = 【缩放值】; // 缩放值是重点,可以根据图片的原始宽高而定
Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 图片格式:Bitmap.CompressFormat 中枚举的3中类型
// 质量:0-100的整数。PNG类型图片因为是无损的,所以此参数无效
originBitmap.compress(【图片格式】, 【质量】, bos);
try {
// 压缩后的文件输出流
fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
fos.write(bos.toByteArray());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 两个流执行flush()和close()
}
缩放值 inSampleSize
会导致压缩的图片的宽高都为原图的 1 / inSampleSize
。
inSampleSize ≤ 1
,则不做缩放。inSampleSize
只能为2的幂,否则会减小为最接近的2的幂。
3.3. 尺寸压缩
通过减少图片的像素来降低图片的磁盘空间大小和内存大小,一般用于生成缩略图。
File sdFile = Environment.getExternalStorageDirectory();
File originFile = new File(sdFile, "originImg.jpg");
// 从本地SD卡读取图片文件,也可以使用BitmapFactory.decode系列方法
Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath());
int radio = 8; // 缩放比,宽高像素值变为原来的 1/radio
final int resultWidth = originBitmap.getWidth() / radio;
final int resultHeight = originBitmap.getHeight() / radio;
// 创建输出图片,可以改变位图配置以进一步降低占用空间
Bitmap resultBitmap = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(resultBitmap);
RectF rectF = new RectF(0, 0, resultWidth, resultHeight);
//将原图画在缩放之后的矩形上
canvas.drawBitmap(originBitmap, null, rectF, null);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 图片格式:Bitmap.CompressFormat 中枚举的3中类型
// 质量:0-100的整数。PNG类型图片因为是无损的,所以此参数无效
originBitmap.compress(【图片格式】, 【质量】, bos);
FileOutputStream fos = null;
try {
// 压缩后的文件输出流
fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
fos.write(bos.toByteArray());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 两个流执行flush()和close()
}
3.4. 图片压缩总结
- WebP 格式可以在保持清晰度的情况下减小图片的磁盘大小,是一种比较优秀的,Google 推荐。
- 质量压缩可以减小图片占用的磁盘空间,不会减小在内存中的大小。
- 采样率压缩通过改变分辨率来减小图片所占用的磁盘空间和内存空间大小,但是采样率只能设置为 2 的幂,可能图片的最优比例在中间。
- 尺寸压缩也通过改变分辨率来减小图片所占用的磁盘空间和内存空间大小,缩放的尺寸没有什么限制。
- jni 调用 jpeg 库来弥补安卓系统 skia 框架的不足,也是比较优秀的解决方式。
- 实际问题中,往往多种方式混合使用以达到最佳效果。
3.5. 实际效果对比
【原图】272k,1024*797。
【尺寸压缩】64k,256*200。图片细节较锐利。
【采样率压缩】52k,256*200。图片细节较模糊。