Open-source project
strukturag/libheif avatar
strukturag/libheif

libheif: the HEIF and AVIF codec layer that almost everything else calls

libheif is an HEIF and AVIF file format decoder and encoder.

2,396 stars414 forksC++NOASSERTION

At a glance

What is it?
A C library that decodes and encodes HEIC, AVIF, VVC, AVC, JPEG and JPEG 2000 through one API, built on top of libde265, libaom and a dozen other codec backends, and maintained on a shoestring through a year of constant security patches.
Who is it for?
libheif earns its place in a stack because it is the layer where HEIF stops being a container question and becomes a codec question. It hands you one API and lets libde265, dav1d, libaom, x265 or kvazaar do the actual compression work, which is why your build decisions are really decisions about which of those you want to link.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 8, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What libheif does and where it sits in the HEIF stack

libheif is a decoder and encoder for ISO/IEC 23008-12 HEIF and AVIF files. The README's first line is precise about scope: it is a file format decoder and encoder, not a codec. That distinction is the whole architecture. HEIC and AVIF are containers, and the actual image coding inside them is HEVC for HEIC and AV1 for AVIF. libheif parses the container, resolves the image graph, handles items, grids, metadata and transformations, then hands the coded bitstream to whichever codec library you linked against and converts the result back into a color space you asked for.

The practical consequence is that HEIF support is never just one decision. It is a dependency graph. The README states the default arrangement plainly: for HEIC, libde265 is used for decoding and x265 for encoding. For AVIF it lists libaom, dav1d, svt-av1 and rav1e as the codecs in play. The project can be built with a subset of the supported codecs to keep size and dependencies low, or the codecs can be compiled as separate plugins that load dynamically at the point of use.

That plugin option is the interesting one for anyone shipping an application. It means you can distribute a core library that knows the HEIF container format and acquire codec capability at runtime, which is how a browser, an image editor or an asset pipeline avoids a hard dependency on a particular encoder's licensing.

The repository carries 2,389 stars, 410 forks and 269 open issues, with the last push recorded on 2026-09-21. The open issue count is high in absolute terms, but it is the visible edge of an unusual situation the README now calls out directly: libheif and libde265 are maintained by a single independent developer with almost no recurring funding. That line sits at the top of the README rather than in a support section, which tells you the maintainer would rather you plan for it.

The codec table and why x265 changes your licensing options

The README ships a full decoder and encoder matrix, and it is worth reading as a decision aid rather than a feature list:

| Format | Decoders | Encoders | |---|---|---| | HEIC | libde265, ffmpeg | x265, kvazaar | | AVIF | libaom, dav1d, ffmpeg | libaom, rav1e, svt-av1 | | VVC | vvdec, ffmpeg | vvenc, uvg266 | | AVC | openh264, ffmpeg | x264 | | JPEG | libjpeg(-turbo), ffmpeg | libjpeg(-turbo) | | JPEG2000 | OpenJPEG, ffmpeg | OpenJPEG | | HTJ2K | OpenJPEG, ffmpeg | OpenJPH | | uncompressed | built-in | built-in |

Two rows matter more than the rest for most integrators. The first is HEIC, because x265 is the practical encoder and it is GPL. The README says so directly, and it names the alternative: kvazaar, which is BSD. If you are linking HEIF encoding into a closed-source product, that row is the license conversation you are having.

The second is the uncompressed row, and the fact that it is built-in on both sides is a deliberate design choice rather than an omission. The README explains that the ISO/IEC 23001-17 codec is built in and allows lossless images and video to be stored in many different formats. It also means libheif always has a working codec path even before you install anything, which is what makes the incremental build instructions in the README possible.

The VVC row is worth flagging for planning purposes. VVC support depends on vvdec, vvenc or uvg266, none of which are default installs anywhere, so VVC capability is always something you add deliberately.

On licensing metadata, three sources need to be reconciled. GitHub reports the license field as NOASSERTION. The README never names a license at all. The tree does contain a `COPYING` file at the root and another inside `examples/`, so a license text is present in the repository even though no metadata field claims it. Read `COPYING` rather than relying on the NOASSERTION field, which in practice records that GitHub could not auto-detect a recognized license identifier, not that the project has none. Treat the codecs themselves as the licensing question that matters, since x265 and kvazaar differ from each other and from libheif's own terms.

