詳解Android Bitmap:關於你所要知道的一切

前言

在平時的 Android 開發中,與 Bitmap 打交道可以說是再常見不過的事了。我在寫這篇文章之前,對於 Bitmap 相關的一些東西總是模模糊糊,比如 Bitmap 的文件大小還有佔用內存大小的區別,還有對 Bitmap 壓縮的幾種方法各自的區別和通途是什麼,等等

在這篇文章中,我將會把在 Bitmap 中相關的知識點都一一介紹,如果你也是對 Bitmap 總是感覺模模糊糊的話, 相信你看完這篇文章後一定會有所收穫

目錄

一、Bitmap 的創建
二、Bitmap 的顏色配置信息與壓縮方式信息
三、Bitmap 的轉換與保存
四、Bitmap 的文件大小
五、Bitmap 佔用內存的大小
六、影響 Bitmap 佔用內存大小的因素
七、Bitmap 的加載優化與壓縮
八、Bitmap 的其他操作

一、Bitmap 的創建

我們如何創建一個 Bitamap 對象呢?Google 給我們提供了兩種方式:

  • Bitmap 的靜態方法 createBitmap(XX)

  • BitmapFactory 的 decodeXX 系列靜態方法

二、Bitmap 的顏色配置信息與壓縮方式信息

Bitmap 中有兩個內部枚舉類:Config 和 CompressFormat,Config 是用來設置顏色配置信息的,CompressFormat 是用來設置壓縮方式的

Config

Config 類描述了一個 Bitmap 是如何存儲像素信息的,它影響了圖片的質量(顏色深度)以及顯示透明/不透明顏色的能力

關於圖片的顏色格式,有幾點需要注意:

  1. Bitmap 默認的圖片格式是 ARGB_8888

  2. 圖片佔用內存的大小與圖片的顏色格式相關, 佔用內存的大小 = 圖片的寬度 × 圖片的高度 × 每個像素佔用的內存大小

  3. 當我們需要做性能優化或者防止 OOM 的時候,可以將 Bitamp 的顏色配置該爲 RGB_565 ,它的佔用內存大小是 ARGB_8888的一半 例如:

  val options = BitmapFactory.Options()
  options.inPreferredConfig = Bitmap.Config.RGB_565  // 設置bitmap的顏色格式
  val bitmap = BitmapFactory.decodeResource(resources, R.drawable.pic, options)

注意:RGB_565 是不支持透明度的,如果你需要顯示帶有透明度的圖片,不要用此格式

CompressFormat

CompressFormat 描述了將 Bitmap 以什麼方式壓縮,它有3個值:

例如:

  fun bitmapToByteArray(bitmap: Bitmap): ByteArray {
        val baos = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
        return baos.toByteArray()
    }

三、Bitmap 的保存和轉換

前面介紹瞭如何創建一個 Bitmap,當我們拿到一個 Bitmap 對象後,通常還有有以下操作:

1. 將 Bitamap 轉換爲 byte 數組

  fun bitmapToByteArray(bitmap: Bitmap): ByteArray {
        val baos = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
        return baos.toByteArray()
    }

2. 將 Bitamap 保存爲 文件

   fun bitmapToFile(bitmap: Bitmap, file: File): Boolean {
        val baos = ByteArrayOutputStream()
        val fileOutputStream = FileOutputStream(file)

        return try {
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
            fileOutputStream.write(baos.toByteArray())
            true
        } catch (e: Exception) {
            e.printStackTrace()
            false
        } finally {
            baos.close()
            fileOutputStream.close()
        }
    }

四、Bitmap 的文件大小

說到 Bitmap 大小這一塊的時候,我們一定要先搞清楚幾個概念:

  • Bitmap 原始的文件大小

  • 把一個 Bitamp 通過壓縮保存到本地的文件大小

  • Bitmap 加載到內存中佔用的內存大小

注意:通常情況下,這三個值不相等!

