偶然看到一篇博客上说Silicompressor这个图片视频压缩开源库很强大,正好想要研究研究图片视频压缩,就读一读源码。
首先来看用法:
String filePath = SiliCompressor.with(Context).compress(imageUriString, destinationFile);
熟悉的链式语法,参数传入原图片uriString和压缩后图片存放的文件夹,返回压缩后图片绝对路径。
核心类就是SiliCompressor了。
with返回一个SiliCompressor实例,这里是一个二次判空实现的单例模式,实例化由静态内部类Builder完成(Builder模式,这里直接调用了构造器,实例化过程并不复杂,用builder有点多余),传入的context调用getApplicationContext作为依赖的context。和一些主流框架,如picasso的设计完全一样。
来看看和图片压缩有关的有哪些方法:
1.String compress(String uriString,File destFolder);
2.String compressImage(String uriString,File destFolder);
3.String compress(String uriString,File destFolder,boolean deleteSource);
4.String compress(int drawableId);
5.Bitmap getCompressImage(String imageUri);
6.Bitmap getCompressImage(String imageUri,boolean deleteSource);
compress(String uriString,File destFolder)直接调用compressImage。compress(String uriString,File destFolder,boolean deleteSource)第三个参数表示是否删除原图片,也就是在调用compressImage后判断是否删除原图片。
compress(int drawableId)新建一个File将bitmap写入file然后调用compressImage,目标文件夹固定为“/SiliCompressor/images”:
String compressImagePath = compressImage(copyImageUri.toString(),new File(Environment.getExternalStorageDirectory(), "Silicompressor/images"));
然后是getCompressBitmap,还是调用compressImage,只不过转为bitmap返回:
public Bitmap getCompressBitmap(String imageUri) throws IOException { File imageFile = new File(compressImage(imageUri,new File(Environment.getExternalStorageDirectory(), "Silicompressor/images"))); Uri newImageUri = Uri.fromFile(imageFile); Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), newImageUri); return bitmap; }
综上,所有图片压缩方法最终都是调用compressImage。另外,笔者还想说一下,感觉这几个方法设计的很别扭,关系不合理。
接着来看compressImage:
一。
这里先用了尺寸压缩,接触过的同学都知道,尺寸压缩的关键在于inSampleSize的确定,这里作者提供了一个方法计算这个值:
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } final float totalPixels = width * height; final float totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { inSampleSize++; } return inSampleSize; }参数是options和期望压缩后的宽高,首先根据原宽高和期望宽高计算出一个倍数,接下来进入while循环直到压缩后像素点数是否小于期望像素点数的2倍。那么接下来看看这个期望宽高是怎么计算出来的吧。
compressImage部分代码:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap bmp = BitmapFactory.decodeFile(filePath, options); int actualHeight = options.outHeight; int actualWidth = options.outWidth; // max Height and width values of the compressed image is taken as 816x612 float maxHeight = 816.0f; float maxWidth = 612.0f; float imgRatio = actualWidth / actualHeight; float maxRatio = maxWidth / maxHeight; if (actualHeight > maxHeight || actualWidth > maxWidth) { if (imgRatio < maxRatio) {//原图更瘦 imgRatio = maxHeight / actualHeight; actualWidth = (int) (imgRatio * actualWidth); actualHeight = (int) maxHeight; } else if (imgRatio > maxRatio) { imgRatio = maxWidth / actualWidth; actualHeight = (int) (imgRatio * actualHeight); actualWidth = (int) maxWidth; } else { actualHeight = (int) maxHeight; actualWidth = (int) maxWidth; } } options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight); options.inJustDecodeBounds = false; // this options allow android to claim the bitmap memory if it runs low on memory options.inPurgeable = true; options.inInputShareable = true; options.inTempStorage = new byte[16 * 1024]; try { // load the bitmap from its path bmp = BitmapFactory.decodeFile(filePath, options); } catch (OutOfMemoryError exception) { exception.printStackTrace(); }
这部分的逻辑有点绕,首先定义一个期望压缩图片的最大宽高(612*816)。想象有这么一个612*816的框,把一个图片等比例压缩直到贴到框上面。具体实现是计算原图的宽高比,然后和这个框的宽高比比较,如果原图的比值比较小(也就是比较瘦),那么期望压缩后高度就与框齐平(816),然后期望宽度根据这个值和原比例计算出来。将期望宽高传入上面的方法就得到压缩比(inSampleSize),至此,尺寸压缩的部分就完成了,生成压缩后的Bitmap bmp。
如果上面的过程你都理解了的话,那么你应该明白,假设期望像素点数是x,那么现在得到的bmp像素点数应该是[x,2x)
二。
这还没完,inSampleSize这个属性只能帮我们逼近目标宽高,接下来就需要再一次压缩,思路是将bmp画到一个期望宽高的新bitmap上。一个小知识点:
ALPHA_8 代表8位Alpha位图
ARGB_4444 代表16位ARGB位图
ARGB_8888 代表32位ARGB位图
RGB_565 代表8位RGB位图
官方推荐使用ARGB_8888作为色彩模式,节省内存而且清晰度高
官方推荐8888,画的时候用8888的bitmap,先上代码:
try { scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888); } catch (OutOfMemoryError exception) { exception.printStackTrace(); } float ratioX = actualWidth / (float) options.outWidth; float ratioY = actualHeight / (float) options.outHeight; float middleX = actualWidth / 2.0f; float middleY = actualHeight / 2.0f; Matrix scaleMatrix = new Matrix(); scaleMatrix.setScale(ratioX, ratioY, middleX, middleY); Canvas canvas = new Canvas(scaledBitmap); canvas.setMatrix(scaleMatrix); canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG)); // check the rotation of the image and display it properly ExifInterface exif; try { exif = new ExifInterface(filePath); int orientation = exif.getAttributeInt( ExifInterface.TAG_ORIENTATION, 0); Log.d("EXIF", "Exif: " + orientation); Matrix matrix = new Matrix(); if (orientation == 6) { matrix.postRotate(90); Log.d("EXIF", "Exif: " + orientation); } else if (orientation == 3) { matrix.postRotate(180); Log.d("EXIF", "Exif: " + orientation); } else if (orientation == 8) { matrix.postRotate(270); Log.d("EXIF", "Exif: " + orientation); } scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); } catch (IOException e) { e.printStackTrace(); } FileOutputStream out = null; String filename = getFilename(imageUri, destDirectory); try { out = new FileOutputStream(filename); // write the compressed bitmap at the destination specified by filename. scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out); } catch (FileNotFoundException e) { e.printStackTrace(); } return filename;
借助matrix矩阵设置缩放比,然后就可以画在scaledBitmap上。接下来还要检查原图是否有旋转,通过ExifInterface这个存储图片信息的类来进行检查,如果有旋转则通过矩阵转回去。最后写到文件里就可以了。
总结一下,先计算压缩后的期望宽高,然后使用inSampleSize将图片压缩到目标图片的[1,2)倍,然后计算精确压缩比并画到一个期望宽高的bitmap上,这就是最终图片,写到目标文件夹下(文件名这里作者用的是当前时间)。还需要注意的是demo里作者用asynctask来调用压缩方法,也就是说过程比较耗时,建议异步处理
matrix参考:https://blog.csdn.net/loongggdroid/article/details/18706999