徒手撸一个框架-MasterImageCompress图片压缩框架

MasterImageCompress

github连接:
github.com/xiaojigugu/…java

线程池+队列+观察者模式+建造者模式 实现多线程图片压缩android

使用

  1. Add it in your root build.gradle at the end of repositories:
allprojects {
		repositories {
			...
			maven { url 'https://jitpack.io' }
		}
	}
复制代码
  1. Add the dependency:
dependencies {
	        implementation 'com.github.xiaojigugu:MasterImageCompress:1.0.1'
	}
复制代码
  1. start
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
复制代码
//配置压缩条件
     CompressConfig compressConfig = CompressConfig
           .builder()
           .keepSource(true) //是否保留源文件
           //压缩方式,分为TYPE_QUALITY、TYPE_PIXEL、TYPE_PIXEL_AND_QUALITY,慎用单独的TYPE_QUALITY(很容易OOM)!
           .comPressType(CompressConfig.TYPE_PIXEL)
           //目标长边像素,对TYPE_PIXEL有效(eg:原图分辨率:7952 X 5304,压缩后7952最终会小于1280)
           .maxPixel(1280)
           //目标大小200kb之内,对TYPE_QUALITY有效
           .targetSize(200 * 1024)
           .format(Bitmap.CompressFormat.WEBP, Bitmap.Config.ARGB_8888) //压缩配置
           .outputDir("storage/emulated/0/DCIM/image_compressed/") //输出目录
           .build();
             
     //或者一句话CompressConfig compressConfig=CompressConfig.getDefault();
                   
     //添加须要压缩的图片路径 
     List<String> images = new ArrayList<>();
     for (File file1 : files) {
         String path = file1.getAbsolutePath();
         SystemOut.println("ImageCompressor ===> image,path=" + path);
         images.add(path);
     }
                    
     ImageCompressManager.builder()
           .paths(images)
           .config(compressConfig)
           .listener(new ImageCompressListener() {
                @Override
                public void onStart() {
                     SystemOut.println("ImageCompressor ===>开始压缩");
                }

                @Override
                public void onSuccess(List<ImageInstance> images) {
                     SystemOut.println("ImageCompressor ===>压缩成功");
                }

                @Override
                public void onFail(boolean allOrSingle, List<ImageInstance> images, CompressException e) {
                      SystemOut.println("ImageCompressor ===>压缩失败,isAll=" + allOrSingle);
                }
           })
           .compress();
复制代码

效率对比

左侧原图大小,右侧压缩后大小git

用时(基于mumu模拟器环境): github

线程池说明

我只开了3个核心线程,最大5个线程(PS:这玩意开多少彻底看项目需求) 多线程


固定执行一个核心线程,用来取压缩任务

观察者模式

经过java内置的ObservaableObserver实现简单的观察者模式
ImageCompressManager中数据更新时,Compressor压缩工具类会收到通知,通知开启多线程执行压缩任务框架

ImageCompressManager类中: maven

Compressor类中: ide

图片压缩原理解析

其实网上关于图片压缩的代码一搜一大堆,基本上都差很少,包括很热门的 luban框架。 写这个框架的初衷无非就是luban可配置项太少,致使不能达到我本身的需求,因此干脆从新写一个。
Android图片压缩提及来大致有三种手段,1. 采样压缩 2.质量压缩 3. 使用libjpeg
先说说libjpeg,使用libjpeg进行压缩操做须要引入相应的so包,这就会致使包体的增长,对于绝大部分项目来讲,咱们的图片压缩要求并无那么严格。因此很显然,用这种方法就显得得不偿失了。最终 MasterImageCompress 仍是采用了前两种方法,而且将选择权交给开发者手上,能够单独使用其中一个,也能够两种混用。工具

  1. 采样压缩(我喜欢叫像素压缩)
    采样压缩压缩的是像素大小(分辨率) 采样压缩的核心代码:
BitmapFactory.Options options = new BitmapFactory.Options();
        //计算采样率只须要宽高
        options.inJustDecodeBounds = true;
        //此处在option中已取得宽高
        BitmapFactory.decodeFile(imageInstance.getInputPath(), options);
        //计算并设置采样率
        options.inSampleSize = calculateSampleSize(options, compressConfig);
        //从新设置为decode整张图片,准备压缩
        options.inJustDecodeBounds = false;
        //应用新配置
        Bitmap bitmap = BitmapFactory.decodeFile(imageInstance.getInputPath(), options);
复制代码

说白了,采样压缩就是根据图片宽高合理的计算一个缩放比(采样率),而后经过这个缩放比去忽略一部分像素点来达到压缩图片的目的。可是因为Bitmap糟糕的内存占用,因此一般获取图片的宽高时,咱们会经过 inJustDecodeBounds来控制取边仍是取整张图,那么为何这个属性能够作到只取边呢,咱们看一下源码中的注释: gradle