我們以一張 寬高爲 1080 * 1920 ,圖片原始大小爲 705 kb 的圖片爲例(本文均以此圖片爲例),逐個解釋和驗證這三個數據:

1. Bitmap 原始的文件大小

這個很好理解,就是圖片的自身的大小嘛,沒有經過任何處理,通過下圖我們可以看到,這張圖片的原始大小是 705 kb:

注意,如果我們直接在 Android Studio 中打開這張圖片的話,上面顯示的圖片大小是 721.96 kb,這個爲什麼不是 705 kb呢?
我個人的理解是,這個大小並不是圖片本身的大小,而是 圖片本身的大小 + 在 Android Studio 中佔用的一些信息
就好比在 Window 的截圖中的大小指的是圖片本身,下面還有一個佔用空間指的是在 Windows 中這張圖片佔用的磁盤空間大小

下面我們通過代碼驗證一下:

  1. 把圖片放到工程的 assets 目錄下

  2. 通過下面代碼加載圖片,然後打印出圖片的大小:

val bytes = assets.open("pic.jpg").readBytes()
log("原始文件大小 :${bytes.size / 1024} kb")

日誌輸出如下:
原始文件大小 :705 kb

2. Bitamp 通過壓縮保存到本地的文件大小

通過上面的驗證,我們知道,這張圖片的原始大小是 705 kb。如果我們把這張圖片保存到手機上,那麼它的大小還會是 705 kb 麼?

把這張圖片保存到手機上,分兩種情況:

1). 直接拿到圖片的輸入流或者說 byte 數組,然後保存到本地

  val bytes = assets.open("pic.jpg").readBytes()
  log("assets 中讀取的大小 : ${bytes.size / 1024} kb")

  val file = File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "pic.jpg")
  if (!file.exists()) {
       file.createNewFile()
     }

  FileUtils.writeToFile(bytes, file)
  log("保存到本地的圖片大小 ${file.readBytes().size / 1024} kb")


   // FileUtil 類中的 writeToFile 方法:
  fun writeToFile(data: ByteArray, file: File) {
      val fileOutputStream = FileOutputStream(file)
        try {
            fileOutputStream.write(data)
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            fileOutputStream.close()
        }
    }

日誌輸出如下:

assets 中讀取的大小 :705 kb
保存到本地的圖片大小 705 kb

然後我們再驗證一下保存的圖片信息:

2.) 創建一個 Bitmap,然後保存到本地

  val bytes = assets.open("pic.jpg").readBytes()
  log("assets 中讀取的大小 : ${bytes.size / 1024} kb")

  val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
  val baos = ByteArrayOutputStream()
  bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)  //壓縮圖片並將數據存儲到 ByteArrayOutputStream 中

  val file = File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "pic.jpg")
  if (!file.exists()) {
         file.createNewFile()
     }
  val fileOutputStream = FileOutputStream(file)
  fileOutputStream.write(baos.toByteArray())
        
  log("保存到本地的圖片大小 : ${file.readBytes().size / 1024} kb")

日誌輸出如下:

assets 中讀取的大小 :705 kb
保存到本地的圖片大小 :  817 kb

再查看一下保存的圖片信息:

到這裏,我們就會有疑問了,爲什麼通過 Bitmap 轉換之後圖片大小就不一樣了呢?

關鍵就在這一句,

 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)  //壓縮圖片並將數據存儲到 ByteArrayOutputStream 中

當我們需要把一個 Bitmap 對象保存到本地時,需要先將其轉換成 byte 數組,這個過程是通過 Bitmap 的 compress 方法完成的。

這個方法中第一個參數代表保存的圖片類型,第二個參數代表圖片的質量。這個值的範圍是 0~100,數值越大圖片質量越高,同時保存後的圖片大小也越大。

也就是說,當通過這個方法把一張 Bitmap 保存到本地時,第二參數控制了保存的圖片質量,同時也就影響了保存圖片的大小

