TakePhoto框架源码流程解析(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_18242391/article/details/79264012

上一篇文章中介绍了TakePhoto的常见api,这一篇打算介绍下TakePhoto的图片压缩解决方案,TakePhoto的图片压缩有两种方案,一种是第三方压缩工具LuBan,另一种是自带的图片压缩方案,自身的图片压缩方案分为尺寸压缩质量压缩,关于图片压缩的更详细的文章可以看这里。接下来将结合源码介绍下TakePhoto中这两种图片压缩方案的实现过程。


入口调用

图片无论是拍照或者选取成功,都会调用takeResult方法,下面是其部分实现代码

CompressImageImpl.of(contextWrap.getActivity(), compressConfig, result.getImages(), new CompressImage.CompressListener() {
@Override
public void onCompressSuccess(ArrayList<TImage> images) {
    if(!compressConfig.isEnableReserveRaw()) {
        deleteRawFile(images);
    }
    handleTakeCallBack(result);
    ....
}

@Override
public void onCompressFailed(ArrayList<TImage> images, String msg) {
    if(!compressConfig.isEnableReserveRaw()) {
        deleteRawFile(images);
    }
    handleTakeCallBack(TResult.of(images), String.format(contextWrap.getActivity().getResources().getString(R.string.tip_compress_failed), message.length > 0 ? message[0] : "", msg, result.getImage().getCompressPath()));
    ....
}
}).compress();

这个只有配置了compressConfig,这段代码才会执行。在这段代码中,首先会调用CompressImageImpl.of初始化图片压缩方式,跳进去查看其实现代码。

public static CompressImage of(Context context,CompressConfig config, ArrayList<TImage> images, CompressImage.CompressListener listener) {
    if(config.getLubanOptions()!=null){
        return new CompressWithLuBan(context,config,images,listener);
    }else {
        return new CompressImageImpl(context,config,images,listener);
    }
}

可以看见根据是否有config.getLubanOptions()配置来选择CompressWithLuBanCompressImageImpl两种实例化方式,根据对应的实例化方式,来调用compress()压缩方法。详细介绍下两种压缩方式的实现过程。


CompressWithLuBan

鲁班压缩,内部封装了鲁班压缩的使用,查看其构造函数:

public CompressWithLuBan(Context context, CompressConfig config, ArrayList<TImage> images,
    CompressListener listener) {
  options = config.getLubanOptions();
  this.images = images;
  this.listener = listener;
  this.context = context;
}

第一个参数CompressConfig压缩配置,通过这个可以配置LuBan自带的压缩自带的压缩可以配置的参数有长宽最大像素压缩的大小,是否采用像素压缩,是否采用质量压缩,是否保留源文件这几种配置,LuBan压缩可以配置的有最大长度最大高度,和压缩的大小这几种。启用压缩时候会调用compress,里面通过images的数量来判断是否压缩单张和多张。压缩的方法是调用LuBan的api,压缩成功回调

listener.onCompressSuccess(images);

压缩的图片有为null的或为空的回调

listener.onCompressFailed();

Luban压缩的实现还是很简单的,内部只是封装了Luban的api,简化了我们的调用过程。


CompressImageImpl

这是TakePhoto自己实现的一套压缩算法,也可以实现对多张图片的压缩

compressImageUtil.compress(image.getOriginalPath(), new CompressImageUtil.CompressListener() {
    @Override
    public void onCompressSuccess(String imgPath) {
        image.setCompressPath(imgPath);
        continueCompress(image,true);
    }

    @Override
    public void onCompressFailed(String imgPath, String msg) {
        continueCompress(image,false,msg);
    }
});

private void continueCompress(TImage image,boolean preSuccess,String...message){
   image.setCompressed(preSuccess);
   int index=images.indexOf(image);
   boolean isLast=index== images.size() - 1;
   if (isLast) {
       handleCompressCallBack(message);
   }else {
       compress(images.get(index+1));
   }
}

 private void handleCompressCallBack(String...message){
     if(message.length>0){
         listener.onCompressFailed(images,message[0]);
         return;
     }

     for(TImage image:images){
         if(!image.isCompressed()){
             listener.onCompressFailed(images,image.getCompressPath()+" is compress failures");
             return;
         }
     }
     listener.onCompressSuccess(images);
 }

这是实现的最重要的部分,其中compressImageUtil中封装了像素压缩质量压缩两种方法。而continueCompress当一张图片压缩完成之后,判断是否还有继续要压缩的图片,有就通过compress继续压缩下一张,直到全部压缩完毕,每压缩一次压缩的图片就会通过setCompressPath放到compressPath中,最后通过handleCompressCallBack回调给最先给到的takeResultonCompressSuccess回调中,在该回调中就可以获取压缩到的图片和源图片。

继续来看compressImageUtil的实现,首先看它的compress方法

public void compress(String imagePath, CompressListener listener) {
if (config.isEnablePixelCompress()){
    try {
        compressImageByPixel(imagePath,listener);
    } catch (FileNotFoundException e) {
        listener.onCompressFailed(imagePath,String.format("图片压缩失败,%s",e.toString()));
        e.printStackTrace();
    }
}else {
    compressImageByQuality(BitmapFactory.decodeFile(imagePath),imagePath,listener);
}
}

takephoto的压缩是回调这个compress方法,在这个方法中首先根据isEnablePixelCompress来判断是否开启了尺寸压缩,是就开启尺寸压缩,否则进行质量压缩,首先看质量压缩

/**
* 按比例缩小图片的像素以达到压缩的目的
* */
private void compressImageByPixel(String imgPath,CompressListener listener) throws FileNotFoundException {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;//只读边,不读内容
BitmapFactory.decodeFile(imgPath, newOpts);
newOpts.inJustDecodeBounds = false;
int width = newOpts.outWidth;
int height = newOpts.outHeight;
float maxSize =config.getMaxPixel();
int be = 1;
if (width >= height && width > maxSize) {//缩放比,用高或者宽其中较大的一个数据进行计算
    be = (int) (newOpts.outWidth / maxSize);
    be++;
} else if (width < height && height > maxSize) {
    be = (int) (newOpts.outHeight / maxSize);
    be++;
}
newOpts.inSampleSize =be;//设置采样率
newOpts.inPreferredConfig = Config.ARGB_8888;//该模式是默认的,可不设
newOpts.inPurgeable = true;// 同时设置才会有效
newOpts.inInputShareable = true;//。当系统内存不够时候图片自动被回收
Bitmap bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
if (config.isEnableQualityCompress()){
    compressImageByQuality(bitmap,imgPath,listener);//压缩好比例大小后再进行质量压缩
}else {
    File thumbnailFile=getThumbnailFile(new File(imgPath));
    bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(thumbnailFile));

    listener.onCompressSuccess(thumbnailFile.getPath());
}
}

