参考文章:http://www.codeceo.com/article/android-image-compression.html
在App开发过程中,免不了要上传图片到Server端。考虑到大图片带来的请求时长及传输失败风险,通常情况下都会对图片进行压缩处理后再上传。目前常用的两种压缩方式是:
1.通过降低图片质量;
2.通过减小图片分辨率;
详见开篇提到的参考文章。
这里再补充一下实现过程中的注意事项:
1.图片质量Quality不能无限制过低;需要根据压缩后图片的展示区域作调整。在200x200的展示区域下,图片质量不宜低于40;
2.有些图片的Exif信息里图片原始的方向是有转向的,比如具体展示时需要根据exif里的方向信息转向90度或者180度再显示。这些信息在图片压缩时可能会被压缩掉;从而导致这些图片展示时方向不对。
下面给出的代码实现综合考虑了上述因素后,实现原理如下:
1.根据图片的最大限制值来判断是否是需要压缩;
2.压缩前,先读取图片的exif信息;如果需要转向,则先对图片进行转向处理;
3.对纠正了转向之后的图片先进行图片分辨率的压缩,再进行图片质量的压缩;
下面是具体实现:
其中方法:
ArrayList<String> compress(Context context, ArrayList<String> srcPicList)
是入口方法。
package com.wuba.job.parttime.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;
import com.wuba.commons.AppCommonInfo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* Created by changwenna on 2016/11/25.
*/
public class PtBitmapUtils {
private final static String PT_CACHE_TEMP_PIC_PATH = AppCommonInfo.sDatadir + File.separator + "ptjob" + File.separator + "temp" + File.separator;
private static int UPLOAD_MAX_FILE_SIZE = 512000;// 500K
private static int UPLOAD_QUALITY_LOW = 40;
public static ArrayList<String> compress(Context context, ArrayList<String> srcPicList) {
ArrayList<String> destPicList = new ArrayList<>();
File file;
for (int i = 0; i < srcPicList.size(); i++) {
String sourceUrl = srcPicList.get(i);
if (TextUtils.isEmpty(sourceUrl) || sourceUrl.startsWith("http")) {
continue;
}
file = new File(sourceUrl);
if (file.exists() && file.length() > UPLOAD_MAX_FILE_SIZE) {
//超过限制,需要压缩
String procedUrl = procPicFile(context, file.getAbsolutePath());
if (TextUtils.isEmpty(procedUrl)) {
//压缩失败,则返回原图
destPicList.add(sourceUrl);
}
File procedFile = new File(procedUrl);
if (procedFile.exists()) {
destPicList.add(procedFile.getAbsolutePath());
}
} else{
destPicList.add(sourceUrl);
}
}
return destPicList;
}
/**
* 是否需要调整图片方向
*
* @param filePath
* @return
*/
public static boolean needRotate(String filePath) {
try {
ExifInterface exif = new ExifInterface(filePath);
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
return true;
case ExifInterface.ORIENTATION_ROTATE_270:
return true;
case ExifInterface.ORIENTATION_ROTATE_180:
return true;
}
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static void rotatedBitmap(Context context, String filePath, String dstFilePath) throws IOException {
File file = new File(dstFilePath);
if (file.getParentFile() != null && !file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
ExifInterface exif = new ExifInterface(filePath);
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
Matrix matrix = null;
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
matrix = new Matrix();
matrix.postRotate(90);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix = new Matrix();
matrix.postRotate(270);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix = new Matrix();
matrix.postRotate(180);
break;
}
Bitmap bmp = getSmallBitmap(context,
Uri.fromFile(new File(filePath)));
if (matrix != null) {
Bitmap bmp2 = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(),
bmp.getHeight(), matrix, true);
bmp.recycle();
FileOutputStream fos = new FileOutputStream(dstFilePath);
bmp2.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.close();
bmp2.recycle();
} else {
FileOutputStream fos = new FileOutputStream(dstFilePath);
FileInputStream fis = new FileInputStream(filePath);
byte[] buffer = new byte[1024];
int len = 0;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.close();
fis.close();
}
}
/**
* 通过压缩图片质量来压缩图片
* 常用于图片上传
*
* @param image
* @param dstFilePath
*/
public static void compressImageByQuality(Bitmap image, String dstFilePath) {
if (image == null) {
return;
}
if (TextUtils.isEmpty(dstFilePath)) {
return;
}
File sourecFile = new File(dstFilePath);
if (!sourecFile.exists()) {
return;
}
int quality = 100;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, quality, baos);
while (baos.toByteArray().length / 1024 > UPLOAD_MAX_FILE_SIZE) {
baos.reset();
try {
image.compress(Bitmap.CompressFormat.JPEG, quality, baos);
} catch (Exception e) {
e.printStackTrace();
break;
}
if (quality <= UPLOAD_QUALITY_LOW) {
break;
}
quality -= 10;
}
image.recycle();
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
FileOutputStream fos;
try {
fos = new FileOutputStream(dstFilePath);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
bitmap.recycle();
}
/**
* 默认先进行像素压缩,如果还达高于大小的限制,再进行图片质量的压缩
*
* @param srcPath
* @param dstFilePath
*/
public static void compressImage(String srcPath, String dstFilePath) {
if (TextUtils.isEmpty(srcPath) || TextUtils.isEmpty(dstFilePath)) {
return;
}
File sourecFile = new File(srcPath);
if (!sourecFile.exists()) {
return;
}
if (sourecFile.length() / 1024 <= UPLOAD_MAX_FILE_SIZE) {
//原始文件不需要压缩
return;
}
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(srcPath, newOpts);
int w = newOpts.outWidth;
int h = newOpts.outHeight;
//现在主流手机比较多是800*480分辨率,所以目标高和宽我们设置为:
float hh = 800f;
float ww = 480f;
//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1; //be=1表示不缩放
if (w > h && w > ww) {
//如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {
//如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0) {
be = 1;
}
newOpts.inSampleSize = be;
newOpts.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
FileOutputStream fos;
try {
fos = new FileOutputStream(dstFilePath);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
File dstFile = new File(dstFilePath);
if (!dstFile.exists()) {
return;
}
if (dstFile.length() <= UPLOAD_MAX_FILE_SIZE) {
return;
}
compressImageByQuality(bitmap, dstFilePath);
}
public static Bitmap getSmallBitmap(Context context, Uri uri) {
return getBitmap(context, uri, -1, 128 * 128);
}
public static Bitmap getBitmap(Context context, Uri uri, int maxWidth,
int maxHeight) {
try {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream is = context.getContentResolver().openInputStream(uri);
BitmapFactory.decodeStream(is, null, options);
is.close();
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
is = context.getContentResolver().openInputStream(uri);
Bitmap bmp = BitmapFactory.decodeStream(is, null, options);
is.close();
return bmp;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int initialSize = computeInitialSampleSize(options, reqWidth, reqHeight);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 2;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
return lowerBound;
}
if ((maxNumOfPixels == -1) &&
(minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}
/**
* @param context
* @param mySourecUrl
* @return
*/
public static String procPicFile(Context context, String mySourecUrl) {
String procedUrl = null;
if (TextUtils.isEmpty(mySourecUrl)) {
return procedUrl;
}
File sourecFile = new File(mySourecUrl);
if (!sourecFile.exists()) {
return procedUrl;
}
if (sourecFile.length() / 1024 <= UPLOAD_MAX_FILE_SIZE) {
return mySourecUrl;
}
// 压缩前准备:调整图片方向,以免压缩后丢掉了exif信息导致图片显示方向异常
if (needRotate(mySourecUrl)) {
String urlRotated = PT_CACHE_TEMP_PIC_PATH + "tmp_rotate" + SystemClock.currentThreadTimeMillis() + ".jpg";
File rotatedFile = new File(urlRotated);
if (rotatedFile.exists()) {
rotatedFile.delete();
try {
rotatedFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
rotatedBitmap(context, mySourecUrl, urlRotated);
procedUrl = urlRotated;
} catch (Exception e) {
e.printStackTrace();
procedUrl = mySourecUrl;
}
} else {
procedUrl = mySourecUrl;
}
String urlCompressed = PT_CACHE_TEMP_PIC_PATH + "tmp_compress" + SystemClock.currentThreadTimeMillis() + ".jpg";
File compressFile = new File(urlCompressed);
if (compressFile.exists()) {
compressFile.delete();
}
//开始压缩图片文件
compressImage(procedUrl, urlCompressed);
File compressedFile = new File(urlCompressed);
if (compressedFile.exists()) {
procedUrl = urlCompressed;
} else {
procedUrl = null;
}
return procedUrl;
}
}