3. Bitmap 加載到內存中佔用的內存大小

請看第五部分

小結

  • Bitmap 原始的文件大小 、Bitamp 壓縮保存到本地的大小、Bitmap 加載到內存中佔用的內存大小,這三者是三個不同的概念,且通常這三者並不相等

  • 將 Bitmap 保存到本地時,可以通過 compress 方法的第二個參數控制圖片的質量,從而達到控制圖片大小的目的。(用於圖片壓縮,後面會介紹)

五、Bitmap 佔用內存的大小

終於到了大家最最關心的點,Bitmap 佔用內存的大小!很多時候,我們只是朦朦朧朧的知道,加載大的圖片要注意,防止OOM。

但是,加載一張圖片到底佔用多少內存呢?

如何計算加載一張圖片到底佔用多少內存

來人,上公式:

總內存 = 寬 × 高 × 色彩空間

把上面的公式再詳細描述一下就是:

總內存 = 寬的像素數 × 高的像素數 × 每個像素點佔用的大小

這個公式也很好理解,寬 × 高 即圖片總共有多少像素點,然後乘 每個像素點佔用的大小 就得出了總內存。

Bitmap 中直接提供了相關方法得到圖片所佔用的內存大小:

  • getAllocationByteCount() // API 19 以後使用

  • getByteCount()

除了系統提供的方法,我們也可以根據上面的公式自己計算。

接下來我們就通過系統提供的方法和我們自己計算來驗證一下:

1). 從 assets 目錄中加載圖片,並計算佔用的內存大小:

// 加載圖片
val bytes = assets.open("pic.jpg").readBytes()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
ivPic.setImageBitmap(bitmap)

log("佔用內存大小: ${Bitmaps.getMemorySize(rawBitmap)} kb \n")
log("計算佔用內存大小: ${Bitmaps.calculateMemorySize(rawBitmap)} kb \n")


// 使用系統 api 提供的方法計算
// Bitmaps 中的 getMemorySize 方法
 fun getMemorySize(bitmap: Bitmap, sizeType: SizeType = SizeType.KB): Int {
        val bytes = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {  //Since API 19
            bitmap.allocationByteCount
        } else {
            bitmap.byteCount
        }

        return when (sizeType) {
            SizeType.B -> bytes
            SizeType.KB -> bytes / 1024
            SizeType.MB -> bytes / 1024 / 1024
            SizeType.GB -> bytes / 1024 / 1024 / 1024
        }
 }

// 根據公式手動計算
// Bitmaps 中的 calculateMemorySize方法
  fun calculateMemorySize(bitmap: Bitmap, sizeType: SizeType = SizeType.KB): Int {
        val pixels = bitmap.width * bitmap.height
        val bytes = when (bitmap.config) {
            Bitmap.Config.ALPHA_8 -> pixels * 1
            Bitmap.Config.ARGB_4444 -> pixels * 2
            Bitmap.Config.ARGB_8888 -> pixels * 4
            Bitmap.Config.RGB_565 -> pixels * 2
            else -> pixels * 4
        }

        return when (sizeType) {
            SizeType.B -> bytes
            SizeType.KB -> bytes / 1024
            SizeType.MB -> bytes / 1024 / 1024
            SizeType.GB -> bytes / 1024 / 1024 / 1024
        }
    }

    // 單位的枚舉類
    enum class SizeType {
        B,
        KB,
        MB,
        GB
    }

注:Bitmaps 是我自己定義的一個工具類,並不是系統的一個類。源碼在文章最下面

2). 計算結果如下:

我們可以看到,利用系統提供的 api 與 我們自己用公式計算得出的佔用內存大小是一樣的。

對於這張圖片來說,寬高爲 1080 * 1920,圖片的顏色格式是 ARGB_8888,證明每個像素佔用 4 個字節的內存,所以加載它佔用的內存就是:

