徒手擼一個框架-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~到此結束

相關文章
相關標籤/搜索