1# HEIF软解码器案例 2 3### 介绍 4 5高效图像文件格式HEIF(High Efficiency Image File Format)具有更高的图像质量,更高效的压缩算法,在应用中大量使用,有些应用静态图90%以上都是HEIF图片(.heic后缀)。本示例介绍将[libheif](https://github.com/strukturag/libheif)编译移植到鸿蒙平台,通过网络库[curl](https://curl.se/)请求HEIF图片资源、libheif软解码HEIF图片,最后在瀑布流中加载解码后的HEIF图片的过程。本案例HEIF软解码方案可供开发者调试,硬解码不支持的设备也可以使用软解码方案。 6 7### 效果图预览 8 9 10 11进入本案例首页,开始加载HEIF网络图片的过程:在Taskpool子线程中,通过libcurl网络请求HEIF图片资源,然后通过libheif软解码出HEIF图片数据,调用OH_PixelMap_CreatePixelMap将HEIF图片数据转为PixelMap,主线程获取图片软解码结果后在WaterFlow中加载所有HEIF图片。 12### 实现思路 13 14对于jpg、png和jpeg等系统支持硬解码的图片格式,开发者使用imageSource即可直接解码,创建PixelMap对象后,可以给上层UI组件展示。理论上,对于.heic这类系统暂不支持的图片格式,需要移植自定义解码器(二方库或者三四方库)到鸿蒙平台,然后调用解码器接口解码出原始像素数据,进行字节对齐、颜色变换等操作后,调用NDK的OH_PixelMap_CreatePixelMap函数即可创建PixelMap,给上层UI组件展示。 15 16### 实现方案 17 18#### 1.libheif移植 19 20本文在Linux平台上编译libheif,下载sdk-linux并配置好环境变量,然后按照文档[libheif 集成到应用hap](https://gitee.com/openharmony-sig/tpc_c_cplusplus/blob/master/thirdparty/libheif/docs/hap_integrate.md#libheif-%E9%9B%86%E6%88%90%E5%88%B0%E5%BA%94%E7%94%A8hap)的流程进行即可。 21 22**注意事项** 23 24根据[libheif github源工程](https://github.com/strukturag/libheif),libheif解码依赖libde265/ffmpeg,所以还需要集成libd265/ffmpeg。 25 26<img src="./decodeheifimage/src/main/resources/base/media/libheif_dependency_so.png" alt="img" width="300" /> 27 28libd265移植参考[ libde265 集成到应用hap](https://gitee.com/openharmony-sig/tpc_c_cplusplus/blob/master/thirdparty/libde265/docs/hap_integrate.md), libde265文件夹与libheif保持同级目录。 29 30修改thirdparty/libheif/HPKBUILD构建脚本,添加libde265依赖,libde265的编译选项由disable改为enable,这样就可以在libheif编译时链接到libde265。 31 32<img src="./decodeheifimage/src/main/resources/base/media/HPKBUILD1.png" alt="img" width="300" /> 33 34<img src="./decodeheifimage/src/main/resources/base/media/HPKBUILD2.png" alt="img" width="300" /> 35 36最后编译成功的产物可在/lycium/usr下获取,可以看到thirdparty/libheif/HPKBUILD依赖的库都成功编译了,并且查看libheif.so的属性,运行时会依赖libde265.so。 37 38<img src="./decodeheifimage/src/main/resources/base/media/libheif_compile_result.png" alt="img" width="300" /> 39 40<img src="./decodeheifimage/src/main/resources/base/media/libheif_symbol_info.png" alt="img" width="300" /> 41 42#### 2.使用curl网络请求HEIF图片 43 44本案例将[网络库curl集成到hap](https://gitee.com/openharmony-sig/tpc_c_cplusplus/blob/master/thirdparty/curl/docs/hap_integrate.md),这样在Native侧就可以直接请求到HEIF图片,并在Native侧解码,减少跨语言调用开销。 45 46#### 3.读取HEIF图片 47 48libheif允许开发者从不同的来源(磁盘文件、内存、自定义Reader)读取HEIF图片文件,并将其加载到heif_context中,以便后续操作和处理HEIF内容。 49 50本案例以`heif_context_read_from_memory_without_copy`读取网络请求到的HEIF图片(已在内存中)为例。 51 52```c++ 53// 创建HEIF上下文指针 54heif_context *ctx = heif_context_alloc(); 55 56/** 57功能:从指定的磁盘文件中读取HEIF文件。 58参数: 59struct heif_context*:指向HEIF上下文的指针,用于存储和管理HEIF文件的解码和处理信息。 60const char* filename:指向包含HEIF文件名的字符串的指针。 61const struct heif_reading_options*:指向读取选项的指针。目前应该设置为NULL,表示没有特殊的读取选项。 62*/ 63struct heif_error heif_context_read_from_file(struct heif_context*, const char* filename, 64 const struct heif_reading_options*); 65 66/** 67功能:从内存中读取HEIF文件,但提供的内存不会被复制。这意味着,只要使用heif_context,就必须保持内存区域有效。 68参数: 69struct heif_context*:同上。 70const void* mem:指向存储HEIF文件数据的内存块的指针。 71size_t size:内存块的大小,以字节为单位。 72const struct heif_reading_options*:读取选项,同上。 73*/ 74struct heif_error heif_context_read_from_memory_without_copy(struct heif_context*, 75 const void* mem, size_t size, 76 const struct heif_reading_options*); 77/** 78功能:从自定义的struct heif_reader(如网络流、加密文件等)中读取HEIF文件。 79参数: 80struct heif_context*:同上。 81const struct heif_reader* reader:指向实现了特定读取函数的heif_reader结构的指针。 82void* userdata:传递给reader回调的用户定义数据。 83const struct heif_reading_options*:读取选项,同上 84*/ 85struct heif_error heif_context_read_from_reader(struct heif_context*, 86 const struct heif_reader* reader, 87 void* userdata, 88 const struct heif_reading_options*); 89``` 90 91#### 4.解码HEIF图片 92 93解码函数(heif_decode_image)的主要作用是将HEIF图片处理句柄(heif_image_handle)解码为实际的像素图像,并可以设置colorspace和chroma参数来指定输出图像的色彩空间和色度采样方式,如果不需要更改,可以保持为默认值,解码选项参数option可以设置解码后的质量、缩放等。 94 95目前OH_PixelMap_CreatePixelMap颜色编码格式只支持BGRA_8888,但是heif_decode_image不支持该颜色编码格式,所以本案例指定HEIF图片的编码格式为BGRA。 96 97```cpp 98// 获取主图像句柄 99heif_image_handle *handle; 100heif_context_get_primary_image_handle(ctx, &handle); 101// 从句柄中解码图像 102heif_image *heif_img; 103heif_decode_image(handle, &heif_img, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, nullptr); 104``` 105 106#### 5.创建PixelMap 107 108##### 访问heif图像 109 110```cpp 111// 获取图像数据 112int stride; 113uint8_t *data = heif_image_get_plane_readonly(heif_img, heif_channel_interleaved, &stride); 114``` 115 116>在访问heif图像数据时,需要考虑stride,表示图像中每行像素数据在内存中所占的字节数。通常,图像数据在内存中是连续存储的,但是由于内存对齐等因素,每行的字节数可能会大于实际图像的实际宽度,即stride > width * bpp,width是图片的宽度,bpp(bytes per pixel)是每像素的字节数。 117 118##### 字节对齐 119 120RGBA格式下,如果stride > width * bpp,即每行的字节数可能会大于图像的实际宽度,此时需要字节对齐,参考如下代码。 121 122```c++ 123const size_t bpp = 4; // 颜色格式为BGRA,每个像素4个字节 124const size_t pixel_count = width * height; // 像素总数 125const size_t row_bytes = width * bpp; // 每一行的字节数,每个像素4个字节 126const size_t total_size = pixel_count * bpp; // 计算平面的总数据大小 127 128uint8_t *new_data = data; // 默认指向原数据 129bool needAlignment = stride != row_bytes; // 是否需要字节对齐 130if (needAlignment) { 131 new_data = new uint8_t[total_size]; 132 // 字节对齐 133 for (int row = 0; row < height; row++) { 134 memcpy(new_data + row * row_bytes, data + row * stride, row_bytes); 135 } 136} 137``` 138 139##### 颜色编码格式变换 140 141目前OH_PixelMap_CreatePixelMap颜色编码格式只支持BGRA(API12开始会新增),而HEIF的颜色格式为RGBA,所以需要将RGBA转换为BGRA。 142 143```c++ 144// 定义颜色编码格式转换函数 145void swapRBChannels(uint8_t *pixels, int pixelCount) { 146 for (int i = 0; i < pixelCount; i++) { 147 std::swap(pixels[i * 4], pixels[i * 4 + 2]); 148 } 149} 150``` 151 152##### 创建PixelMap 153 154定义HEIF的宽高、颜色编码格式,调用NDK的OH_PixelMap_CreatePixelMap函数即可创建PixelMap。 155 156```c++ 157struct OhosPixelMapCreateOps createOps; 158createOps.width = width; 159createOps.height = height; 160createOps.pixelFormat = 4; // BGRA 161createOps.alphaType = 0; 162 163int32_t res = OH_PixelMap_CreatePixelMap(env, createOps, (void *)new_data, total_size, &pixel_map); 164if (res != IMAGE_RESULT_SUCCESS || pixel_map == nullptr) { 165 OH_LOG_ERROR(LOG_APP, "创建pixelMap错误"); 166 return nullptr; 167} 168``` 169 170### 高性能知识点 171 1721. 本示例使用了LazyForEach进行数据懒加载,WaterFlow布局时会根据可视区域按需创建FlowItem组件,并在FlowItem滑出可视区域外时销毁以降低内存占用。 1732. 本案例的图片软解码使用Taskpool来实现多线程并发能力,提升系统资源利用率,减少主线程负载,加快应用的启动速度和响应速度。 174 175### 工程结构&模块类型 176 177```txt 178``` 179decodeheifimage // har类型 180|---libs\ 181| |---arm64-v8a\libde265.so // arm64-v8a类型libde265库 182| |---arm64-v8a\libheif.so.1 // arm64-v8a类型libheif库 183| |---arm64-v8a\libnativedownloadheif.so // arm64-v8a类型基于libcurl的网络请求so 184| |---arm64-v8a\libnativedecodeheif.so // arm64-v8a类型基于libheif的HEIF解码so 185|---src\main\ets\components\ 186| |---ReusableFlowItem.ets // 公共组件-瀑布流FlowItem组件 187|---src\main\ets\model\ // 模型层 188| |---WaterFlowData.ets // 瀑布流数据和操作类 189| |---TaskPool.ets // TaskPool子线程加载so库 190|---src\main\ets\ 191| |---DecodeHEIFImageView.ets // 主页面 192|---src\main\cpp\ 193| |--- CMakeLists.txt // C++编译和链接配置成 194| |--- decode_heif_image.cpp // 基于libheif的HEIF软解码实现 195| |--- download_heif_image.cpp // 基于libcurl的网络请求实现 196| |--- napi_init.cpp // native层HEIF软解码实现 197``` 198``` 199 200### 模块依赖 201 2021. 依赖公共libs库中的[libcurl.so](./decodeheifimage/libs/arm64-v8a/libcurl.so),进行Native侧的网络请求HEIF图片资源。 203 204### 参考资料 205 206[高效并发编程](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/performance/efficient-concurrent-programming.md#/openharmony/docs/blob/master/zh-cn/application-dev/arkts-utils/cpu-intensive-task-development.md) 207 208[WaterFlow+LazyForEach详细用法可参考性能范例](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/performance/waterflow_optimization.md) 209 210[libheif github源工程](https://github.com/strukturag/libheif) 211 212[libheif 集成到应用hap](https://gitee.com/openharmony-sig/tpc_c_cplusplus/blob/master/thirdparty/libheif/docs/hap_integrate.md#libheif-%E9%9B%86%E6%88%90%E5%88%B0%E5%BA%94%E7%94%A8hap) 213 214[libde265 集成到应用hap](https://gitee.com/openharmony-sig/tpc_c_cplusplus/blob/master/thirdparty/libde265/docs/hap_integrate.md) 215 216[网络库curl集成到hap](https://gitee.com/openharmony-sig/tpc_c_cplusplus/blob/master/thirdparty/curl/docs/hap_integrate.md) 217 218### 相关权限 219 220不涉及 221 222### 约束与限制 223 2241.本示例仅支持在标准系统上运行,支持设备:Phone。 225 2262.本示例为Stage模型,支持API12版本SDK,SDK版本号(API Version 12 Release)。 227 2283.本示例需要使用DevEco Studio 5.0.0 Release 才可编译运行。 229 230### 下载 231 232如需单独下载本工程,执行如下命令: 233```javascript 234git init 235git config core.sparsecheckout true 236echo code/BasicFeature/Native/DecodeHEIFImage/ > .git/info/sparse-checkout 237git remote add origin https://gitee.com/openharmony/applications_app_samples.git 238git pull origin master 239```