總內存 = 寬 * 高 * 色彩空間 = 1080 * 1920 * 4 = 8294400 byte = 8100 KB = 7.9 MB

注:
1 byte = 8 bit
1 KB = 1024 byte
1 MB = 1024 KB
1 GB = 1024 MB

六、影響 Bitmap 佔用內存大小的因素

根據公式:

總內存 = 寬的像素數 × 高的像素數 × 每個像素點佔用的大小

可以得出,影響佔用內存的大小因素有:

  • 寬高

  • 色彩空間

所以,當我們需要對 Bitamp 加載進行優化的時候,就可以從這兩個方面進行着手:

  • 減少 Bitmap 的寬高

  • 使用佔用更少內存的色彩模式

除了上面兩點,還有一個因素也會影響到 Bitamp 佔用的內存大小,它就是 縮放

縮放

1. 什麼是縮放

根據前面幾部分的介紹,我們知道,加載一張 1080 * 1920 的圖片,然後通過 bitmap.getWidth() 和 bitmap.getHeight() 得到的也是 1080 * 1920

如果圖片的原始大小是 1080 * 1920,那邊加載出來的 Bitmap 對象也一定是 1080 * 1920 麼?

答案是否定的。在加載 Bitamp 對象時可以手動設置 inSampleSize 來進行縮放。另外,如果是從 Drawable 目錄下加載圖片的話,系統會默認地根據圖片所在的 Drawable 目錄以及手機的 DPI 對加載的圖片進行縮放

2. 縮放是如何影響影響佔用內存的

當我們對圖片進行縮放時,實際上造成的結果是圖片寬高的改變,通過上面的公式我們可以知道,寬高改變了,佔用的內存也就改變了。

所以圖片的縮放對內存的影響本質上還是寬高對佔用內存的影響

3. Bitmap 中如何對圖片進行縮放

1)、 手動設置縮放參數 當我們創建一個 Bitmap 對象的時候,會有一個可選的 Options 對象,其中的 inSampleSize 參數可以控制縮放的比例,inSampleSize 的值代表 圖片的寬度、高度分別變爲原來的 1/inSampleSize

比如一張 1080 * 1920 的圖片,如果加載時設置了 inSampleSize = 2,證明圖片的寬度變爲原來的 1/2,高度也變爲原來的 1/2,所以得到的 Bitmap 對象的寬高是 540 * 860

根據上面的佔用內存的計算公式,它佔用的內存大小就變爲原來的 1/2 * 1/2 = 1/4

下面我們來驗證一下,還是那張圖片,在加載的時候設置 inSampleSize = 2 ,然後看一下圖片的寬高和佔用內存的情況:

 val bytes = assets.open("pic.jpg").readBytes()

 val options = BitmapFactory.Options()
 ptions.inSampleSize = 2
 val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)

 ivPic.setImageBitmap(bitmap)
 showInfo(bitmap)

結果如下:

image

我們可以看到,圖片的寬高由原來的 1080 * 1920 變成了 540 * 960,寬高分別變爲原來的 1/2,佔用內存的大小由原來的 8100 變成了 2025,內存大小變爲了原來的 1/2 * 1/2 = 1/4

根據這個特性,我們可以在加載大圖的時候進行縮放處理,防止OOM的發生

注意,inSampleSize 的值要求必須大於1,且只能是2的整數倍

2)、 從 Drawable 目錄中加載圖片的自動縮放當我們從 assets 目錄中或者網絡上加載一張圖片的時候,默認情況下得到的 Bitmap 對象的寬高是與原圖片的寬高一致的。比如前面的我們舉的例子,寬高都是 1080 * 1920

如果我們從 Drawable 目錄下加載圖片的話,系統會根據圖片所在的目錄以及手機的DPI對圖片進行縮放

下面是手機 dpi 與 Drawable 目錄的對應關係圖:

Drawable 目錄的選擇流程

