Silicompressor源码学习(一) 基本架构和图片压缩

偶然看到一篇博客上说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



猜你喜欢

转载自blog.csdn.net/zhang___yong/article/details/79697146