All formats

libheif-rs

LanguageRust
LicenseMIT (libheif itself: LGPL-3.0)
Repositorygithub.com/Cykooz/libheif-rs
Documentationdocs.rs/libheif-rs
Depends onlibheif 1.17 or later (system library, or built from source with the embedded-libheif feature), plus libde265 / x265 for HEVC
Read / writeyes / yes, HEIC and AVIF, alpha, high bit depth, thumbnails, depth images, Exif/XMP

libheif-rs wraps the libheif C API through the libheif-sys crate with a safe Rust API: HeifContext for a file, ImageHandle for each image, LibHeif for decoding and encoders, and Image for pixel planes. With the image feature it registers decoding hooks so that the image crate opens HEIC and AVIF files.

The crate needs libheif at build and run time. The embedded-libheif feature compiles libheif from source and links it statically, but not its codecs: libde265 (and x265 to encode) must still be available.

Installation#

Shell
# Debian / Ubuntu
sudo apt install libheif-dev libheif-plugin-libde265 libheif-plugin-x265
# macOS
brew install libheif
cargo add libheif-rs
# with image crate integration
cargo add libheif-rs --features image

Features v1_17 … v1_21 select the minimum libheif API; the default latest requires the newest version the crate supports. With an older system libheif use e.g. default-features = false, features = ["v1_17"].

Basic usage: decode a HEIC file to RGB#

Rust
use libheif_rs::{ColorSpace, HeifContext, LibHeif, RgbChroma};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let lib_heif = LibHeif::new();
    let ctx = HeifContext::read_from_file("IMG_0001.heic")?;
    let handle = ctx.primary_image_handle()?;
    println!("{} x {}, alpha: {}", handle.width(), handle.height(), handle.has_alpha_channel());

    let image = lib_heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?;
    let planes = image.planes();
    let plane = planes.interleaved.ok_or("no interleaved plane")?;

    // copy rows into a tightly packed buffer (rows may be padded)
    let row = plane.width as usize * 3;
    let mut rgb = Vec::with_capacity(row * plane.height as usize);
    for y in 0..plane.height as usize {
        rgb.extend_from_slice(&plane.data[y * plane.stride..y * plane.stride + row]);
    }
    println!("{} bytes of RGB", rgb.len());
    Ok(())
}

Grid tiles, cropping and rotation are applied during decoding. Use RgbChroma::Rgba for images with alpha.

How to open HEIC with the image crate#

Rust
use image::ImageReader;
use libheif_rs::integration::image::register_all_decoding_hooks;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    register_all_decoding_hooks();  // HEIF and AVIF
    let img = ImageReader::open("IMG_0001.heic")?.decode()?;
    img.save("IMG_0001.png")?;
    Ok(())
}

How to encode an image to HEIC#

Rust
use libheif_rs::{Channel, ColorSpace, CompressionFormat, EncoderQuality, HeifContext, Image, LibHeif, RgbChroma};

fn save_heic(path: &str, rgb: &[u8], width: u32, height: u32) -> libheif_rs::Result<()> {
    let mut image = Image::new(width, height, ColorSpace::Rgb(RgbChroma::Rgb))?;
    image.create_plane(Channel::Interleaved, width, height, 8)?;
    let planes = image.planes_mut();
    let plane = planes.interleaved.unwrap();
    let row = width as usize * 3;
    for y in 0..height as usize {
        plane.data[y * plane.stride..y * plane.stride + row].copy_from_slice(&rgb[y * row..(y + 1) * row]);
    }

    let lib_heif = LibHeif::new();
    let mut encoder = lib_heif.encoder_for_format(CompressionFormat::Hevc)?;  // fails without x265/kvazaar
    encoder.set_quality(EncoderQuality::Lossy(50))?;
    let mut context = HeifContext::new()?;
    context.encode_image(&image, &mut encoder, None)?;
    context.write_to_file(path)?;
    Ok(())
}

CompressionFormat::Av1 writes AVIF with the same code.

How to read Exif metadata#

Rust
use libheif_rs::ItemId;

let mut ids: Vec<ItemId> = vec![0; 1];
if handle.metadata_block_ids(&mut ids, b"Exif") == 1 {
    let exif: Vec<u8> = handle.metadata(ids[0])?;
    // first 4 bytes: offset to the TIFF header
    println!("Exif: {} bytes", exif.len());
}

How to get the thumbnail and depth map#

Rust
let mut ids: Vec<ItemId> = vec![0; handle.number_of_thumbnails()];
handle.thumbnail_ids(&mut ids);
for id in ids {
    let thumb = handle.thumbnail(id)?;
    println!("thumbnail {} x {}", thumb.width(), thumb.height());
}

if handle.has_depth_image() {
    let mut ids: Vec<ItemId> = vec![0; handle.number_of_depth_images() as usize];
    handle.depth_image_ids(&mut ids);
    let depth = handle.depth_image_handle(ids[0])?;
    println!("depth map {} x {}", depth.width(), depth.height());
}

Tips#