The C API in practice: loading an image and writing one back

The library exposes a C API first, and the README gives three runnable examples. That choice is what makes libheif usable from almost any language, since a C ABI is reachable from Rust, Go, Python and anything with an FFI, and the tree backs this up: the `examples/` directory contains C++ programs plus a single Go example, `heif-test.go`, alongside `heif-dec.cc`, `heif-enc.cc`, `heif_view.cc`, `heif_thumbnailer.cc` and `benchmark.cc`.

Loading the primary image looks like this, and it is the shape almost every integration ends up with:

C
heif_context* ctx = heif_context_alloc();
heif_context_read_from_file(ctx, input_filename, nullptr);

// get a handle to the primary image
heif_image_handle* handle;
heif_context_get_primary_image_handle(ctx, &handle);

// decode the image and convert colorspace to RGB, saved as 24bit interleaved
heif_image* img;
heif_decode_image(handle, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGB, nullptr);

int stride;
const uint8_t* data = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride);

Four things are visible in those thirteen lines. A context owns the file. A handle represents the image without decoding it, so you can inspect metadata cheaply before paying for pixels. The decode call takes the colorspace and chroma layout you want, so conversion is part of the decode rather than a separate step. And the returned pointer is a read-only plane view with an explicit stride, which is the right abstraction to hand to a pixel loop or to another library.

Writing is the mirror image, and the encoder selection is a single enum:

C
heif_context* ctx = heif_context_alloc();

// get the default encoder
heif_encoder* encoder;
heif_context_get_encoder_for_format(ctx, heif_compression_HEVC, &encoder);

// set the encoder parameters
heif_encoder_set_lossy_quality(encoder, 50);

The README states the rule for switching formats directly: set `heif_compression_HEVC` or `heif_compression_AV1` on the `heif_context_get_encoder_for_format()` call, or use any of the other supported compression formats. That single call is why an application can accept HEIC and AVIF input through one code path.

For metadata there is a separate block, and it shows the item-level addressing model:

C
heif_item_id exif_id;

int n = heif_image_handle_get_list_of_metadata_block_IDs(image_handle, "Exif", &exif_id, 1);
if (n==1) {
  size_t exifSize = heif_image_handle_get_metadata_size(image_handle, exif_id);
  uint8_t* exifData = malloc(exifSize);
  struct heif_error error = heif_image_handle_get_metadata(image_handle, exif_id, exifData);
}

The size query is separate from the fetch, so the allocation is never guessed. And the return value of the ID list call is checked against one, because metadata blocks are optional and absent EXIF is the normal case rather than an error.

There is also a C++ API, described in the README as a header-only wrapper over the C API. Because it wraps rather than replaces, code written against the C++ header stays binary compatible with a C build. That property is the reason to prefer it in application code: you get a less verbose interface without taking on an ABI commitment.

What you get that a decoder alone would not provide

The feature list is where libheif stops looking like a codec binding and starts looking like an image pipeline. The README claims support for alpha channels, depth maps, thumbnails and auxiliary images, multiple images in a file, HEIF image sequences and MP4 video including alpha channels, tiled images with both decoding of individual tiles and incremental encoding by adding tiles one after another, HDR images with correct color transform according to embedded color profiles, image transformations such as crop, mirror and rotate along with overlay images, a plugin interface for alternative codecs, EXIF and XMP metadata, region annotations and mask images, and streaming of images and video by requesting data from the network through a data-reader interface.

Several of those are load-bearing for specific products, and it is worth naming which.

Tiled images are the answer to very large images. The README notes that for very large resolutions it is not always feasible to process the whole image, and points at separate API documentation for reading and writing tiled images. If you are building something that handles print-resolution or geospatial imagery, this is the feature that decides whether libheif is viable for you.

The data-reader interface is the streaming story. HEIF can hold items lazily, so a network-backed reader means you can decode an image whose bytes you have not fully downloaded. That is the mechanism behind progressive loading in a browser or a client that must display before the transfer completes.

MP4 support comes from the same observation the README makes about image sequences: since HEIF image sequences are very similar to MP4 video, libheif can read and write MP4 video without audio across the supported codecs. If you were treating libheif as image-only, that is free additional scope.

