libheif-rs
| Language | Rust |
|---|---|
| License | MIT (libheif itself: LGPL-3.0) |
| Repository | github.com/Cykooz/libheif-rs |
| Documentation | docs.rs/libheif-rs |
| Depends on | libheif 1.17 or later (system library, or built from source with the embedded-libheif feature), plus libde265 / x265 for HEVC |
| Read / write | yes / 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#
# 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#
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#
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#
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#
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#
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#
HeifContext::read_from_bytes(&data)reads from memory;ctx.image_ids()lists all top-level images.image.scale(w, h, None)resizes a decoded image inside libheif.LibHeif::new_checked()returns an error instead of panicking if the codec plugins can’t be loaded.