使用libheif库读取heic文件的缩略图

使用libheif库读取heic文件的缩略图

1.先使用vcpkg安装libheif库

在开始之前,我们需要安装 libheif 库。通过 vcpkg 工具可以方便地安装所需的依赖,根据实际需求选择适合的位数以及动态库或静态库版本。

安装命令示例:

1
2
vcpkg install libheif:x86-windows-static
vcpkg install libheif:x64-windows-static

完成安装后,确保正确配置 vcpkg 环境,能够被 CMake 或其他构建工具找到。

2.解码缩略图和解码原图的区别

在处理 HEIC 格式文件时,缩略图解码和原图解码的使用场景有所不同。以下从资源占用和代码实现两方面进行比较。

1.解码原图

解码原图时,图像数据会被完全加载到内存中,适合用于查看高清图片或后续进行深度处理。但这也意味着需要更多的内存和 CPU 资源。

2.解码缩略图

如果仅用于预览或快速浏览,可以选择解码缩略图。这种方法占用资源更少,加载速度更快,非常适合需要快速显示大量图片的场景。

解码原图(查看图片使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void heifToQPixmap(const QString& filePath, QPixmap& pixmap) {
QString extension = QFileInfo(filePath).suffix().toLower();

if (extension == "jpg" || extension == "jpeg") {
if (!pixmap.load(filePath)) {
throw std::runtime_error("Failed to load JPG image.");
}
return;
}
std::string filePathStd = filePath.toStdString();

struct heif_context* ctx = heif_context_alloc();
if (!ctx) {
throw std::runtime_error("Failed to allocate HEIF context.");
}

struct heif_error err = heif_context_read_from_file(ctx, filePathStd.c_str(), nullptr);
if (err.code != heif_error_Ok) {
heif_context_free(ctx);
throw std::runtime_error("Failed to read HEIF file: " + std::string(err.message));
}

struct heif_image_handle* handle = nullptr;
err = heif_context_get_primary_image_handle(ctx, &handle);
if (err.code != heif_error_Ok) {
heif_context_free(ctx);
throw std::runtime_error("Failed to get primary image handle: " + std::string(err.message));
}

struct heif_image* img = nullptr;
err = heif_decode_image(handle, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, nullptr);
if (err.code != heif_error_Ok) {
heif_image_handle_release(handle);
heif_context_free(ctx);
throw std::runtime_error("Failed to decode HEIF image: " + std::string(err.message));
}

int width = heif_image_get_width(img, heif_channel_interleaved);
int height = heif_image_get_height(img, heif_channel_interleaved);
const uint8_t* data = heif_image_get_plane_readonly(img, heif_channel_interleaved, nullptr);

QVector<uchar> buffer(width * height * 4);
memcpy(buffer.data(), data, buffer.size());

QImage image(buffer.data(), width, height, QImage::Format_RGBA8888);
pixmap = QPixmap::fromImage(image);

heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
}

解码缩略图(供预览使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
bool loadHeicThumbnail(const QString& filePath, QPixmap& thumbnail) {
QString extension = QFileInfo(filePath).suffix().toLower();

if (extension == "jpg" || extension == "jpeg") {
QImageReader reader(filePath);
reader.setAutoTransform(true);

QSize originalSize = reader.size();
if (originalSize.isEmpty()) {
qDebug() << "Failed to read image size:" << reader.errorString() << filePath;
return false;
}
QSize scaledSize = originalSize.scaled(QSize(originalSize/20), Qt::KeepAspectRatio);

reader.setScaledSize(scaledSize);

QImage image = reader.read();
if (image.isNull()) {
qWarning() << "Failed to load image:" << reader.errorString() << filePath;
return false;
}

thumbnail = QPixmap::fromImage(image);
return true;
}

heif_context* ctx = heif_context_alloc();
heif_error err = heif_context_read_from_file(ctx, filePath.toStdString().c_str(), nullptr);
if (err.code != heif_error_Ok) {
qDebug() << "Failed to read HEIF file: ";
heif_context_free(ctx);
return false;
}

heif_image_handle* handle = nullptr;
err = heif_context_get_primary_image_handle(ctx, &handle);
if (err.code != heif_error_Ok) {
qDebug() << "Failed to get primary image handle: ";
heif_context_free(ctx);
return false;
}

heif_image_handle* thumbnail_handle = nullptr;
int num_thumbnails = heif_image_handle_get_number_of_thumbnails(handle);
if (num_thumbnails > 0) {
heif_item_id thumbnail_id;
heif_image_handle_get_list_of_thumbnail_IDs(handle, &thumbnail_id, 1);
err = heif_image_handle_get_thumbnail(handle, thumbnail_id, &thumbnail_handle);
if (err.code != heif_error_Ok) {
qDebug() << "Failed to get thumbnail handle: ";
heif_image_handle_release(handle);
heif_context_free(ctx);
return false;
}

// 解码缩略图
heif_image* thumbnail_image = nullptr;
err = heif_decode_image(thumbnail_handle, &thumbnail_image, heif_colorspace_RGB, heif_chroma_interleaved_RGB, nullptr);
if (err.code != heif_error_Ok) {
qDebug() << "Failed to decode thumbnail: " << err.message;
heif_image_handle_release(thumbnail_handle);
heif_image_handle_release(handle);
heif_context_free(ctx);
return false;
}
int width = heif_image_get_width(thumbnail_image, heif_channel_interleaved);
int height = heif_image_get_height(thumbnail_image, heif_channel_interleaved);
int stride;
const uint8_t* data = heif_image_get_plane_readonly(thumbnail_image, heif_channel_interleaved, &stride);
QImage image(data, width, height, stride, QImage::Format_RGB888); \

if (image.isNull()) {
qDebug() << "Failed to convert HEIF thumbnail to QImage.";
heif_image_release(thumbnail_image);
heif_image_handle_release(thumbnail_handle);
heif_image_handle_release(handle);
heif_context_free(ctx);
return false;
}

thumbnail = QPixmap::fromImage(image.copy());//不使用copy data释放掉后内存访问冲突

heif_image_release(thumbnail_image);
heif_image_handle_release(thumbnail_handle);
}
else {
qDebug() << "No thumbnails found in the HEIF file.";
heif_image_handle_release(handle);
heif_context_free(ctx);
return false;
}

heif_image_handle_release(handle);
heif_context_free(ctx);
return true;
}

3.测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <QApplication>
#include <QLabel>
#include <QImage>
#include <QPixmap>
#include <libheif/heif.h>
#include <iostream>

QImage heifToQImage(const std::string& filePath) {
// 初始化 libheif 句柄
struct heif_context* ctx = heif_context_alloc();
if (!ctx) {
throw std::runtime_error("Failed to allocate HEIF context.");
}

// 读取 HEIF 文件
struct heif_error err = heif_context_read_from_file(ctx, filePath.c_str(), nullptr);
if (err.code != heif_error_Ok) {
heif_context_free(ctx);
throw std::runtime_error("Failed to read HEIF file: " + std::string(err.message));
}

// 获取主图像句柄
struct heif_image_handle* handle = nullptr;
err = heif_context_get_primary_image_handle(ctx, &handle);
if (err.code != heif_error_Ok) {
heif_context_free(ctx);
throw std::runtime_error("Failed to get primary image handle: " + std::string(err.message));
}

// 解码图像
struct heif_image* img = nullptr;
err = heif_decode_image(handle, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, nullptr);
if (err.code != heif_error_Ok) {
heif_image_handle_release(handle);
heif_context_free(ctx);
throw std::runtime_error("Failed to decode HEIF image: " + std::string(err.message));
}

// 获取图像数据
int width = heif_image_get_width(img, heif_channel_interleaved);
int height = heif_image_get_height(img, heif_channel_interleaved);
const uint8_t* data = heif_image_get_plane_readonly(img, heif_channel_interleaved, nullptr);

// 创建缓冲区并复制数据
QVector<uchar> buffer(width * height * 4); // RGBA8888,每像素4字节
memcpy(buffer.data(), data, buffer.size());

// 转换为 QImage
QImage qimage(buffer.data(), width, height, QImage::Format_RGBA8888);

// 释放资源
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);

return qimage.copy(); // 确保 QImage 独立持有数据
}


int main(int argc, char* argv[]) {
QApplication app(argc, argv);

try {
QImage image = heifToQImage("C:/Users/lianx/Pictures/IMG_0001.HEIC");

QLabel label;
label.setPixmap(QPixmap::fromImage(image));
label.setScaledContents(true);
label.resize(image.size()/2);
label.show();

return app.exec();
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return -1;
}
}

4.效果如图