Metadata handling is broader than EXIF. XMP is listed alongside it, which matters for publishing pipelines where rights and workflow metadata live outside the image pixels.

And the plugin interface is the escape hatch. Every item above assumes a codec you linked. The plugin interface is how you add one you did not, which is how the library keeps handling new compression formats without a release for each one.

Building from source after autotools was dropped

The build story is short and has one hard break in it. The README states that the library uses CMake, and that the earlier autotools build files were removed in v1.16.0. Any build script, distribution package or CI recipe you inherited that shells out to `configure` is running against a version older than 1.16.0 and will need rewriting.

The recommended minimal configuration is deliberate: libde265 and x265 for HEIC, and AOM for AVIF. The README says to compile and install libde265 first so that the configuration script will find it, which is an ordering requirement rather than a suggestion, since CMake locates it during configuration and an installed-later libde265 means reconfiguring. x265 and its development files are needed only if you want encoding, and the GPL note plus the kvazaar alternative come attached to that same instruction.

The build steps are presented as a standard out-of-tree sequence, and the one non-obvious constraint is stated inline: the `--preset` argument needs CMake 3.21 or newer. The tree confirms the modern CMake layout with `CMakeLists.txt`, `CMakePresets.json`, a `cmake/` directory and a `libheif.pc.in` for pkg-config output.

The rest of the tree tells you what the project is prepared for. `fuzzing/` exists as a top-level directory, which is consistent with a library that parses untrusted container input, and it lines up with the release notes discussed below. `third-party/` is present for vendored dependencies. `heifio/` is the I/O abstraction that the streaming interface builds on. There are integration directories for specific consumers: `gdk-pixbuf/` and `gnome/` for the desktop image pipeline, plus `go/` for the Go bindings, and `extra/` for platform-specific pieces. `build-emscripten.sh` at the root is the WebAssembly build, and the 1.23.4 advisory references an Emscripten-specific failure, so that path is maintained rather than vestigial.

For packaging, note that libheif ships a `.pc` file. That means pkg-config based dependency declarations, which is what a Linux distribution will want, and it means consumers can pick up include paths and link flags without hardcoding them.

The 2026 security record and what it means for shipping it

The README's project status note puts a number on 2026: 61 security advisories had to be investigated, fixed and released in that year alone, with the library maintained by one independent developer. The three most recent releases are all security releases, and they landed within about three weeks of each other, which tells you how much of the maintainer's bandwidth security work is consuming.

Version 1.23.5 shipped on 2026-09-21, is ABI- and API-compatible with 1.23.4 and is described as a drop-in replacement. Its headline fix is rated high, with all users advised to upgrade. The bug is memory exhaustion caused by a mismatch between the container and the bitstream image size: a crafted image can declare a small size in its ispe property while the bitstream declares a much larger coded frame. Container-level checks used the ispe size, so the oversized bitstream reached the decoder, which allocated a frame buffer for the in-band size before libheif rejected the mismatch. The advisory demonstrates it for AV1 with the libaom backend using a 351-byte AVIF that declares 64x64 but codes up to 27648x27648, allocating hundreds of megabytes to more than 10 GB. The notes point out the same class affects every codec whose real frame size lives in the bitstream. The fix checks the coded size against `max_image_size_pixels` in the codec-independent decode path, before any bytes reach a decoder plugin.

That fix location is the interesting part. Moving the check into the shared path means it protects against codecs not yet written, rather than patching one plugin at a time.

Version 1.23.4 shipped on 2026-09-06 with three high-rated issues. One is the `max_items` security limit not being enforced for the child boxes of `iinf`, so a file could declare an unbounded number of items, and parsing then cost quadratic time in the per-item `iref` reference scan. The fix bounds the declared count by the remaining input, reports a short read instead of silently yielding fewer children, and indexes the `iref` queries by source item ID instead of scanning the whole reference list. The release gives a measurement: on a file with 1000 items and 160000 dimg references, read time drops from 1.83 s to 0.26 s. The same issue had a WebAssembly-specific consequence, since in Emscripten builds the item count sized an `alloca()` in the JavaScript item-ID helpers, overflowing the 64 KB shadow stack and permanently trapping the module.