當我們從 Drawable 目錄中加載一張圖片的時候:

  1. 比如在一箇中等分辨率的手機上,Android 就會選擇d rawable-mdpi 文件夾下的圖片,文件夾下有這張圖就會優先被使用,在這種情況下,圖片是不會被縮放的

  2. 但是如果沒有在 drawable-mdpi 的文件夾下找到相應圖片的話, Android 系統會首先從更高一級的 drawable-hdpi 文件夾中查找, 如果找到圖片資源就進行縮放處理(縮小),顯示在屏幕上

  3. 如果 drawable-hdpi 文件夾下也沒有的話,就依次往 drawable-xhdp i文件夾、drawable-xxhdpi 文件夾、 drawable-xxxhdpi 文件夾、drawable-nodpi 文件夾中尋找

  4. 如果更高密度的文件夾裏都沒有找到,就往更低密度的文件夾 drawable-ldpi 文件夾下查找。如果找到圖片資源就進行縮放處理(放大),顯示在屏幕上

  5. 如果都沒找到,最終會在默認的drawable文件夾中尋找,如果默認的drawable文件夾中也沒有那就會報錯啦

Drawable 縮放規則小結

  • 如果圖片所在的文件夾 dpi 剛好是手機屏幕密度所對應的文件夾(比如:手機 dpi 爲 xxhdpi,圖片在 drawable-xxhdpi 文件夾中), 則該圖片不會被壓縮

  • 如果圖片所在目錄 dpi 低於匹配目錄,那麼該圖片被認爲是爲低密度設備需要的,現在要顯示在高密度設備上,圖片會被放大,寬和高,以及佔用的內存都會變大

注意:如果圖片本身就比較大,而又放在了密度較低的文件夾中, 加載時會導致佔用內存變得非常大,導致OOM

  • 如果圖片所在目錄 dpi 高於匹配目錄,那麼該圖片被認爲是爲高密度設備需要的,現在要顯示在低密度設備上,圖片會被縮小,寬和高,以及佔用的內存都會變小

  • 如果圖片所在目錄爲 drawable-nodpi,則無論設備 dpi 爲多少,保留原圖片大小,不進行縮放

驗證以我的手機爲例,屏幕分辨率是 1080 * 1920,DPI 是 480,對應的 Drawable 目錄是 drawable-xxhdpi(超超高密度)

  1. 把圖片拷貝到 drawable-xxhdpi 目錄下,然後加載圖片並顯示其信息

 val bitmap = BitmapFactory.decodeResource(resources, R.drawable.pic)

ivPic.setImageBitmap(bitmap)
showInfo(bitmap)  //顯示圖片信息

根據上面的介紹的規則,我們加載圖片所對應的 Drawable 與我們的手機 DPI 相匹配,所以圖片不會進行縮放

2.把圖片放拷貝到 drawable-xxxhdpi 目錄下(高於手機DPI),然後加載圖片並顯示其信息,此時圖片會被縮小

3.把圖片放拷貝到 drawable-xhdpi 目錄下(低於手機DPI),然後加載圖片並顯示其信息,此時圖片會被放大

注意:
在做測試的時候,要保證同時只有一個 drawable 文件夾中存在需要加載的那張圖片

4. 小結

  • 總內存 = 寬的像素數 × 高的像素數 × 每個像素點佔用的大小

  • 由以上公式可以知道影響內存佔用大小的因素是 寬高和色彩空間

  • 加載 一個 Bitamap 可以通過設置 inSampleSize 的值控制加載得到的圖片的大小

  • 從 Drawable 目錄中加載圖片時,系統會根據手機 DPI 和 Drawable 目錄對圖片進行縮放

七、Bitmap 的加載優化與壓縮