在该方法中可以看见主要是通过动态计算图片的采样率,来达到压缩图片尺寸的目的。在方法开始首先通过将inJustDecodeBounds设置为true,来读图片的宽高,而不去加载图片,接着计算要压缩的be,如果宽高有一个大于maxSize,就用宽高中较大的去除以maxSize,然后自身加一,来保证压缩的尺寸不能超过设置的做大值,通过getThumbnailFile来保证存放的位置在cachetakephoto_cache目录下面,最后通过bitmap.compress来将图片存放到刚才指定目录下面。通过该方法可以直到这是常见的图片无损压缩方法,我们通过这种方法来让显示的图片不会因为过大而OOM

如果我们配置了质量压缩,就会继续调用compressImageByQuality来进行有损压缩图片,继续看下有损压缩

/**
* 多线程压缩图片的质量
* */
private void compressImageByQuality(final Bitmap bitmap, final String imgPath, final CompressListener listener){
if(bitmap==null){
    sendMsg(false,imgPath,"像素压缩失败,bitmap is null",listener);
    return;
}
new Thread(new Runnable() {//开启多线程进行压缩处理
    @Override
    public void run() {
        // TODO Auto-generated method stub
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int options = 100;
        bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//质量压缩方法,把压缩后的数据存放到baos中 (100表示不压缩,0表示压缩到最小)
        while (baos.toByteArray().length >config.getMaxSize()) {//循环判断如果压缩后图片是否大于指定大小,大于继续压缩
            baos.reset();//重置baos即让下一次的写入覆盖之前的内容 
            options -= 5;//图片质量每次减少5
            if(options<=5)options=5;//如果图片质量小于5,为保证压缩后的图片质量,图片最底压缩质量为5
            bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//将压缩后的图片保存到baos中
            if(options==5)break;//如果图片的质量已降到最低则,不再进行压缩
        }
//              if(bitmap!=null&&!bitmap.isRecycled()){
//                  bitmap.recycle();//回收内存中的图片
//              }
        try {
            File thumbnailFile=getThumbnailFile(new File(imgPath));
            FileOutputStream fos = new FileOutputStream(thumbnailFile);//将压缩后的图片保存的本地上指定路径中
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
            sendMsg(true, thumbnailFile.getPath(),null,listener);
        } catch (Exception e) {
            sendMsg(false,imgPath,"质量压缩失败",listener);
            e.printStackTrace();
        }
    }
}).start();
}

图片的质量压缩是内部开启一个子线程,然后不断减少bitmap.compress第二个参数的值,当减少到一定阈值时候,就停止压缩,通过sendMsg将压缩的图片发送给主线程,然后借助listener.onCompressSuccess(imagePath)将图片路径回调出去,最后通过onCompressSuccess可以取出图片的压缩路径和源路径,将结果交给handleTakeCallBack处理,最后会交给takeSuccess,用户可以通过takeSuccess的参数拿到最终的图片,如果我们不想保存源图,可以配置isEnableReserveRaw来将拍照后的源图片删除,用户此时就只会获取到压缩的图片。这是TakePhoto自带的图片压缩的大致实现流程。


ImageRotateUtil

图片角度矫正类,如果我们配置了isCorrectImage,内部会矫正图片的角度。这是一个附加的功能。内部大致原理是通过getBitmapDegree获取图片倾斜的度数,然后通过rotateBitmapByDegree旋转图片得到新的图片,将新的图片覆盖以前老的图片地址,得到矫正后的图片地址。


总结

TakePhoto的图片压缩分为Luban压缩和自带的尺寸和质量压缩两种算法,我自己一般在项目中使用Luban压缩,Luban压缩可以单独使用,而且效果也很好,但是内部的压缩算法我们还是有必要了解的,这对我们自己写图片的ImageLoader有很大帮助,如果我们需要自己定制自己的图片框架,可以参考TakePhoto的内部压缩算法,这是很有用的。

猜你喜欢

转载自blog.csdn.net/qq_18242391/article/details/79264012