Version 1.23.3 shipped on 2026-09-01 with one critical issue, for which all users are strongly advised to upgrade. It is a heap buffer overflow write in the uncompressed (unci) mixed-interleave decoder when the two chroma components declare different bit depths, with both the written bytes and the overflow length controlled by the file. The same release fixes a permanent decoder deadlock caused by a reference cycle between an image and its alpha auxiliary image, where the alpha edge was not covered by the cycle guard and re-entered a held mutex, and a heap out-of-bounds read in the YCbCr 4:2:0 to 16-bit interleaved RGB conversion when chroma planes have a lower bit depth than luma, which could place heap memory into the decoded image. Both of those conversions are now rejected rather than attempted.

The pattern across all three releases is worth naming: the bugs are in container parsing, item graph traversal and chroma bit-depth handling, not in the compression codecs themselves. If you feed libheif untrusted input, which any web service or upload handler does, that is the surface you are exposed to. The practical response is unglamorous. Track the version, take each of these releases since they are drop-in replacements with no API change, keep `max_image_size_pixels` set to a value appropriate for your use case rather than leaving it permissive, and read SECURITY.md for how to report an issue you find yourself.

Editorial conclusion

libheif earns its place in a stack because it is the layer where HEIF stops being a container question and becomes a codec question. It hands you one API and lets libde265, dav1d, libaom, x265 or kvazaar do the actual compression work, which is why your build decisions are really decisions about which of those you want to link. The two things to plan around are the x265 GPL constraint if you ship a closed binary, and the 2026 advisory cadence: this library had 61 security issues investigated and released in a single year under one maintainer, so treating its release notes as something to skim is a mistake. Pin the version, read the advisory list when you bump, and take the drop-in security releases without hesitating.

Frequently asked questions

What is libheif used for?

libheif decodes and encodes HEIF container formats, which includes HEIC, AVIF, VVC, AVC, JPEG-in-HEIF, JPEG 2000, HTJ2K and an uncompressed codec. Applications use it to read and write these files without linking a specific compression library directly, because libheif routes the actual bitstream work to libde265, dav1d, libaom, x265, kvazaar and others that you choose at build time. It also handles alpha channels, thumbnails, depth maps, EXIF and XMP metadata, tiled large images, and MP4 video, so most integrators use it as a complete image pipeline rather than a codec alone.

Should I enable HEIF?

Enable it if you receive images from phones, since HEIC is the default capture format on current iPhones and Android devices, and AVIF is increasingly common on the web. In libheif terms, enabling HEIF means linking libde265 for decoding, plus x265 or kvazaar for encoding. The reason to be deliberate is the license: x265 is GPL, while kvazaar is BSD, so the encoder you pick determines whether you can ship a closed binary. Decoding alone through libde265 is the low-friction path, and you can build with a subset of codecs to keep dependencies small.

How can I open a HEIF file?

With the libheif tools, allocate a context, read the file into it, get a handle to the primary image, then decode it into the color space you want. The README's example calls heif_context_alloc(), heif_context_read_from_file(), heif_context_get_primary_image_handle() and heif_decode_image(), then reads the interleaved plane with heif_image_get_plane_readonly() and the returned stride. For a quick look without writing code, the repository ships heif_view.cc, heif_dec.cc and heif_info.cc as examples, and the README notes a gdk-pixbuf integration directory, so desktop image viewers that use gdk-pixbuf can display HEIF once the plugin is installed.

Can I change HEIF to JPEG?

Yes, and libheif is the library to do it with. Allocate a context, read the HEIF file, get the primary image handle and decode it to a color space and chroma layout you control, such as heif_colorspace_RGB with heif_chroma_interleaved_RGB. That gives you a 24-bit interleaved buffer with an explicit stride that you can write out through any JPEG encoder, including libjpeg or libjpeg-turbo, both of which appear as encoder and decoder entries in the README's supported codec table. libheif itself does not transcode to JPEG in one call; the decode and re-encode steps are yours to wire together.

Official sources

  1. Issues
  2. README
  3. Releases
  4. strukturag/libheif on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/strukturag-libheif.svg)](https://hysenlabs.com/projects/strukturag-libheif)