图片压缩的两种方法
android中对于图片的处理非常的常见,总体上希望图片能占用内存小,View上显示刚刚好,并且上传服务器大小符合要求且图片不模糊。要达到这些要求我们得知道一些知识
图片的格式
- 有损压缩:顾名思义有损压缩是会损伤图片质量的,在压缩过程中会保持颜色逐渐变化,删除图片中颜色突然变化部分。这样能减少图片在内存和磁盘中的内存空间,同时压缩的越厉害图片显示效果越差。并且图片后期不能恢复
- 无损压缩:无损压缩是对文件本身的压缩,和压缩文件一样,是对数据存储的优化。压缩后图片可以恢复,所以图片在内存和磁盘上并不能减少大小。
图片格式 | 是否是有损压缩 | 支持透明色 | 常用场景 |
---|---|---|---|
JPEG | 有损 | 不支持 | 适合色彩丰富的大图压缩,适合使用logo、线图 |
PNG | 无损 | 支持透明和半透明 | 安卓中常用切图都适合使用PNG |
WebP | 同时提供有损和无损 | 支持 | 无损的WebP图片比PNG小26%,有损的WebP图片比JPEG小25-34%,非常优秀,Android 4.0+默认支持WebP,Android 4.2.1+开始支持无损WebP和带alpha通道的WebP,对系统支持有一定要求 |
可以看到,其实目前来说在不适配较低机型情况下,WebP是最好的选择,不过在一些大公司上比如微信上,就用到了另外的UI显示方式,Android微信上的SVG,有兴趣的可以了解下。
图片优化
在谈到图片压缩,首先说一下图片所占内存大小的计算方式:width* height*一个像素的所占用的字节数,这个计算方式适合图片来源于网络、磁盘、文件、流。对于来自res中的图片就不适合这个计算方式,具体可以查看博客图片占据内存大小的计算方式
知道了图片计算方式后,就可以减少宽高或降低一个像素所占字节数。来达到图片优化。同时除了处理图片本身还可以从内存预警,手动清除,图片弱引用等方面展开。
说到降低一个像素所占字节数那么就先说一下像素点的数据格式
- ALPHA_8 --(1B) 每个像素都需要
1(8位)个字节
的内存,只存储位图的透明度,没有颜色信息 - RGB_4444 --(2B) A(Alpha)占4位的精度,R(Red)占4位的精度,G(Green)占4位的精度,B(Blue)占4位的精度,加起来一共是16位的精度,折合是
2个字节
,也就是一个像素占两个字节的内存,同时存储位图的透明度和颜色信息。不过由于该精度的位图质量较差,官方不推荐使用 - ARGB_8888 – (4B) 这个类型的跟ARGB_4444的原理是一样的,只是A,R,G,B各占8个位的精度,所以一个像素占
4个字节
的内存。由于该类型的位图质量较好,官方特别推荐使用 - RGB_565 – (2B) R占5位精度,G占6位精度,B占5位精度,一共是16位精度,折合
2个字节
。这里注意的时,这个类型存储的只是颜色信息,没有透明度信息
质量压缩
减少像素点大小&&质量压缩
ARGB_8888占内存、清晰度高、支持透明效果
RGB_565节省内存、清晰度适中、不支持透明效果
首先我找到一张JPG图片http://pic.netbian.com/uploads/allimg/180315/110404-1521083044b19d.jpg
图片宽高 3840*2160,保存到手机是4.1M,
同时找到一张PNGhttps://pic.ibaotu.com/00/70/48/30V888piCw87.jpg-0.jpg!ww7002
图片宽高 5017*3543,保存到手机是2.8M,
/**
* 加载网络图片
*
* @param url
* @return
*/
public static Bitmap getBitmapByUrl(String url) {
URL imgUrl = null;
Bitmap bitmap = null;
try {
imgUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) imgUrl
.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
/**
* 根据本地图片地址加载图片
*
* @param url
* @return
*/
public static Bitmap getBitmapByPhone(String url) {
Bitmap bitmap = BitmapFactory.decodeFile(url);
return bitmap;
}
public static Bitmap getBitmapByPhoneCompress(String url, Bitmap.CompressFormat format, int quality) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Bitmap bitmap = BitmapFactory.decodeFile(url);
bitmap.compress(format, quality, baos);
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
return bitmap;
}
/**
* 计算图片大小 适用于网络,本地等图片
* 内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
*
* @param bitmap
* @return
*/
public static int getBitmapSize(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //API 19
return bitmap.getAllocationByteCount();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12
return bitmap.getByteCount();
}
// 在低版本中用一行的字节x高度
return bitmap.getRowBytes() * bitmap.getHeight(); //earlier version
}
public static Bitmap getBitmap565(String url) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeFile(url, options);
return bitmap;
}
/**
* 根据图片路径进行压缩保存图片
*
* @param bitmap
* @param format
* @param quality
* @param name
* @param type
*/
public static void saveImage(Bitmap bitmap, Bitmap.CompressFormat format, int quality, String name, String type) {
String dir = Environment.getExternalStorageDirectory().getAbsolutePath();
File dirFile = new File(dir);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
File file = new File(dir, name + quality + type);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
bitmap.compress(format, quality, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
我们看下面的数据能得到信息:
首先我们根据图片在内存中的计算公式:JPG图片38402160=82944004=33177600,刚好等于JPG的图片内存大小。也就是说这个
JPG的图片一个像素占用4个字节,RGB_565的图片格式占用2个字节33177600/2=16588800,刚好等于上面16588800。
PNG图片50173543=177752314=71100924,刚好等于PNG的图片内存大小。也就是说PNG的图片一个像素占用4个字节。
图片类型 | 图片手机大小 | 图片内存大小 | 设置图片格式为565后内存 | 压缩90,压缩格式JPEG内存 | 压缩50,压缩格式JPEG内存 | 压缩90,压缩格式PNG内存 | 压缩50,压缩格式PNG内存 |
---|---|---|---|---|---|---|---|
JPG | 4211KB | 33177600 | 16588800 | 33177600 | 33177600 | 33177600 | 33177600 |
PNG | 2881KB | 71100924 | 71100924 | 71100924 | 71100924 | 71100924 | 71100924 |
结论:质量压缩不能减少在内存中的大小,JPG的图片如果原先每个像素占用4个字节设置图片格式后可能减少内存大小。
图片类型 | 图片手机大小 | 压缩90,压缩格式JPEG手机大小 | 压缩50,压缩格式JPEG手机大小 | 压缩90,压缩格式PNG手机大小 | 压缩50,压缩格式PNG手机大小 |
---|---|---|---|---|---|
JPG | 4211KB | 926.3kb | 203.5kb | 8.9M | 8.9M |
PNG | 2881KB | 404.6kb | 224.1kb | 2.9M | 2.9M |
结论:质量压缩后保存到手机设置压缩格式JPEG能减少在手机中的大小。如果JPG格式图片设置了压缩格式为PNG,那么JPG图片在手机中会更加大
Log.e("LOG", "========JPG手机中小size" + new File(dir_JPG).length() / 1024 + "KB");
Log.e("LOG", "========PNG手机中大小size" + new File(dir_PNG).length() / 1024 + "KB");
Log.i("LOG", "========JPG内存中大小size" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhone(dir_JPG)));
Log.i("LOG", "========PNG内存中大小size" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhone(dir_PNG)));
Log.i("LOG", "========JPG设置图片格式为565后内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmap565(dir_JPG)));//本地图片修改图片格式大小
Log.i("LOG", "========PNG设置图片格式为565后内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmap565(dir_PNG)));//本地图片修改图片格式大小
Log.i("LOG", "========JPG压缩90,压缩格式JPEG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_JPG, Bitmap.CompressFormat.JPEG, 90)));
Log.i("LOG", "========JPG压缩50,压缩格式JPEG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_JPG, Bitmap.CompressFormat.JPEG, 50)));
Log.i("LOG", "========JPG压缩90,压缩格式PNG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_JPG, Bitmap.CompressFormat.PNG, 90)));
Log.i("LOG", "========JPG压缩50,压缩格式PNG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_JPG, Bitmap.CompressFormat.PNG, 50)));
Log.i("LOG", "========PNG压缩90,压缩格式JPEG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_PNG, Bitmap.CompressFormat.JPEG, 90)));
Log.i("LOG", "========PNG压缩50,压缩格式JPEG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_PNG, Bitmap.CompressFormat.JPEG, 50)));
Log.i("LOG", "========PNG压缩90,压缩格式PNG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_PNG, Bitmap.CompressFormat.PNG, 90)));
Log.i("LOG", "========PNG压缩50,压缩格式PNG内存" + BitmapUtil.getBitmapSize(BitmapUtil.getBitmapByPhoneCompress(dir_PNG, Bitmap.CompressFormat.PNG, 50)));
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.JPEG, 100, "j_image", ".JPG");//3.9M
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.JPEG, 90, "j_image", ".JPG");//926.3kb
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.JPEG, 50, "j_image", ".JPG");///203.5kb
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.PNG, 100, "j2_image", ".JPG");//8.9M
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.PNG, 90, "j2_image", ".JPG");//8.9M
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_JPG), Bitmap.CompressFormat.PNG, 50, "j2_image", ".JPG");//8.9M
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.JPEG, 100, "p_image", ".PNG");//1M
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.JPEG, 90, "p_image", ".PNG");//404.6kb
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.JPEG, 50, "p_image", ".PNG");//224.1kb
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.PNG, 100, "p2_image", ".PNG");//2.9M
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.PNG, 90, "p2_image", ".PNG");//2.9M
BitmapUtil.saveImage(BitmapUtil.getBitmapByPhone(dir_PNG), Bitmap.CompressFormat.PNG, 50, "p2_image", ".PNG");//2.9M
分辨率压缩
分辨率压缩很好理解,原来图片是38402160,压缩到20001000,根据图片内存公式,那么图片自然在内存中就减少了.
public static Bitmap bitmapFactory(String url, int width, int height) {
// 配置压缩的参数
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; //获取当前图片的边界大小,而不是将整张图片载入在内存中,避免内存溢出
BitmapFactory.decodeFile(url, options);
options.inJustDecodeBounds = false;
inSampleSize的作用就是可以把图片的长短缩小inSampleSize倍,所占内存缩小inSampleSize的平方
options.inSampleSize = caculateSampleSize(options, width, height);
Bitmap bm = BitmapFactory.decodeFile(url, options); // 解码文件
return bm;
}
/**
* 计算出所需要压缩的大小
*
* @param options
* @param reqWidth 我们期望的图片的宽,单位px
* @param reqHeight 我们期望的图片的高,单位px
* @return
*/
private static int caculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int sampleSize = 1;
int picWidth = options.outWidth;
int picHeight = options.outHeight;
if (picWidth > reqWidth || picHeight > reqHeight) {
int halfPicWidth = picWidth / 2;
int halfPicHeight = picHeight / 2;
while (halfPicWidth / sampleSize > reqWidth || halfPicHeight / sampleSize > reqHeight) {
sampleSize *= 2;
}
}
return sampleSize;
}
Log.i("LOG", "========JPG设置2000*1000分辨率后内存" + BitmapUtil.getBitmapSize(BitmapUtil.bitmapFactory(dir_JPG, 2000, 1000)));//8294400
Log.i("LOG", "========JPG设置1000*500分辨率后内存" + BitmapUtil.getBitmapSize(BitmapUtil.bitmapFactory(dir_JPG, 1000, 500)));//2073600
Log.i("LOG", "========PNG设置2000*1000分辨率后内存" + BitmapUtil.getBitmapSize(BitmapUtil.bitmapFactory(dir_PNG, 2000, 1000)));//17766672
Log.i("LOG", "========PNG设置1000*500分辨率后内存" + BitmapUtil.getBitmapSize(BitmapUtil.bitmapFactory(dir_PNG, 1000, 500)));// 4439160
BitmapUtil.saveImage(BitmapUtil.bitmapFactory(dir_JPG, 2000, 1000), Bitmap.CompressFormat.JPEG, 100, "j_image", ".JPG");//1920*1080 1M
BitmapUtil.saveImage(BitmapUtil.bitmapFactory(dir_JPG, 1000, 500), Bitmap.CompressFormat.JPEG, 100, "j2_image", ".JPG");//960*540 253.4kb
BitmapUtil.saveImage(BitmapUtil.bitmapFactory(dir_PNG, 2000, 1000), Bitmap.CompressFormat.JPEG, 100, "p_image", ".PNG");//2508*1771 361.7kb
BitmapUtil.saveImage(BitmapUtil.bitmapFactory(dir_PNG, 1000, 500), Bitmap.CompressFormat.JPEG, 100, "p2_image", ".PNG");//1254*885 131kb
图片类型 | 压缩比例2000*1000 | 压缩比例1000* 500 |
---|---|---|
JPG | 实际比例:920*1080 手机大小:1M | 实际比例:960*540 手机大小:253.4kb |
PNG | 实际比例:2508*1771 手机大小:361.7kb | 实际比例:1254*885 手机大小:131kb |
结论:分辨率压缩能减少图片在手机和内存中的大小,其实减小了图片大小,根据减少后的实际大小就能计算出图片在内存中的大小。根据表格中的实际比例,其实算出来的图片内存大小就是和我注释中的一样。那么根据以上原理,我们可以根据不用需求去处理内存OOM或者是上传图片限制大小。
注意:上面的保存图片都使用的是Bitmap.CompressFormat.JPEG
如何计算一张图片在内存中大小
图片的压缩
Android图片压缩以及优化
Android支持的图片格式
一张图片的内存大小是怎么计算的