轻量级、无耦合、高灵活图片压缩类库
2018-04-12 本文已影响555人
af83084249b7
起因
各位安卓开发的小伙伴,开发app或多或少都涉及到图片。我们的图片是那里来的呢?毫无疑问,第一次是从网络下载,之后才是用的缓存。
如果图片过于高清(资源大),我们下载展示出来就会很慢,这样用户体验肯定很差。那么,该怎么办呢?对的,下载尺寸更小的图片。不过,这些图片(头像,封面)等都是用户主动上传的。我们需要在上传前就对这些图片进行处理。
思路
我们图片处理的库应该是可以选择配置的。具体有:大小、格式、色彩值、质量等等。这样,我们可以根据业务需求设置不同参数,达到预期效果。
效果(简书让效果图色彩失真了,原图很直观,可参考文章最后github的展示图效果)

流程
首先设置我们想要的参数
//可以构造者方式设置,也可以创建对象设置属性值
TCompress tCompress = new TCompress.Builder()
.setMaxWidth(800) //指定最大宽度(算法处理之后,实际宽度小于以及接近指定最大宽度)
.setMaxHeight(900) //指定最大高度(算法处理之后,实际宽度小于以及接近指定最大高度)
.setQuality(80) //指定图片质量(范围0-100,对PNG类型无效)
.setFormat(Bitmap.CompressFormat.JPEG) //图片压缩类型,PNG带透明度
.setConfig(Bitmap.Config.RGB_565) //颜色格式
.build();
// TCompress tCompress = new TCompress();
// tCompress.setConfig(Bitmap.Config.RGB_565);
// tCompress.setFormat(Bitmap.CompressFormat.WEBP);
// tCompress.setQuality(80);
// tCompress.setMaxWidth(800);
// tCompress.setMaxHeight(800);
选择同步、异步压缩
图片压缩涉及IO耗时操作,推荐异步处理。
//同步压缩
private void sync(TCompress tCompress) {
File compressedFile = tCompress.compressedToFile(mFile);
if (compressedFile == null) {
//请查看文件权限问题(其他问题基本不存在,可以查看日志详情)
return;
}
// 另外三种(设定入参类型、返回类型)
// File compressedFile1 = tCompress.compressedToFile(mBitmap);
// Bitmap bitmap = tCompress.compressedToBitmap(mFile);
// Bitmap bitmap1 = tCompress.compressedToBitmap(mBitmap);
//数据显示
showData(compressedFile);
}
------------------------------------------分割--------------------------------------------------
//异步压缩(入参是文件类型(待压缩图片文件),返回类型是文件(压缩后图片文件))
private void async(TCompress tCompress) {
//泛型设置回调类型。如果不指定泛型,也可以根据方法名的ToFile、ToBitmap进行强转
//文件压缩到指定文件
tCompress.compressToFileAsync(mFile, new OnCompressListener<File>() {
//onCompressStart是非抽象方法,可选监听 可以开启提示框等 默认不重写
@Override
public void onCompressStart() {
// showToast("开始压缩");
}
@Override
public void onCompressFinish(boolean success, File file) {
if (success) {
showData(file);
} else {
//请查看文件权限问题(其他问题基本不存在,可以查看日志详情)
}
}
});
//----------------其他三种异步压缩类似(入参、返回参数类型)-------------
// otherThreeAsync();
}
完毕了,哈哈哈。不过这个类库是我自己封装的,最后放地址。
核心实现
主要流程(以图片文件压缩到图片文件为例子,其他过程包含在内)
一:创建tcompress对象指定配置信息(最大宽高参数指定图片同比例压缩后最接近的宽高值,但是实际宽高都小于设定值)。
二:选择压缩模式(输入类型、输出类型。被压缩对象可以是bitmap、文件类型,压缩后的对象也可以是bitmap或者文件类型,根据业务需要选择)
三:一次采样得到图片的宽高,不读入图片数据到内存(防止数据过大)
四:根据图片实际宽高和用户创建tcompress时候指定的最大宽高,计算第一次压缩倍数(让第一次压缩后读入内存的数据略大于用户设定的宽高)
五:根据第一次压缩倍数,设定参数。读入图片数据到内存,得到bitmap对象(此时完成一次压缩)
六:根据函数得到图片的旋转角度(三星手机相册选择图片旋转问题,其他手机正常),进行bitmap的旋转校正处理。
七:根据第一次图片压缩后的宽高(此时略大于用户设定的最大宽高值),以及用户设定的最大宽高值数据,进行算法处理。得到二次压缩比例。
八:根据比例,进行第二次图片压缩,得到最终bitmap。该bitmap宽高都略小于并接近用户设定的最大宽高。
九:根据用户设定的质量、格式等数据,从最终的bitmap得到最终的图片文件。
核心代码(附注释,不懂留言)
//压缩处理者对象
public class TCompress {
//默认属性,通过构造者模式或者set方法设置
private int mQuality = 80;
private float mMaxHeight = 1280;
private float mMaxWidth = 960;
private Bitmap.CompressFormat mFormat = Bitmap.CompressFormat.JPEG;
private Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
//---------------------主线路:文件里面的图片压缩完毕存入文件---------------------------------
public File compressedToFile(File file) {
File ret = null;
try {
//从图片文件里面获取bitmap对象。
Bitmap bitmap = getBitmap(file);
//进行图片的压缩,得到压缩后的bitmap(可以展示给用户了)
Bitmap compressedBitmap = compressedToBitmap(bitmap);
//将压缩后的bitmap存入文件(需要时候,将文件上传服务器)
ret = bitmap2File(compressedBitmap);
//bitmap资源回收
bitmap.recycle();
compressedBitmap.recycle();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return ret;
}
private Bitmap getBitmap(File file) {
//获取bitmapFactory.options对象(包含宽高等数据,但是并未载入内存,一次采样)
BitmapFactory.Options options = getOptions(file);
//根据原始图片文件以及options对象解析bitmap(二次采样,读入实际数据至内存)
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
//图片角度处理(我不会告诉你三星拍照后图片需要进行角度调整,要不然是横向图片)
bitmap = rotateBitmap(bitmap, file);
return bitmap;
}
private BitmapFactory.Options getOptions(File file) {
BitmapFactory.Options options = new BitmapFactory.Options();
//指明,只是解析Bounds数据,不读入数据
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
//设定第一次压缩的倍数(根据图片实际尺寸和用户设定最大宽高尺寸,算法得出,后续还有第二次压缩)
options.inSampleSize = setSampleSize(options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
return options;
}
private int setSampleSize(int outWidth, int outHeight) {
int sampleSize = 1;
//一次缩放倍数处理(保证图片最终尺寸刚好大于设定最大宽高尺寸)
//之后会进行二次缩放处理
//第一次是整数倍数处理,尽可能小的尺寸加载图片,占用尽可能少的内存
while (outWidth > mMaxWidth * (sampleSize + 1) && outHeight > mMaxHeight * (sampleSize + 1)) {
sampleSize++;
}
return sampleSize;
}
private Bitmap rotateBitmap(Bitmap bitmap, File file) {
//degree等于0,表示图片不需要旋转
int degree = getPictureDegree(file);
if (degree == 0) {
return bitmap;
}
Matrix matrix = new Matrix();
//创建bitmap
Bitmap ret = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
//以bitmap的中心为圆心,旋转degree度数(三星角度问题)
matrix.setRotate(degree, ret.getWidth() / 2, ret.getHeight() / 2);
Canvas canvas = new Canvas(ret);
canvas.drawBitmap(bitmap, matrix, null);
return ret;
}
private static int getPictureDegree(File file) {
int degree = 0;
try {
//获取当前角度
ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath());
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
//bitmap到压缩后的bitmap
public Bitmap compressedToBitmap(Bitmap bitmap) {
Bitmap ret = null;
try {
float height = bitmap.getHeight();
float width = bitmap.getWidth();
//获取压缩(缩放)比例
float ratio = setRatio(width, height);
//进行第二次压缩
ret = Bitmap.createBitmap((int) (width * ratio), (int) (height * ratio), mConfig);
Canvas canvas = new Canvas(ret);
canvas.drawBitmap(bitmap, null, new RectF(0, 0, ret.getWidth(), ret.getHeight()), null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return ret;
}
private float setRatio(float width, float height) {
float ratio = 1;
//保证压缩后图片宽高都小于设定的最大宽高值,并且接近之
if (mMaxWidth < width && mMaxHeight < height) {
if (mMaxWidth / width < mMaxHeight / height)
ratio = mMaxWidth / width;
else ratio = mMaxHeight / height;
} else if (mMaxWidth < width) ratio = mMaxWidth / width;
else if (mMaxHeight < height) ratio = mMaxHeight / height;
return ratio;
}
private File bitmap2File(Bitmap bitmap) {
File ret = null;
try {
//指定文件格式
String prefix = String.valueOf(System.currentTimeMillis());
String suffix = null;
switch (mFormat) {
case JPEG:
suffix = ".jpg";
break;
case PNG:
suffix = ".png";
break;
case WEBP:
suffix = ".webp";
break;
}
//创建文件并将bitmap数据写到文件
ret = File.createTempFile(prefix, suffix);
ret.deleteOnExit();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileOutputStream outputStream = new FileOutputStream(ret);
//指定了bitmap的格式、质量,最后输给字节流对象
bitmap.compress(mFormat, mQuality, baos);
outputStream.write(baos.toByteArray());
outputStream.flush();
baos.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
//-------扩展(bitmap到压缩后的bitmap,已经包含在,文件到压缩后的文件的步骤之中)----------------------------------
//文件图片压缩到新的bitmap
public Bitmap compressedToBitmap(File file) {
Bitmap ret = null;
try {
Bitmap bitmap = getBitmap(file);
ret = compressedToBitmap(bitmap);
bitmap.recycle();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return ret;
}
//bitmap压缩到新的文件
public File compressedToFile(Bitmap bitmap) {
File ret = null;
try {
Bitmap compressedBitmap = compressedToBitmap(bitmap);
ret = bitmap2File(compressedBitmap);
compressedBitmap.recycle();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return ret;
}
//--------------------------------设置参数--------------------------------------
public void setMaxHeight(int maxHeight) {
mMaxHeight = maxHeight;
}
public void setMaxWidth(int maxWidth) {
mMaxWidth = maxWidth;
}
public void setQuality(int quality) {
mQuality = quality;
}
public void setConfig(Bitmap.Config config) {
mConfig = config;
}
public void setFormat(Bitmap.CompressFormat format) {
mFormat = format;
}
//---------------------------------构建者模式---------------------------------------
public static class Builder {
private TCompress mTCompress;
public Builder() {
mTCompress = new TCompress();
}
public Builder setMaxHeight(int height) {
mTCompress.mMaxHeight = height;
return this;
}
public Builder setMaxWidth(int weight) {
mTCompress.mMaxWidth = weight;
return this;
}
public Builder setQuality(int quality) {
mTCompress.mQuality = quality;
return this;
}
public Builder setConfig(Bitmap.Config config) {
mTCompress.mConfig = config;
return this;
}
public Builder setFormat(Bitmap.CompressFormat format) {
mTCompress.mFormat = format;
return this;
}
public TCompress build() {
return mTCompress;
}
}
//-------------------------------添加异步处理----------------------------------------------
//图片压缩是耗时的,异步处理比较合适。
//添加属性
public OnCompressListener mListener;
public Handler mHandler;
//--------------------------------Handler--------------------------------------------------
private Handler getHandler() {
return new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (mListener != null) {
if (msg.obj == null) mListener.onCompressFinish(false, null);
else mListener.onCompressFinish(true, msg.obj);
}
return true;
}
});
}
//-------------------------------------异步方法-------------------------------------------
//文件压缩到文件
public void compressToFileAsync(final File file, OnCompressListener listener) {
mListener = listener;
mHandler = getHandler();
listener.onCompressStart();
new Thread(new Runnable() {
@Override
public void run() {
File target = compressedToFile(file);
Message message = mHandler.obtainMessage();
message.obj = target;
message.sendToTarget();
}
}).start();
}
//Bitmap压缩到文件
public void compressToFileAsync(final Bitmap bitmap, OnCompressListener listener) {
mListener = listener;
mHandler = getHandler();
listener.onCompressStart();
new Thread(new Runnable() {
@Override
public void run() {
File target = compressedToFile(bitmap);
Message message = mHandler.obtainMessage();
message.obj = target;
message.sendToTarget();
}
}).start();
}
//文件压缩到Bitmap
public void compressToBitmapAsync(final File file, OnCompressListener listener) {
mListener = listener;
mHandler = getHandler();
listener.onCompressStart();
new Thread(new Runnable() {
@Override
public void run() {
Bitmap target = compressedToBitmap(file);
Message message = mHandler.obtainMessage();
message.obj = target;
message.sendToTarget();
}
}).start();
}
//Bitmap压缩到Bitmap
public void compressToBitmapAsync(final Bitmap bitmap, OnCompressListener listener) {
mListener = listener;
mHandler = getHandler();
listener.onCompressStart();
new Thread(new Runnable() {
@Override
public void run() {
Bitmap target = compressedToBitmap(bitmap);
Message message = mHandler.obtainMessage();
message.obj = target;
message.sendToTarget();
}
}).start();
}
}
完事。
总结
自认为该仓库轻量级、代码规范易懂、无耦合(不含其他三方库)、灵活易用,是个良心图片处理类库。希望能帮助到大家~
依赖:compile 'com.jkt:tcompress:1.2.3'