本文为您介绍如何配置Android客户端的相关依赖,以便支持AVIF和HEIC图片解码。
Android接入HEIC解码说明
高效率图像格式HEIF(High Efficiency Image Format )是一种压缩图片格式,通过使用更现代的压缩算法,在相同质量的前提下,HEIF文件大小是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:0.0.4'
混淆配置
如果开启混淆需配置,需要添加如下代码以保证功能正常使用。
-keep class com.aliyun.libheif.**
解码HEIC图片
场景一:直接使用API接口解码
/** * judge file is heic format or not * @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:0.0.4' 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:0.0.4' 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,Wep等图片格式压缩率更高,并且画面细节更好。AVIF通过使用更现代的压缩算法,在相同质量的前提下,AVIF文件大小是JPEG文件的35%左右。
AVIF的解码可以用采用官方解码库libavif。libavif已经封装好了Android的相关接口,相关代码在android_jni目录下。关于libavif代码的更多信息,请参见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版本开始支持,只需要添加如下依赖即可使Glide支持AVIF解码。
请确保版本号为4.13.0及以上版本。
implementation "com.github.bumptech.glide:avif-integration:4.13.2"
Fresco集成解码库
关于Fresco如何集成解码库,请参见自定义解码器相关文档。