翻译过来大致意思就是:
若是设置为true,decoder将不会返回bitmap,可是它的外部区域将会被设置(实际上是放入了BitmapFactory.Option中),容许调用者在不占用内存的状况下查询bitmap。 那么设置了为true之后就能够拿到长宽属性了吗?咱们再往下看看BitmapFactory.Option类中关于长宽的注释:
看一下咱们关心的地方,注释说道:若是 inJustDecodeBounds设置为true, outWidth/outHeight将会是输入图片的 width/height而且不会进行任何的缩放
那么到这里咱们就拿到了宽高属性,接下来就是计算 缩放比(采样率)
就是计算长边与目标像素大小的比值:
原图分辨率:
缩放比=长边像素/目标像素效果: (int)4032/1280=3
缩放比=长边像素/目标像素+1:(int)4032/1280+1=4( MasterImageCompress采用这种)

那么为何要+1呢?咱们来看一下源码注释:
英语10级水准的我现场翻译:
!@#¥%……& ()——+——)(&……%¥#@!@#¥%……&*()................................(不想看),直接看For Example后面的注释=>若是inSampleSize == 4则返回的图片的宽高将是原图的1/4,像素数量将是原来的1/16;inSampleSize <= 1时将按照1处理;注意:decoders使用的常量是2的幂方数,任何其余不是2的幂方数的数值将会向下找最接近他的2的幂方数。
因此,咱们计算出来的缩放比在最终使用的时候极可能小于咱们的目标缩放比,这样计算出来的像素确定是要大于咱们需求的最大像素的,因此这里咱们选择缩放比=长边像素/目标像素+1。 这里我仍是计算一下,省得小伙伴没看懂:
原图大小:3024 X 4032 得:长边像素:4032
缩放比:4032 / 1280 = 3(demo中设置取1280做为咱们能忍受的最大图片像素大小)
  实际缩放比:2(小于3且最接近3的2的幂方数)
  压缩后像素:3024 / 2 = 1512   4032 / 2= 2016 =>最终像素:1512 X 2016
缩放比+1=> 4032 / 1280 +1 = 4
  实际缩放比:4
  压缩后像素:3024 / 4 = 756  4032 / 4= 1008 =>最终像素:1512 X 4032
最终咱们可以保证获得一个像素小于目标最大像素的图片,但 是不能确保恰好等于目标最大像素。

  1. 质量压缩
    质量压缩改变的是透明度、位深等,不能改变加载出来的Bitmap占用的内存大小,但能切实改变磁盘占用大小
    核心代码就一句:
    Bitmap.compress(compressConfig.getComPressFormat(), quality, byteArrayOutputStream); 咱们就是经过修改quality参数达到压缩的目的,老规矩,看一下源码注释:
    真·人工智能翻译:
    向给定的输出流中写入一个压缩版本的bitmap.若是返回true,则能够经过将相应的inputstream传递给BitmapFactory.decodeStream()来重构bitmap。注意:并不是全部格式都直接支持全部bitmap配置,所以极可能从BitmapFactory返回的bitmap会有不一样的位深,而且/或者可能丢失每一个像素的alpha值(例如:JPEG只支持不透明像素)。
    @param quality:提示压缩器,取值0-100.0表明最小尺寸,100表明最大尺寸,一些诸如PNG这样无损的图片格式将会忽略质量设置。 好,看了注释没啥说的了,就是直接调用compress()压缩呗,quality递减。
/** * 质量压缩 */
    private void compressQuality(ImageInstance imageInstance, CompressConfig compressConfig) {
        SystemOut.println("ImageCompressor ===>compressQuality()");
        Bitmap inputBitmap = BitmapFactory.decodeFile(imageInstance.getInputPath());
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //上来就先压90%
        int quality = 90;
        inputBitmap.compress(compressConfig.getComPressFormat(), quality, byteArrayOutputStream);
        //若是压缩后图片仍是>targetSize,则继续压缩(整个过程是在线程池中开了线程处理的)
        while (byteArrayOutputStream.toByteArray().length > compressConfig.getTargetSize()) {
            byteArrayOutputStream.reset();
            quality -= 10;
            if (quality <= 10) {//为了缩短压缩次数,节约你们时间,每次质量比上次减小10%
                quality = 5;//限制最低压缩到5
            }
            inputBitmap.compress(compressConfig.getComPressFormat(), quality, byteArrayOutputStream);
            if (quality == 5) {
                //压缩结束
                inputBitmap.recycle();
                break;
            }
        }
    }
复制代码

ok~到此结束

相关文章
相关标签/搜索