• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![decode_heif_image](./decodeheifimage/src/main/resources/base/media/decode_heif_image.gif)
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/ffmpeg25
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.so37
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```