本文為您介紹如何配置Android用戶端的相關依賴,以便支援AVIF和HEIC圖片解碼。
Android接入HEIC解碼
HEIC是一種高效的壓縮圖片格式,在相同品質的前提下,HEIC檔案大小約為JPEG的40%。
阿里雲高效能HEIC解碼庫在開源的libheif和libde265基礎上進行二次開發,針對ARM平台做了大量最佳化,參考了其它同類開源庫優點,在解碼鏈路上的各個環節進行最佳化,顯著地提升了運行速度。相比原始的開源版本,解碼的效率有數倍的提升。阿里雲高效能HEIC解碼庫代碼連結,請參見Github。
Android用戶端接入
通過Android用戶端接入HEIC解碼步驟如下:
在build.gradle的repositories節點中添加阿里雲Maven倉庫配置。
maven { url 'https://maven.aliyun.com/repository/public' }在build.gradle的dependencies節點添加依賴。
implementation 'com.aliyun:libheif:1.0.0'
混淆配置
如果開啟混淆需配置,添加如下代碼以保證功能正常使用。
-keep class com.aliyun.libheif.**解碼HEIC圖片
情境一:直接使用API介面解碼
/** * decode HEIC file to RGBA format * @param length the length of effective file memory * @param filebuf file pointer in memory * @return rgba byte, convenient to create a {@link android.graphics.Bitmap} */ public static native boolean toRgba(long length, byte[] fileBuf, Bitmap bitmap); /** * judge file is heic format or not * @param length the length of effective file memory * @param filebuf file pointer in memory * @return bool, if true, the format is heif; * if false, the fromat is not heif */ public static native boolean isHeic(long length, byte[] fileBuf); /** * get info from heic picture * @param HeifInfo info * @param length the length of effective file memory * @param filebuf file pointer in memory * @return bool, if true, outSize is valid; * if false, outSize is not valid */ public static native boolean getInfo(HeifInfo info, long length, byte[] fileBuf);以下樣本展示了如何將
asset目錄下的test.heic解碼成Bitmap。val image = findViewById<ImageView>(R.id.image); val inputStream = assets.open("test.heic") val buffer = ByteArray(8192) var bytesRead: Int val output = ByteArrayOutputStream() while (inputStream.read(buffer).also { bytesRead = it } != -1) { output.write(buffer, 0, bytesRead) } val heifInfo = HeifInfo() val fileBuffer: ByteArray = output.toByteArray() HeifNative.getInfo(heifInfo, fileBuffer.size.toLong(), fileBuffer) // 單幀圖片,擷取frameList的第一個元素即圖片高寬。 val heifSize = heifInfo.frameList.first() // 根據高寬建立好圖片。 val bitmap = Bitmap.createBitmap( heifSize.width, heifSize.height, Bitmap.Config.ARGB_8888) // 解碼。 val heifBuffer = HeifNative.toRgba(fileBuffer.size.toLong(), fileBuffer, bitmap) image.setImageBitmap(bitmap)情境二:使用Glide整合解碼庫解碼
Glide是一個知名開源的圖片緩衝庫。更多資訊,請參見Glide官方文檔。
build.gradle中接入Glide。
implementation 'com.aliyun:libheif:1.0.0' implementation 'com.github.bumptech.glide:glide:4.13.2' implementation 'com.github.bumptech.glide:compiler:4.13.2'自訂HEIF解碼器,在decode方法中接入阿里雲高效能HEIC解碼庫,具體可以參考Glide官方文檔中自訂群組件模組。
class HeifByteBufferBitmapDecoder(bitmapPool: BitmapPool): ResourceDecoder<ByteBuffer, Bitmap> { private val bitmapPool: BitmapPool init { this.bitmapPool = Preconditions.checkNotNull(bitmapPool) } override fun handles(source: ByteBuffer, options: Options): Boolean { val buffer = ByteBufferUtil.toBytes(source) return HeifNative.isHeic(buffer.size.toLong(), buffer); } override fun decode( source: ByteBuffer, width: Int, height: Int, options: Options ): Resource<Bitmap>? { val buffer = ByteBufferUtil.toBytes(source) var heifInfo = HeifInfo() HeifNative.getInfo(heifInfo, buffer.size.toLong(), buffer) val heifSize = heifInfo.frameList[0] val bitmap = Bitmap.createBitmap(heifSize.width, heifSize.height, Bitmap.Config.ARGB_8888) HeifNative.toRgba(buffer.size.toLong(), buffer, bitmap) return BitmapResource.obtain(bitmap, bitmapPool) } }註冊解碼器Module。
@GlideModule(glideName = "HeifGlide") open class HeifGlideModule : LibraryGlideModule() { override fun registerComponents( context: Context, glide: Glide, registry: Registry ) { val byteBufferBitmapDecoder = HeifByteBufferBitmapDecoder(glide.bitmapPool) registry.prepend( ByteBuffer::class.java, Bitmap::class.java, byteBufferBitmapDecoder ) } }載入顯示HEIF圖片。
fun loadHeif(context: Context, imageView: ImageView, file: File) { Glide.with(context).asBitmap().load(file).into(imageView) }
情境三:使用Fresco整合解碼庫解碼
Fresco是Facebook開源的一個知名Android圖片緩衝庫。更多資訊,請參見Fresco官方文檔。
在build.gradle中接入Fresco。
implementation 'com.aliyun:libheif:1.0.0' implementation 'com.facebook.fresco:fresco:2.6.0'建立自訂HEIF解碼器,在decode方法中接入阿里雲高效能HEIC解碼庫。
class FrescoHeifDecoder: ImageDecoder { private var mFile: File? = null private var mUri: Uri? = null constructor(file: File) { mFile = file } constructor(uri: Uri) { mUri = uri } override fun decode( encodedImage: EncodedImage, length: Int, qualityInfo: QualityInfo, options: ImageDecodeOptions ): CloseableImage? { var imageRequest: ImageRequest? = null if (mFile != null) { imageRequest = ImageRequest.fromFile(mFile) } if (mUri != null) { imageRequest = ImageRequest.fromUri(mUri) } try { val cacheKey = DefaultCacheKeyFactory.getInstance() .getEncodedCacheKey(imageRequest, null) val fileCache = ImagePipelineFactory.getInstance() .mainFileCache val resource = fileCache.getResource(cacheKey) val file: File = if (resource == null) { mFile!! } else { (resource as FileBinaryResource).file } val bufferFromFile = ByteBufferUtil.fromFile(file) val bytes = ByteBufferUtil.toBytes(bufferFromFile) val heifInfo = HeifInfo() HeifNative.getInfo(heifInfo, file.length(), bytes) val heifSize = heifInfo.frameList[0] val bitmap = Bitmap.createBitmap(heifSize.width, heifSize.height, Bitmap.Config.ARGB_8888) HeifNative.toRgba(file.length(), bytes, bitmap) return CloseableStaticBitmap( pinBitmap(bitmap), qualityInfo, encodedImage.rotationAngle, encodedImage.exifOrientation ) } catch (e: Exception) { } return null } private fun pinBitmap(bitmap: Bitmap?): CloseableReference<Bitmap>? { return CloseableReference.of(Preconditions.checkNotNull(bitmap), BitmapCounterProvider.get().releaser) } }接入阿里雲高效能HEIC解碼庫時,調用了ByteBufferUtil參考類,具體如下:
object ByteBufferUtil { @Throws(IOException::class) fun fromFile(file: File): ByteBuffer { var raf: RandomAccessFile? = null var channel: FileChannel? = null return try { val fileLength = file.length() if (fileLength > Int.MAX_VALUE) { throw IOException("File too large to map into memory") } if (fileLength == 0L) { throw IOException("File unsuitable for memory mapping") } raf = RandomAccessFile(file, "r") channel = raf.channel channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength).load() } finally { if (channel != null) { try { channel.close() } catch (e: IOException) { } } if (raf != null) { try { raf.close() } catch (e: IOException) { } } } } fun toBytes(byteBuffer: ByteBuffer): ByteArray { val result: ByteArray val safeArray = getSafeArray(byteBuffer) if (safeArray != null && safeArray.offset == 0 && safeArray.limit == safeArray.data.size) { result = byteBuffer.array() } else { val toCopy = byteBuffer.asReadOnlyBuffer() result = ByteArray(toCopy.limit()) rewind(toCopy) toCopy[result] } return result } private fun rewind(buffer: ByteBuffer): ByteBuffer { return buffer.position(0) as ByteBuffer } private fun getSafeArray(byteBuffer: ByteBuffer): SafeArray? { return if (!byteBuffer.isReadOnly && byteBuffer.hasArray()) { SafeArray(byteBuffer.array(), byteBuffer.arrayOffset(), byteBuffer.limit()) } else null } internal class SafeArray (val data: ByteArray, val offset: Int, val limit: Int) }載入HEIF圖片。
fun loadHeif(simpleDraweeView: SimpleDraweeView, file: File) { val request = ImageRequestBuilder.newBuilderWithSource(Uri.fromFile(file)).setImageDecodeOptions( ImageDecodeOptions.newBuilder().setCustomImageDecoder(FrescoHeifDecoder(file)) .build() ).build() val controller = Fresco.newDraweeControllerBuilder() .setImageRequest(request).build() simpleDraweeView.controller = controller }
Android接入AVIF解碼
AVIF是一種基於AV1視頻編碼的新映像格式,相比JPEG和WebP,壓縮率更高,畫面細節更好。AVIF檔案大小約為同品質JPEG檔案的35%。
AVIF解碼可採用官方解碼庫libavif,已封裝Android介面,代碼在android_jni目錄,更多資訊請參見Github。
AVIF整合解碼庫
在build.gradle中添加以下依賴:
implementation'org.aomedia.avif.android:avif:0.11.1.3c786d2'直接解碼
調用以下方法解碼ByteBuffer:
/**
* Decodes the AVIF image into the bitmap.
*
* @param encoded The encoded AVIF image. encoded.position() must be 0.
* @param length Length of the encoded buffer.
* @param bitmap The decoded pixels will be copied into the bitmap.
* @param threads Number of threads to be used for the AVIF decode. Zero means use number of CPU
* cores as the thread count. Negative values are invalid. When this value is > 0, it is
* simply mapped to the maxThreads parameter in libavif. For more details, see the
* documentation for maxThreads variable in avif.h.
* @return true on success and false on failure. A few possible reasons for failure are: 1) Input
* was not valid AVIF. 2) Bitmap was not large enough to store the decoded image. 3) Negative
* value was passed for the threads parameter.
*/
public static native boolean decode(ByteBuffer encoded, int length, Bitmap bitmap, int threads);建議將threads參數設為1(單線程解碼)。若圖片編碼使用了AV1 tiles特性,開啟多線程可提升效率。更多資訊請參見Github。
Glide整合解碼庫
Glide自訂Module已支援AVIF,源碼請參見Github。
從4.13.0版本開始支援,添加以下依賴即可:
請確保版本號碼為4.13.0及以上版本。
implementation "com.github.bumptech.glide:avif-integration:4.13.2" Fresco整合解碼庫
Fresco整合解碼庫請參見自訂解碼器相關文檔。