目前市面上的成熟的APP,其用戶體系中均存在 設置頭像
的功能,考慮到尺寸規範問題,通常會加入 圖片裁剪
功能; 考慮到頁面UI統一度問題,甚至會在應用內實現 相冊
功能。據此推斷:各位的項目中,會遇到 Heif格式圖片
須要兼容的需求。java
筆者目前參與的商業項目,也被市場要求對Heif圖片進行適配。這篇文章,記錄了我在這件事情上 折騰
的過程。android
好玩系列是我進行
新事物實踐
、嘗試創造
的記錄,瞭解更多git
HEIF格式的全名爲 High Efficiency Image File Format(高效率圖檔格式),是由動態圖像專家組(MPEG)在2013年推出的新格式,瞭解更多github
瞭解Heif整個項目markdown
測試文件app
筆者注:印象中,iOS系統大約在16年就全面支持這一類型的文件了,而Android大約是三年前,在Android P推出的時候,宣佈原生支持Heif文件框架
隨着市場上的Android機器已經大面積過渡到 Android Q
,從這一點看,確實到了該適配的階段了。ide
目標,至少實現Android P及其以上的適配,嘗試向更低版本適配oop
HEIF格式是基於 ISO Base Media File Format格式衍生出來的圖像封裝格式,因此它的文件格式一樣符合ISO Base Media File Format (ISO/IEC 14496-12)中的定義( ISOBMFF)。測試
文件中全部的數據都存儲在稱爲Box的數據塊結構中,每一個文件由若干個Box組成,每一個Box有本身的類型和長度。在一個Box中還能夠包含子Box,最終由一系列的Box組成完整的文件內容,結構以下圖所示,圖中每一個方塊即表明一個Box。
咱們常見的MP4文件一樣是ISOBMFF結構,因此HEIF文件結構和MP4文件結構基本一致,只是用到的Box類型有區別。
HEIF文件若是是單幅的靜態圖片的話,使用item的形式保存數據,全部item單獨解碼;若是保存的爲圖片序列的話,使用track的方式保存。
做者:金山視頻雲 連接:www.jianshu.com/p/b016d10a0… 來源:簡書 著做權歸做者全部
系統經過ContentProvider向其餘應用暴露圖片等內容信息。目前 還沒有查詢相關文檔
,未肯定 Android相冊向其餘應用提供了Heif文件查詢支持
經過查詢咱們獲得Heif文件的 主要的
擴展名爲 heic
、 heif
.
ContentResolver contentResolver = context.getContentResolver();
String sort = MediaStore.Images.Media.DATE_MODIFIED + " desc ";
String selection = MediaStore.Images.Media.MIME_TYPE + "=?";
String[] selectionArgs = new String[]{"image/heic"};
String[] projection = {MediaStore.Images.Media._ID, MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_MODIFIED};
Cursor cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sort
);
複製代碼
咱們在測試文件中只找到了 heic
, 就先只測一種。
咱們發現,系統支持的狀況下,是能夠查詢到數據的。PS,導入數據到手機後,最好重啓下
咱們忽略掉Android版本相應的適配問題,假定已經獲得了相應文件的 Uri, 項目中Glide-4.12.0版本已經處理了適配。
咱們去探索一下,是自行添加的解碼器,仍是依賴於系統API
ExifInterfaceImageHeaderParser 說起內容
/**
* Uses {@link ExifInterface} to parse orientation data.
*
* <p>ExifInterface supports the HEIF format on OMR1+. Glide's {@link DefaultImageHeaderParser}
* doesn't currently support HEIF. In the future we should reconcile these two classes, but for now
* this is a simple way to ensure that HEIF files are oriented correctly on platforms where they're
* supported.
*/
複製代碼
文檔中提到,系統版本 O_MR1+
中已經支持了 HEIF
,可是目前的 DefaultImageHeaderParser
還不支持,將來會綜合考慮這兩個類(系統Exif相關類和DefaultImageHeaderParser),但目前,這是一個簡單的方式,確保HEIF在受支持的平臺上被正確處理圖片方向。
// Right now we're only using this parser for HEIF images, which are only supported on OMR1+.
// If we need this for other file types, we should consider removing this restriction.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
registry.register(new ExifInterfaceImageHeaderParser());
}
複製代碼
目前僅用於解析HEIF文件的頭信息。
咱們知道,Glide加載是先獲取流,解析頭信息,利用對應的解碼器處理。而加載此類性質的圖片時,是先解碼爲Bitmap,在進行 裝飾
, 而Bitmap的解碼是利用了系統API,見BitmapFactory.
因此,若是項目中使用了Glide(彷佛高於4.10.0即具備功能,沒有仔細查閱),而手機也支持了HEIF,那麼應用就能夠支持Heif顯示了。
Glide官方對於 自定義解碼器
仍是持保守態度的。可是咱們要試一下,嘗試在Glide中接入Heif解碼器。
至此,咱們已經完成了基本目標:
ContentResolver
獲取 Heif格式的圖片Glide官方提供了支持,是一件值得慶幸的事情。
由於項目中僅使用了Glide,筆者沒有繼續對Fresco展開調研。而Fresco做爲一款優秀的圖片加載框架,而且有龐大的社區支持,盲目推測其亦實現了內部支持。
接下來展開向更低版本適配的嘗試。固然,這 僅限於
解碼、呈現環節,並不考慮 ContentProvider
, ContentResolver
在低版本上對於Heif格式文件的適配。
將官方測試數據集導入
支持Heif
的小米、華爲部分機型後,我發現部分圖片未被系統支持,提示文件損毀或者不受支持。
另外,衝着好玩,我值得折騰一下。
必須申明:下面的實踐只是從好玩角度出發的,並未考慮
健壯性和全場景覆蓋。
我計劃將Heif文件放入Assets資源,按照咱們對Glide的瞭解,其解碼路徑起始點是:android.content.res.AssetManager$AssetInputStream
@GlideModule
class CustomGlideModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
registry.register(object : ImageHeaderParser {
override fun getType(`is`: InputStream): ImageHeaderParser.ImageType {
return ImageHeaderParser.ImageType.UNKNOWN
}
override fun getType(byteBuffer: ByteBuffer): ImageHeaderParser.ImageType {
return ImageHeaderParser.ImageType.UNKNOWN
}
override fun getOrientation(`is`: InputStream, byteArrayPool: ArrayPool): Int {
return ImageHeaderParser.UNKNOWN_ORIENTATION
}
override fun getOrientation(byteBuffer: ByteBuffer, byteArrayPool: ArrayPool): Int {
return ImageHeaderParser.UNKNOWN_ORIENTATION
}
})
}
registry.prepend(
Registry.BUCKET_BITMAP,
InputStream::class.java, Bitmap::class.java, CustomBitmapDecoder(context, glide.bitmapPool)
)
}
}
複製代碼
這樣,咱們會獲得這樣一條解碼路徑:
DecodePath{
dataClass=class android.content.res.AssetManager$AssetInputStream,
decoders=[
osp.leobert.android.heifdemo.CustomBitmapDecoder@5c4ee9e,
com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder@1c1ed7f
],
transcoder=com.bumptech.glide.load.resource.transcode.BitmapDrawableTranscoder@529014c
}
複製代碼
接下來咱們須要考慮解碼器的接入。
Nokia的Heif庫:連接
草率了,通過一番源碼研讀,發現只有讀寫過程封裝,至關於只有 最基礎
的協議封包、拆包,想要真正在Android上使用,還有不少事情要處理。
咱們知道,Android P原生支持了Heif,查一下資料,其底層支持以下:
一番思考後,發現 成本過大
。
再附上 Glide 適用的Decoder:
class CustomBitmapDecoder(val context: Context, val bitmapPool: BitmapPool) : ResourceDecoder<InputStream, Bitmap> {
override fun handles(source: InputStream, options: Options): Boolean {
return true
}
@Throws(IOException::class)
fun toByteArray(input: InputStream): ByteArray? {
val output = ByteArrayOutputStream()
copy(input, output)
return output.toByteArray()
}
@Throws(IOException::class)
fun copy(input: InputStream, output: OutputStream): Int {
val count = copyLarge(input, output)
return if (count > 2147483647L) {
-1
} else count.toInt()
}
@Throws(IOException::class)
fun copyLarge(input: InputStream, output: OutputStream): Long {
val buffer = ByteArray(4096)
var count = 0L
var n = 0
while (-1 != input.read(buffer).also { n = it }) {
output.write(buffer, 0, n)
count += n.toLong()
}
return count
}
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<Bitmap>? {
val heif = HEIF()
try {
val byteArray = toByteArray(source)
// Load the file
heif.load(ByteArrayInputStream(byteArray))
// Get the primary image
val primaryImage = heif.primaryImage
// Check the type, assuming that it's a HEVC image
if (primaryImage is HEVCImageItem) {
// val decoderConfig = primaryImage.decoderConfig.config
val imageData = primaryImage.itemDataAsArray
// Feed the data to a decoder
// FIXME: 2021/3/23 find a decoder to generate Bitmap when not upon Android P
return BitmapResource.obtain(
BitmapFactory.decodeByteArray(imageData, 0, imageData.size),
bitmapPool
)
}
} // All exceptions thrown by the HEIF library are of the same type
// Check the error code to see what happened
catch (e: Exception) {
e.printStackTrace()
} finally {
heif.release()
}
return null
}
}
複製代碼
若是找到了一個解碼器,在Android P如下支持解碼或轉碼,封裝爲Bitmap,就 能夠在低版本上適配
了。固然還須要完成:適配全部可能的解碼路徑
,頭信息處理
工做。
此次嘗試, 以失敗了結
。
竟然翻車了,壓壓驚
力大磚飛?集成ImageMagick之類的庫,直接實現圖片轉碼,成本有點過大了,先不折騰。
本次實踐,咱們實現了基本目標,高級目標由於初步調研不充分以失敗了結,可是也增加了知識。