1. 質量壓縮

    /**
     * 將圖片 [bitmap] 壓縮到指定大小 [targetSize] 以內 ,單位是 kb
     * 這裏的大小指的是 “文件大小”,而不是 “內存大小”
     **/
   fun compressQuality(bitmap: Bitmap, targetSize: Int, declineQuality: Int = 10): ByteArray {

        val baos = ByteArrayOutputStream()

        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
        log("壓縮前文件大小:${baos.toByteArray().size / 1024} kb")

        var quality = 100
        while ((baos.toByteArray().size / 1024) > targetSize) {
            baos.reset()
            quality -= declineQuality
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos)
        }

        log("壓縮後文件大小:${baos.toByteArray().size / 1024} kb")

        return baos.toByteArray()
    }
  1. 質量壓縮不會減少圖片的像素,它是在保持像素的前提下改變圖片的位深及透明度,來達到壓縮圖片的目的

  2. 壓縮後圖片的長,寬,像素都不會改變,那麼 bitmap 所佔內存大小是不會變的

  3. 由於圖片的質量變低了,所以壓縮後圖片的大小會變小

  4. 質量壓縮 png 格式這種圖片沒有作用,因爲 png 是無損壓縮

2. 採樣率壓縮

  /**
   * 將圖片 [byteArray] 壓縮到 寬度小於 [targetWidth]、高度小於 [targetHeight]
   *
   **/
  fun compressInSampleSize(byteArray: ByteArray, targetWidth: Int, targetHeight: Int): ByteArray {

        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)

        var inSampleSize = 1
        while (options.outWidth / inSampleSize > targetWidth || options.outHeight / inSampleSize > targetHeight) {
            inSampleSize *= 2
        }

        options.inJustDecodeBounds = false
        options.inSampleSize = inSampleSize
        val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)

        val compressedByreArray = bitmapToByteArray(bitmap)

        log("壓縮前文件大小 :${byteArray.size / 1024} kb")
        log("採樣率 :$inSampleSize ")
        log("壓縮後文件大小 :${compressedByreArray.size / 1024} kb")

        return compressedByreArray
    }
  1. 採樣率壓縮其原理是縮放 bitmap 的尺寸

  2. 壓縮後圖片的 寬度、高度以及佔用的內存都會變小,文件大小也會變小(指壓縮後保存到本地的文件)

  3. 採樣率 inSampleSize 代表 寬度、高度變爲原來的幾分之一, 比如 inSampleSize 爲 2,代表 寬度、高度都變爲原來的 1/2,佔用的內存就會變爲原來的 1/4

  4. 採樣率 inSampleSize 只能爲 2 的整次冪,比如:2、4、8、16 ...

  5. 由於 inSampleSize 只能爲 2 的整次冪,所以無法精確控制大小

3. 縮放壓縮

    /**
     * 將圖片 [bitmap] 壓縮到指定寬高範圍內
    **/
    fun compressScale(bitmap: Bitmap, targetWidth: Int, targetHeight: Int): Bitmap {
        return try {
            val scale = Math.min(targetWidth * 1.0f / bitmap.width, targetHeight * 1.0f / bitmap.height)

            val matrix = Matrix()
            matrix.setScale(scale, scale)

            val scaledBitmap = Bitmap.createScaledBitmap(bitmap, (bitmap.width * scale).toInt(), (bitmap.height * scale).toInt(), true)

            val rawBytes = bitmapToByteArray(bitmap)
            val scaledBytes = bitmapToByteArray(scaledBitmap)
            log("壓縮前文件大小 :${rawBytes.size / 1024} kb")
            log("縮放率 :$scale ")
            log("壓縮後文件大小 :${scaledBytes.size / 1024} kb")

            scaledBitmap
        } catch (e: Exception) {
            e.printStackTrace()
            bitmap
        }
    }
  1. 放縮法壓縮使用的是通過矩陣對圖片進行縮放

  2. 縮放後圖片的 寬度、高度以及佔用的內存都會變小,文件大小也會變小(指壓縮後保存到本地的文件,原始文件不會改變)

