libheif
| Language | C API (with a header-only C++ wrapper), implementation in C++ |
|---|---|
| License | LGPL-3.0 (library), MIT (sample programs) |
| Repository | github.com/strukturag/libheif |
| Documentation | README, wiki and the comments in the libheif/heif_*.h headers |
| Depends on | libde265 (HEVC decoding), x265 or kvazaar (HEVC encoding); optional libaom/dav1d/rav1e/SVT-AV1 (AVIF), libjpeg, OpenJPEG |
| Read / write | yes / yes, HEIC, AVIF and other HEIF codecs; grids, alpha, depth maps, thumbnails, Exif/XMP, image sequences |
libheif parses and writes the HEIF container: items, properties, grids, thumbnails, auxiliary images, metadata and sequences. It hands the coded data to codec plugins: libde265 decodes HEVC, x265 or kvazaar encode it, and AV1, VVC, JPEG and JPEG 2000 codecs are supported too. Most open-source software that opens HEIC uses it: GIMP, Krita, ImageMagick, libvips, darktable, digiKam, and the bindings for Python, Go, Rust and .NET.
libheif itself is LGPL-3.0. Codec plugins have their own licenses: libde265 is LGPL-3.0, x265 is GPL-2.0, kvazaar is BSD-3-Clause. HEVC patents are a separate matter from these copyright licenses; see HEIC patents.
It ships command-line tools: heif-dec (decode to JPEG/PNG/TIFF), heif-enc (encode) and heif-info (print the item structure of a file).
Installation#
# Debian / Ubuntu (libheif-plugin-* packages add codecs, e.g. libheif-plugin-x265 for encoding)
sudo apt install libheif-dev libheif-plugin-libde265 libheif-plugin-x265 libheif-examples
# Fedora
sudo dnf install libheif-devel libheif-tools
# macOS
brew install libheif
# Windows (vcpkg)
vcpkg install libheif
With CMake:
find_package(libheif REQUIRED)
target_link_libraries(myapp PRIVATE heif)
Basic usage: decode a HEIC file to RGB#
This program decodes the primary image and writes it as a binary PPM file:
#include <libheif/heif.h>
#include <stdio.h>
int main(void) {
heif_init(NULL);
struct heif_context *ctx = heif_context_alloc();
struct heif_error err = heif_context_read_from_file(ctx, "in.heic", NULL);
if (err.code != heif_error_Ok) {
fprintf(stderr, "read error: %s\n", err.message);
return 1;
}
struct heif_image_handle *handle;
heif_context_get_primary_image_handle(ctx, &handle);
printf("%d x %d, alpha: %d\n", heif_image_handle_get_width(handle),
heif_image_handle_get_height(handle), heif_image_handle_has_alpha_channel(handle));
struct heif_image *img;
err = heif_decode_image(handle, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGB, NULL);
if (err.code != heif_error_Ok) {
fprintf(stderr, "decode error: %s\n", err.message); // e.g. no HEVC decoder plugin
return 1;
}
// grid tiles, cropping and rotation (irot/imir) are already applied
int w = heif_image_get_width(img, heif_channel_interleaved);
int h = heif_image_get_height(img, heif_channel_interleaved);
size_t stride;
const uint8_t *data = heif_image_get_plane_readonly2(img, heif_channel_interleaved, &stride);
FILE *f = fopen("out.ppm", "wb");
fprintf(f, "P6\n%d %d\n255\n", w, h);
for (int y = 0; y < h; y++)
fwrite(data + y * stride, 1, (size_t)w * 3, f); // rows may be padded: use stride
fclose(f);
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
heif_deinit();
return 0;
}
heif_image_get_plane_readonly2 (size_t stride) is in libheif 1.20 and later. With older versions use heif_image_get_plane_readonly, which returns an int stride.
How to encode RGB pixels to HEIC#
#include <libheif/heif.h>
#include <string.h>
int save_heic(const char *path, const uint8_t *rgb, int w, int h, int quality) {
struct heif_image *img;
heif_image_create(w, h, heif_colorspace_RGB, heif_chroma_interleaved_RGB, &img);
heif_image_add_plane(img, heif_channel_interleaved, w, h, 8);
size_t stride;
uint8_t *dst = heif_image_get_plane2(img, heif_channel_interleaved, &stride);
for (int y = 0; y < h; y++)
memcpy(dst + y * stride, rgb + (size_t)y * w * 3, (size_t)w * 3);
struct heif_context *ctx = heif_context_alloc();
struct heif_encoder *encoder;
struct heif_error err = heif_context_get_encoder_for_format(ctx, heif_compression_HEVC, &encoder);
if (err.code != heif_error_Ok) return 1; // no HEVC encoder plugin (x265/kvazaar) installed
heif_encoder_set_lossy_quality(encoder, quality); // 0-100; 50 is about JPEG quality 90
err = heif_context_encode_image(ctx, img, encoder, NULL, NULL);
heif_encoder_release(encoder);
if (err.code == heif_error_Ok)
err = heif_context_write_to_file(ctx, path);
heif_image_release(img);
heif_context_free(ctx);
return err.code == heif_error_Ok ? 0 : 1;
}
libheif converts RGB to YCbCr 4:2:0 for HEVC. Use heif_compression_AV1 instead to write AVIF with the same code.
How to check if a file is HEIC#
// data: the first bytes of the file (12 or more)
enum heif_filetype_result r = heif_check_filetype(data, len);
if (r == heif_filetype_yes_supported) {
// a HEIF file libheif can read
}
How to read Exif metadata#
heif_item_id exif_id;
int n = heif_image_handle_get_list_of_metadata_block_IDs(handle, "Exif", &exif_id, 1);
if (n == 1) {
size_t size = heif_image_handle_get_metadata_size(handle, exif_id);
uint8_t *exif = malloc(size);
heif_image_handle_get_metadata(handle, exif_id, exif);
// the first 4 bytes are the offset to the TIFF header ("MM" or "II")
free(exif);
}
XMP is stored as a mime item: list IDs with type "mime" and check heif_image_handle_get_metadata_content_type for application/rdf+xml.
How to get the thumbnail and the depth map#
heif_item_id thumb_id;
if (heif_image_handle_get_list_of_thumbnail_IDs(handle, &thumb_id, 1) == 1) {
struct heif_image_handle *thumb;
heif_image_handle_get_thumbnail(handle, thumb_id, &thumb);
// decode thumb with heif_decode_image like the main image
heif_image_handle_release(thumb);
}
if (heif_image_handle_has_depth_image(handle)) {
heif_item_id depth_id;
heif_image_handle_get_list_of_depth_image_IDs(handle, &depth_id, 1);
struct heif_image_handle *depth;
heif_image_handle_get_depth_image_handle(handle, depth_id, &depth);
// decode with heif_colorspace_monochrome, heif_chroma_monochrome
heif_image_handle_release(depth);
}
How to list all images in a file#
int count = heif_context_get_number_of_top_level_images(ctx);
heif_item_id ids[64];
count = heif_context_get_list_of_top_level_image_IDs(ctx, ids, count < 64 ? count : 64);
for (int i = 0; i < count; i++) {
struct heif_image_handle *h;
heif_context_get_image_handle(ctx, ids[i], &h);
printf("item %u: %d x %d%s\n", ids[i], heif_image_handle_get_width(h),
heif_image_handle_get_height(h), heif_image_handle_is_primary_image(h) ? " (primary)" : "");
heif_image_handle_release(h);
}
Grid tiles and thumbnails are not top-level images, so a normal phone photo has one.
Tips#
- Call
heif_init(NULL)once before using libheif: it loads the codec plugins (fromLIBHEIF_PLUGIN_PATHif set). heif-info -d photo.heicdumps all boxes; use it to debug files.- libheif enforces security limits (maximum image size, number of tiles). For trusted huge images adjust them with
heif_context_get_security_limits. - The header-only C++ API (
#include <libheif/heif_cxx.h>) wraps the same functions with classes and exceptions.