目前市面上的成熟的APP,其用户体系中均存在 设置头像 的功能,考虑到尺寸规范问题,一般会加入 图片裁剪 功能;
考虑到页面UI统一度问题,甚至会在应用内实现 相册功能。据此推断:各位的项目中,会遇到 Heif格式图片 需要兼容的需求。
笔者目前参与的商业项目,也被市场要求对Heif图片进行适配。这篇文章,记录了我在这件事情上 折腾 的过程。
好玩系列是我进行 新事物实践 、 尝试创造 的记录,了解更多
背景HEIF格式的全名为 High Efficiency Image File Format(高效率图档格式),是由动态图像专家组(MPEG)在2013年推出的新格式,了解更多
了解Heif整个项目
测试文件
笔者注:印象中,iOS系统大约在16年就全面支持这一类型的文件了,而Android大约是三年前,在Android P推出的时候,宣布原生支持Heif文件
随着市场上的Android机器已经大面积过渡到 Android Q,从这一点看,确实到了该适配的阶段了。
目标,至少实现Android P及其以上的适配,尝试向更低版本适配
ISO Base Media File FormatHEIF格式是基于 ISO Base Media File Format格式衍生出来的图像封装格式,所以它的文件格式同样符合ISO Base Media File Format (ISO/IEC 14496-12)中的定义( ISOBMFF)。
文件中所有的数据都存储在称为Box的数据块结构中,每个文件由若干个Box组成,每个Box有自己的类型和长度。在一个Box中还可以包含子Box,最终由一系列的Box组成完整的文件内容,结构如下图所示,图中每个方块即代表一个Box。
便宜美国vps我们常见的MP4文件同样是ISOBMFF结构,所以HEIF文件结构和MP4文件结构基本一致,只是用到的Box类型有区别。
HEIF文件如果是单幅的静态图片的话,使用item的形式保存数据,所有item单独解码;如果保存的为图片序列的话,使用track的方式保存。
作者:金山视频云
链接:https://www.jianshu.com/p/b016d10a087d
来源:简书
著作权归作者所有
系统通过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在受支持的平台上被正确处理图片方向。
Glide 类中提及的内容
// 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已有的实现,直接在支持的平台版本上解码、构建Bitmap、构建相应Drawable、呈现。Glide官方提供了支持,是一件值得庆幸的事情。
因为项目中仅使用了Glide,笔者没有继续对Fresco展开调研。而Fresco作为一款优秀的图片加载框架,并且有庞大的社区支持,盲目推测其亦实现了内部支持。
接下来展开向更低版本适配的尝试。当然,这 仅限于 解码、呈现环节,并不考虑 ContentProvider, ContentResolver 在低版本上对于Heif格式文件的适配。
尝试向Glide接入Heif解码器将官方测试数据集导入 支持Heif 的小米、华为部分机型后,我发现部分图片未被系统支持,提示文件损毁或者不受支持。
另外,冲着好玩,我值得折腾一下。
必须申明:下面的实践只是从好玩角度出发的,并未考虑 健壮性和全场景覆盖。
我计划将Heif文件放入Assets资源,按照我们对Glide的了解,其解码路径起始点是:android.content.res.AssetManager$AssetInputStream
@GlideModuleclass 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的SDKNokia的Heif库:链接
草率了,经过一番源码研读,发现只有读写过程封装,相当于只有 最基础 的协议封包、拆包,想要真正在Android上使用,还有很多事情要处理。
看下Android P我们知道,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之类的库,直接实现图片转码,成本有点过大了,先不折腾。
本次实践,我们实现了基本目标,高级目标因为初步调研不充分以失败告终,但是也增长了知识。
73624242