4. 色彩模式壓縮(RGB565)

    /**
     * 將圖片格式更改爲 Bitmap.Config.RGB_565,減少圖片佔用的內存大小
    **/
    fun compressRGB565(byteArray: ByteArray): Bitmap {

        return try {
            val options = BitmapFactory.Options()
            options.inPreferredConfig = Bitmap.Config.RGB_565
            val compressedBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)

            log("壓縮前文件大小 :${byteArray.size / 1024} kb")
            log("壓縮後文件大小 :${byteArray.size / 1024} kb")
            compressedBitmap
        } catch (e: Exception) {
            e.printStackTrace()
            BitmapFactory.decodeByteArray(ByteArray(0), 0, 0)
        }
    }
  1. 由於圖片的存儲格式改變,與 ARGB_8888 相比,每個像素的佔用的字節由 8 變爲 4 , 所以圖片佔用的內存也爲原來的一半

  2. 圖片的寬高不發生變化

  3. 如果圖片不包含透明信息的話,可以使用此方法進行壓縮

八、Bitmap 的其他操作

1. 旋轉

    /**
     * 旋轉
     *
     * 注意:如果 [degree] 不是90的倍數的話,會導致旋轉後圖片變成"斜的",
     * 然而此時計算圖片的寬高時仍然是按照水平和豎直方向計算,所以會導致最終旋轉後的圖片變大
     * 如果進行多次旋轉的話,最終會出現OMM
     */
    fun rotate(bitmap: Bitmap, degree: Float): Bitmap {
        val matrix = Matrix()
        matrix.postRotate(degree)
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, false)
    }

2. 鏡像

    /**
     * 水平鏡像
     */
    fun mirrorX(bitmap: Bitmap): Bitmap {
        val matrix = Matrix()
        matrix.setScale(-1f, 1f)
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, false)
    }

    /**
     * 豎直鏡像
     */
    fun mirrorY(bitmap: Bitmap): Bitmap {
        val matrix = Matrix()
        matrix.setScale(1f, -1f)
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, false)
    }

3. 裁切

  /**
     * 從圖片中間位置裁剪出一個寬高爲的 [width] [height]圖片
     */
    fun crop(bitmap: Bitmap, width: Int, height: Int): Bitmap {
        return if (bitmap.width < width || bitmap.height < height) {
            bitmap
        } else {
            Bitmap.createBitmap(bitmap, (bitmap.width - width) / 2, (bitmap.height - height) / 2, width, height)
        }
    }

    /**
     * 從圖片中間位置裁剪出一個半徑爲 [radius] 的圓形圖片
     */
    fun cropCircle(bitmap: Bitmap, radius: Int): Bitmap {

        val realRadius: Int = if (bitmap.width / 2 < radius || bitmap.height / 2 < radius) {
            Math.min(bitmap.width, bitmap.height) / 2
        } else {
            radius
        }

        val src = crop(bitmap, realRadius * 2, realRadius * 2)
        val circle = Bitmap.createBitmap(src.width, src.height, Bitmap.Config.ARGB_8888)

        val canvas = Canvas(circle)
        canvas.drawARGB(0, 0, 0, 0)
        val paint = Paint()
        paint.isAntiAlias = true

        canvas.drawCircle((circle.width / 2).toFloat(), (circle.height / 2).toFloat(), realRadius.toFloat(), paint)

        val rect = Rect(0, 0, circle.width, circle.height)
        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
        canvas.drawBitmap(src, rect, rect, paint)

        return circle
    }

九、 總結

  1. Bitmap 的顏色配置,以及不同格式佔用內存的大小

  2. 注意區分 原始圖片大小、Bitmap 對象的大小(寬、高)、Bitmap 佔用內存的大小、將 Bitmap 保存成文件的大小

  3. Bitmap 佔用內存:總內存 = 寬的像素數 × 高的像素數 × 每個像素點佔用的大小

  4. Bitmap 的縮放和從 Drawable 目錄中加載圖片的規則

  5. Bitmap 的幾種壓縮方法和各自的特點

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