Apr 08, 2025
3 min read
Rust,

Conversion of HEIF/HEIC Format Images to Other Image Formats

This article introduces methods for converting HEIF/HEIC format images, which suffer from poor compatibility due to patent issues related to HEVC technology. In Rust, HEIF images can be decoded using `libheif-rs` and converted to other formats via the `image` crate. On the frontend, similar functionality can be achieved with `heic-convert`, focusing on constructing buffer data that meets requirements for format conversion.

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.