If you are using an iPhone and your camera capture format is set to “High Efficiency,” you will get images in the HEIF format. HEIF/HEIC uses HEVC (High Efficiency Video Coding, also known as H.265) technology, which allows storing images at a higher compression rate than traditional JPEG formats. However, even in 2025, its compatibility remains poor.
The main reason is that HEVC (H.265) has patent issues. Open-source projects cannot afford the hefty patent fees. Therefore, support for HEIF images in open source is very limited. Currently, the only HEIF encoder/decoder I know of is the C++ implementation libheif.
Despite its poor compatibility, due to Apple’s large user base, when developing applications for end-users, it becomes necessary to consider HEIF image compatibility for Apple users.
In Rust, we use a binding library based on libheif:
libheif-rs = { version = "2.1", default-features = false, features = ["v1_17"] }
image = {version = "0.25.6"}
Since it is a binding library, you need to install libheif on your system. For example, on Windows:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.bat
./vcpkg integrate install
./vcpkg install libheif
On Ubuntu/Debian, simply run: sudo apt install libheif-dev.
For macOS, install directly using brew: brew install libheif.
Since the image crate integrates most commonly used image formats, our goal is to decode HEIF images using libheif-rs and convert them into image objects, which can then naturally be converted to other common formats.
Decoding the image:
let lib_heif = LibHeif::new();
//let ctx = HeifContext::read_from_file("./data/test.heif")?; // If loading from a file
let ctx = HeifContext::read_from_bytes(data)?;
let handle = ctx.primary_image_handle()?;
// Decode HEIF image to RGB format
let image = lib_heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?;
This gives us the image pixel data:
let planes = image.planes();
Convert the pixel data to RgbImage format:
let img_buf = image::RgbImage::from_raw( image.width(), image.height(), planes.interleaved.unwrap().data.to_vec(), ) .context("Invalid image data")?;
The rest is straightforward. For instance, saving it as a jpg format:
img_buf.save_with_format("output.jpg", image::ImageFormat::Jpeg)?;
Side Note
I once had the opportunity to handle HEIF issues on the frontend.
There is a package called heic2any on the frontend. It appears to have the most detailed documentation, but this package seems to have some issues, resulting in strange errors like HEIC/HEIF files are not supported.
The best approach is to use the heic-convert package. The issue with heic-convert is that it only provides Node.js environment call documentation.
The specific browser implementation is as follows:
import convert from 'heic-convert/browser'
let elementFiles = (e.target as HTMLInputElement).files;
const file = elementFiles[0];
const buffer = new Uint8Array(await file.arrayBuffer())
const outputBuffer = await convert({
buffer: buffer, // the HEIC file buffer
format: 'JPEG', // output format
quality: 1 // the jpeg compression quality, between 0 and 1
});
const blob = new Blob([outputBuffer], {type: "image/jpeg"});
const heicFile = new File([blob], 'example.jpg', {type: "image/jpeg"});
The key point lies in constructing the buffer data. The signature of convert’s buffer is ArrayBufferLike.