圖片壓縮庫 compressor 學習

compressor 是一個 Android 平臺上的開源圖片壓縮庫,使用它,可以方便的對本地圖片進行壓縮,並提供了各種壓縮參數設置。

使用

val compressedImageFile = Compressor.compress(context, actualImageFile) {
    resolution(1280, 720)
    quality(80)
    format(Bitmap.CompressFormat.WEBP)
    size(2_097_152) // 2 MB
}

輸入一個正常的圖片文件,並設置壓縮質量,以及格式化類型,最大質量,輸出壓縮後的圖片文件。

對於這種語法,還不太清楚這是是 kotlin 的什麼語法。

核心

壓縮實現

通過 Bitmap 自身提供的 compress 方法進行壓縮,但是這個方法有一定的限制,具體看# 細節

bitmap.compress(format, quality, fileOutputStream)

壓縮參數組合

通過單例類 Compressor 的 compress 方法入口先指定目標壓縮文件,具體壓縮參數控制通過第四個參數 compressionPatch 控制,它有一個默認的實現 DefaultConstraint.default(),所以如果不指定其他設置,默認設置就會生效。

此外,當設置了自定義的壓縮參數設置,這些參數設置項都會保存在 Compression 的 constraints 集合中,這是一個圖片壓縮參數的抽象接口集合,然後遍歷參數集合,並調用的實現。

compression.constraints.forEach { constraint ->
    //該策略是否滿足條件
    while (constraint.isSatisfied(result).not()) {
        //如果不滿足,就進行處理
        result = constraint.satisfy(result)
    }
}

這樣每一個壓縮參數的實現結果,都會作爲接下來壓縮參數的輸入,從而達到鏈式調用的效果,一步一步,讓所有的參數設置在一個圖片源文件上生效。

壓縮參數接口

Constraint 接口是該庫的核心,也是一個很巧妙的設計。

通常來講,對於圖片壓縮,我們可以按照面向過程的思想,只需要定義一個方法,然後在方法中對圖片壓縮質量、壓縮格式、輸出位置等按個進行處理,最終進行壓縮,這樣代碼邏輯就會集中在一塊裏,這樣的設計對後續代碼的維護並不好,而且不具備模塊性,整個是一個大塊,看着也不是很優雅。

該庫通過 Constraint 的接口很優雅的解決了這個問題。

不同的壓縮參數,自己去實現自己的壓縮方案,這個接口提供了兩個方法:

 interface Constraint {
    fun isSatisfied(imageFile: File): Boolean
    fun satisfy(imageFile: File): File
}

第一個方法是 isSatisfied,它用於判斷當前圖片文件是否已經滿足參數設置條件,如果已經滿足,就不執行 satisfy 方法,否則就執行 satisfy 方法,satisfy 方法用於具體的壓縮選項設置。

比如 FormatConstraint 的實現,這是指定壓縮格式的實現類,如果當前圖片已經是指定的格式,就進行處理,否則不處理。

class FormatConstraint(private val format: Bitmap.CompressFormat) : Constraint {
    override fun isSatisfied(imageFile: File): Boolean {
        return format == imageFile.compressFormat()
    }

    override fun satisfy(imageFile: File): File {
        return overWrite(imageFile, loadBitmap(imageFile), format)
    }
}

這裏當檢測到當前圖片的格式不是指定的格式,就會執行 satisfy 方法,satisfy 方法中執行具體的壓縮,縱觀其他幾個參數策略的實現,它們大都是通過 overWriter 去進行具體的圖片參數設置。

overWrite 的實現

  • 檢查圖片格式是否跟指定格式一致,否則更改圖片名稱後綴
  • 刪除臨時的本地圖片文件
  • 使用新參數對 Bitmap 進行壓縮、處理,並保存到新的臨時文件並返回

代碼如下所示:

fun overWrite(imageFile: File, bitmap: Bitmap, format: Bitmap.CompressFormat = imageFile.compressFormat(), quality: Int = 100): File {
    val result = if (format == imageFile.compressFormat()) {
        imageFile
    } else {
        File("${imageFile.absolutePath.substringBeforeLast(".")}.${format.extension()}")
    }
    imageFile.delete()
    saveBitmap(bitmap, result, format, quality)
    return result
}

saveBitmap 方法具體就是調用 Bitmap 的 compress 方法進行壓縮。

拆分

  • Constraint 壓縮參數設置的抽象接口,每一種壓縮策略必須實現該接口
  • Compressor 壓縮門面類,入口類,只提供一個 方法,用於讓調用者設置不同的壓縮選項,並啓動壓縮。
    • Compression 一個用於盛放不同 Constraint 的集合
  • 不同壓縮策略的實現類
    • DefaultConstraint 默認壓縮參數的實現
    • DestinationConstraint 指定壓縮文件輸出的文件位置
    • FormatConstraint 指定文件最終輸出的壓縮格式
    • QualityConstraint 指定壓縮質量
    • ResolutionConstraint 指定圖片寬高值
    • SizeConstraint 指定圖片最終的壓縮大小

細節

  • 壓縮質量設置對 PNG圖片無效。

這是由於 Bitmap 自身的壓縮限制,它提供的 compress 方法,即使設置了壓縮質量,但是對 PNG 格式無效。

from Bitmap#compress 參數介紹

  • 如何實現指定大小的壓縮 #SizeConstraint

設置文件最大質量,如果當前文件大小大於最大質量,則繼續進行壓縮,具體通過設置圖片採樣率進行壓縮,並設置最低採樣率爲10,另外設置了壓縮次數,如果超過了指定的壓縮次數,還沒有達到大小,則不再壓縮,技即使圖片質量還沒有達到目標值。

不足

從上面 overWrite 方法的實現可以看到,每一次壓縮參數的生效,都會伴隨上一個緩存文件的刪除,以及下一個臨時文件的生成,這樣可能導致壓縮會產生比較多的 IO 開銷。

但這是一種博弈,這樣的好處,是把不同的壓縮參數實現拆分到不同的模塊類,讓代碼結構更清晰,而且在使用過程中( 自己之前開發的手機圖牀軟件-咕咚雲圖,使用了該壓縮庫),並沒有發現 IO 開銷導致什麼問題,所以這塊還好。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章